Merge pull request 'Webserver_Dashboard and basic functionality' (#1) from Webserver_Layout into master

Reviewed-on: #1
This commit is contained in:
Roman Schenk 2022-08-23 11:28:04 -07:00
commit 0363682d7c
20 changed files with 1114 additions and 1 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
# ---> Python
# ---> Webserver
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

12
FileIO/__init__.py Normal file
View File

@ -0,0 +1,12 @@
from Zone import Zone
class FileIO:
def __init__(self, systemSettings):
self.systemSettings = systemSettings
def loadZones(self):
zones = []
for i in range(12):
zones.append(Zone(number=i+1, name="Zone " + str(i+1), actualHumidity=50, desiredHumidity=70, autoMode=True, state=False, setState=0, endTimeSetState=0, planedDuration=0))
return zones

10
SystemSettings.py Normal file
View File

@ -0,0 +1,10 @@
class SystemSettings:
def __init__(self, dataDir="/Data", multiZoneIrrigation=False, defaultAutoIrrigationDuration=10, defaultManualIrrigationDuration=10, defaultManualOffDuration=10, webDurationOptions=[5, 10, 15, 20, 25, 30, 45, 60]):
self.dataDir = dataDir
self.multiZoneIrrigation = multiZoneIrrigation
self.defaultAutoIrrigationDuration = defaultAutoIrrigationDuration
self.defaultManualIrrigationDuration = defaultManualIrrigationDuration
self.defaultManualOffDuration = defaultManualOffDuration
self.webDurationOptions = webDurationOptions

View File

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='Styles/dashboard.css') }}">
<title>{{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }}</title>
<style>
@media screen and (min-width: 2051px){
#zones{
grid-template-rows: repeat( {{ (zoneManager.zones|length / 3) + 1 }} , minmax(150px, auto));
}
}
@media screen and (max-width: 2050px) and (min-width: 1351px){
#zones{
grid-template-rows: repeat( {{ (zoneManager.zones|length / 2) + 1 }} , minmax(150px, auto));
}
}
@media screen and (max-width: 1350px){
#zones{
grid-template-rows: repeat( {{ zoneManager.zones|length }} , minmax(150px, auto));
}
}
</style>
</head>
<body>
{% include "header.html" %}
<main>
<h2>{{ translater.getTranslation("Dashboard") }}</h2>
<h3>{{ translater.getTranslation("planed irrigationjobs") }}</h3>
<div id="pipeline">
<table>
<thead>
<tr>
<td>ID</td>
<td>{{ translater.getTranslation("Zone") }}</td>
<td>{{ translater.getTranslation("planed duration") }}</td>
</tr>
</thead>
<tbody>
{% for job in zoneManager.pipeLine %}
<tr>
<td>{{ job.id }}</td>
<td>{{ job.zone.number|string + ": " + job.zone.name }}</td>
<td>{{ ((job.duration/60)|int)|string + " " + translater.getTranslation("minutes")}}</td>
<td><button onclick="executeAction('delete_job_by_id','{{ job.id }}', 0)">{{ translater.getTranslation("delete") }}</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<h3>{{ translater.getTranslation("irrigation zones") }}</h3>
<div id="zones">
{% for zone in zoneManager.zones %}
<div id="zone_{{ zone.number }}" class="zone">
<a href="/zones/{{ zone.number}}">
<h4>{{ zone.name }}</h3>
</a>
<table>
<tbody>
<tr>
<td class="icon">
<span class="outer_dot">
<span class="inner_icon {{ 'dot_green' if zone.state else 'dot_red' }}"></span>
</span>
</td>
<td class="property">{{ translater.getTranslation("state") }}:</td>
<td class="value">
{{ translater.getTranslation("switched on") if zone.state else translater.getTranslation("switched off") }}
</td>
<td>
<select id="duration_zone_{{ zone.number }}">
{% for option in zoneManager.systemSettings.webDurationOptions %}
<option value="{{ option }}">{{ option }} {{ translater.getTranslation("minutes") }}</option>
{% endfor %}
</select>
<button onclick="executeActionByValueID({{'"switch_zone_off"' if zone.state else '"switch_zone_on"'}},'{{ zone.number }}', 'duration_zone_{{ zone.number }}', 60)">{{ translater.getTranslation("turn off") if zone.state else translater.getTranslation("turn on") }}</button>
</td>
</tr>
{% if (zone.setState == 1 or zone.setState == 2) %}
<tr>
<td></td>
<td></td>
<td>
{{translater.getTranslation("until") + zone.endTimeSetState|string}}
</td>
<td>
<button onclick="executeAction({{'"switch_zone_off"' if zone.state else '"switch_zone_on"'}},'{{ zone.number }}', -1)">{{ translater.getTranslation("cancel") }}</button>
</td>
</tr>
{% endif %}
{% if zone.planedDuration > 0 %}
<tr>
<td></td>
<td></td>
<td>
{{translater.getTranslation("irragation is planed for") + " " + ((zone.planedDuration/60)|int)|string + " " + translater.getTranslation("minutes") + "." }}
</td>
<td>
<button onclick="executeAction('delete_jobs_for_zone','{{ zone.number }}', 0)">{{ translater.getTranslation("delete") }}</button>
</td>
</tr>
{% endif %}
<tr>
<td>
<label class="toggle">
<input type="checkbox" {{ "checked" if zone.autoMode }} onchange="switchZoneMode(this.checked, {{ zone.number }})">
<span class="slider"></span>
<span class="labels" data-on="A" data-off="H"></span>
</label>
</td>
<td class="property">{{ translater.getTranslation("operating mode") }}:</td>
<td class="value">{{translater.getTranslation("automatic mode") if zone.autoMode else translater.getTranslation("manual mode")}}</td>
<td>
<button onclick="executeAction('switch_zone_mode','{{ zone.number }}', '{{ 'manual' if zone.autoMode else 'automatic' }}')">{{translater.getTranslation("switch to manual mode") if zone.autoMode else translater.getTranslation("switch to automatic mode")}}</button>
</td>
</tr>
<tr>
<td class="icon">
<span class="outer_dot">
<span class="inner_icon {{ 'dot_green' if(zone.actualHumidity >= zone.desiredHumidity) else 'dot_red' }}"></span>
</span>
</td>
<td class="property">{{ translater.getTranslation("actual humidity") }}:</td>
<td class="value">{{ zone.actualHumidity}}</td>
</tr>
<tr>
<td class="icon">
</td>
<td class="property">{{ translater.getTranslation("desired humidity") }}:</td>
<td class="value">{{ zone.desiredHumidity }}</td>
</tr>
</tbody>
</table>
<p class="zone_number">{{ zone.number }}</p>
</div>
{% endfor %}
</div>
</main>
<p><br><br></p>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='Styles/header.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='Styles/main.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='Styles/switch.css') }}">
<meta charset="UTF-8">
<meta http-equiv="refresh" content="5" >
<script>
function executeAction(command, index, value) {
send_web_request('{{url_for("executeAction")}}/' + command + '/' + index + '/' + value, 'no', 'variable');
}
function executeActionByValueID(command, index, valueID, valueFactor){
var value = document.getElementById(valueID).value * valueFactor;
executeAction(command, index, value);
}
function switchZoneMode(autoMode, zone) {
executeAction('switch_zone_mode' ,zone, (autoMode ? 'automatic' : 'manual'))
}
</script>
</head>
<body>
<header>
<nav>
<ul>
{% for item in nav.top %}
<li {{ 'class=active' if item.is_active else '' }}>
<a href="{{ item.url }}">
{{ item.label }}
</a>
</li>
{% endfor %}
</ul>
</nav>
<a class="heading" href="{{ url_for('startPage')}}">
<img src="{{ url_for('static', filename='img/header/wassertropfen.png') }}" alt="" class="title-img">
<h1>{{ translater.getTranslation("Irrigation") }}-<br>{{ translater.getTranslation("system") }}</h1>
</a>
<img src="{{ url_for('static', filename='img/header/blumenbeet.jpg') }}" alt="" class="header-img">
</header>
<script src="{{ url_for('static', filename='js/webhook.js') }}"></script>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>{{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }}</title>
</head>
<body>
{% include "header.html" %}
<main>
<h2>Systemeinstellungen</h2>
<div>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>{{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }}</title>
</head>
<body>
{% include "header.html" %}
<main>
<h2>Sperrzeiteneinstellung</h2>
<div>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>{{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }}</title>
</head>
<body>
{% include "header.html" %}
<main>
<h2>Zone</h2>
<div>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>{{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }}</title>
</head>
<body>
{% include "header.html" %}
<main>
<h2>Zonen</h2>
<div>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
<p>
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Sed excepturi quod dicta temporibus eveniet corporis incidunt molestiae, laborum deleniti.
Molestiae architecto quod veritatis laudantium aliquid aperiam earum quia voluptates saepe!
</p>
</div>
</main>
</body>
</html>

45
Webserver/Translater.py Normal file
View File

@ -0,0 +1,45 @@
from enum import Enum
class Language(Enum):
ENGLISH = 1
GERMAN = 2
class Translater:
def __init__(self, language):
self.language = language
self.dict_german = { "Irrigation": "Bewässerungs",
"system": "system",
"Dashboard": "Startseite",
"irrigation zones": "Bewässerungszonen",
"blocking times": "Sperrzeiten",
"system settings": "Systemeinstellungen",
"Zone": "Zone",
"state": "Status",
"operating mode": "Betriebsmodus",
"actual humidity": "momentane Feuchtigkeit",
"desired humidity": "gewünschte Feuchtigkeit",
"switched on": "eingeschaltet",
"switched off": "ausgeschaltet",
"turn on": "einschalten",
"turn off": "ausschalten",
"manual mode": "Handbetrieb",
"automatic mode": "Automatikbetrieb",
"switch to manual mode": "Auf Handbetrieb umstellen",
"switch to automatic mode": "Auf Automatikbetrieb umstellen",
"minutes": "Minuten",
"until": "bis",
"irragation is planed for": "Bewässerung ist geplant für",
"planed irrigationjobs": "geplante Bewässerungsaufträge",
"planed duration": "geplante Dauer",
"cancel": "abbrechen",
"delete": "löschen",
}
def getTranslation(self, english_String):
match self.language:
case Language.ENGLISH:
return english_String
case Language.GERMAN:
return self.dict_german[english_String]
case _:
return "no translations for these language"

107
Webserver/__init__.py Normal file
View File

@ -0,0 +1,107 @@
from flask import Flask, render_template, request, redirect, url_for
from flask_navigation import Navigation
from Webserver.Translater import Translater, Language
class Webserver:
def __init__(self, zoneManager, port=80):
self.port = port
self.zoneManager = zoneManager
self.translater_EN = Translater(Language.ENGLISH)
self.translater_DE = Translater(Language.GERMAN)
self.translater = self.translater_DE
def startWebserver(self):
app = Flask("Bewässerungssystem", template_folder="Webserver/templates", static_folder="Webserver/static")
nav = Navigation(app)
nav.Bar('top', [
nav.Item(self.translater.getTranslation('Dashboard'), 'showDashboard'),
nav.Item(self.translater.getTranslation('irrigation zones'), 'showZones'),
nav.Item(self.translater.getTranslation('blocking times'), 'showTimes'),
nav.Item(self.translater.getTranslation('system settings'), 'showSystem')
# nav.Item('Gfg', 'gfg', {'page': 5}), #(example with pages)
])
# Example Route with pages:
# @app.route('/gfg/<int:page>')
# def gfg(page):
# return render_template('gfg.html', page=page)
@app.route('/')
def startPage():
return redirect(url_for('showDashboard'))
@app.route('/action', methods=['GET', 'POST'])
@app.route('/action/<command>', methods=['GET', 'POST'])
@app.route('/action/<command>/<index_str>', methods=['GET', 'POST'])
@app.route('/action/<command>/<index_str>/<value_str>', methods=['GET', 'POST'])
def executeAction(command=False, index_str=False, value_str=False):
sucess = False
try:
index = int(index_str)
value = int(value_str)
except:
pass
match command:
case "switch_zone_on":
if (index and value):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=True, duration=value)
sucess = True
elif(index):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=True, duration=self.zoneManager.systemSettings.defaultManualIrrigationDuration)
sucess = True
case "switch_zone_off":
if (index and value):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=False, duration=value, instant=True)
sucess = True
elif(index):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=False, duration=self.zoneManager.systemSettings.defaultManualOffDuration, instant=True)
sucess = True
case "switch_zone_mode":
if (index and value_str):
zone = self.zoneManager.getZone(index)
match value_str:
case "automatic":
zone.switchMode(autoMode=True)
sucess = True
case "manual":
zone.switchMode(autoMode=False)
sucess = True
case "set_desired_humidity":
if (index and value):
zone = self.zoneManager.getZone(index)
zone.desiredHumidity = value
sucess = True
case "delete_jobs_for_zone":
if (index):
zone = self.zoneManager.getZone(index)
self.zoneManager.deleteIrrigationJobsForZone(zone)
case "delete_job_by_id":
if (index):
self.zoneManager.deleteIrrigationJobByID(index)
return render_template('action.html', translater=self.translater, zones=self.zoneManager.zones, sucess=sucess)
@app.route('/dashboard')
def showDashboard():
return render_template('dashboard.html', translater=self.translater, zoneManager=self.zoneManager)
@app.route('/zones')
@app.route('/zones/<zoneNumber>')
def showZones(zoneNumber=False):
if (zoneNumber):
return render_template('zone.html', translater=self.translater, zone=self.zoneManager.getZone(zoneNumber))
else:
return render_template('zones.html', translater=self.translater, zones=self.zoneManager.zones)
@app.route('/times')
def showTimes():
return render_template('times.html', translater=self.translater)
@app.route('/system')
def showSystem():
return render_template('system.html', translater=self.translater)
app.run(debug=True, port=self.port)

View File

@ -0,0 +1,123 @@
#zones{
display: grid;
grid-template-columns: repeat(3, 1fr);
/* grid-template-rows: repeat( {{ zones|length }} , minmax(150px, auto)); moved to content div to use jinja */
grid-gap: 30px;
margin: 0 auto;
}
.zone{
position: relative;
background: green;
color: white;
font-size: larger;
padding-left: 30px;
padding-bottom: 30px;
padding-right: 30px;
}
.zone a{
color: inherit;
text-decoration: inherit;
}
.zone:hover{
animation: scale 0.5s linear 0.0s normal both;
}
@keyframes scale{
0% {transform: scale3d(100%, 100%, 100%);}
100% {transform: scale3d(107%, 120%, 100%);}
}
.zone h4{
padding-top: 25px;
padding-bottom: 25px;
font-size: x-large;
}
.zone_number{
position: absolute;
top: 15px;
right: 30px;;
}
.zone table{
table-layout: auto;
width: 100%;
}
td.icon{
text-align: left;
width: 30px;
}
td.property{
text-align: left;
}
td.value{
text-align: left;
position: relative;
}
.outer_dot {
height: 30px;
width: 30px;
background-color: #bbb;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
z-index: 1;
}
.inner_icon{
color: black;
font-size: 16px;
font-weight: bold;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
height: 15px;
width: 15px;
margin-left: 7.5px;
margin-top: 7.5px;
border-radius: 50%;
display: inline-block;
z-index: 2;
}
.dot_green {
background-color: green;
}
.dot_red {
background-color: red;
}
@media screen and (min-width: 2051px){
.zone:nth-child(even){
background: blue;
}
}
@media screen and (max-width: 2050px) and (min-width: 1351px){
#zones{
grid-template-columns: repeat(2, 1fr);
}
.zone:nth-child(4n){
background: blue;
}
.zone:nth-child(4n+1){
background: blue;
}
}
@media screen and (max-width: 1350px){
#zones{
grid-template-columns: repeat(1, 1fr);
}
.zone{
max-width: 600px;
}
.zone:nth-child(even){
background: blue;
}
}

View File

@ -0,0 +1,70 @@
header{
font-family: "Century Gothic", CenturyGothic, AppleGothic, sans-serif;
padding: 2px;
position: sticky;
left: 0px;
top: 0px;
z-index: 20;
}
nav{
z-index: 21;
position: absolute;
top: 0px;
right: 25px;
}
nav ul li{
display: inline;
}
nav ul li a{
white-space: nowrap;
text-decoration: none;
color: white;
background-color: blue;
padding: 5px;
margin: 10px;
}
nav ul li a:hover{
background-color: darkblue;
}
.active{
font-size : 1.2em;
font-weight : bold;
}
.heading{
text-align: center;
color: white;
position: absolute;
left: 10%;
top: 2px;
background-color: darkblue;
padding-top: 0px;
padding-bottom: 0px;
margin: 0px;
text-decoration: none;
}
.heading h1{
font-size: 32;
line-height: 2em;
text-transform: uppercase;
margin-top: 0px;
}
.title-img{
width: 186px;
margin-top: 10px;
}
.header-img{
width: 100%;
height: 300px;
object-fit: cover;
object-position: 100%;
}

View File

@ -0,0 +1,17 @@
main{
max-width: 90%;
width: 1800px;
margin-left: auto;
margin-right: auto;
}
h2{
font-family: "Century Gothic", CenturyGothic, AppleGothic, sans-serif;
text-transform: uppercase;
margin-bottom: 2em;
}
h3{
font-family: "Century Gothic", CenturyGothic, AppleGothic, sans-serif;
text-transform: uppercase;
}

View File

@ -0,0 +1,88 @@
.toggle {
--width: 60px;
--height: 25px; /*calc(var(--width) / 3); */
position: relative;
display: inline-block;
width: var(--width);
height: var(--height);
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3);
border-radius: var(--height);
cursor: pointer;
margin-right: 10px;
}
.toggle input {
display: none;
}
.toggle .slider {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: var(--height);
background-color: #ccc;
transition: all 0.4s ease-in-out;
}
.toggle .slider::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: calc(var(--height));
height: calc(var(--height));
border-radius: calc(var(--height) / 2);
background-color: #fff;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3);
transition: all 0.4s ease-in-out;
}
.toggle input:checked+.slider {
background-color: #2196F3;
}
.toggle input:checked+.slider::before {
transform: translateX(calc(var(--width) - var(--height)));
}
.toggle .labels {
position: absolute;
top: 4px;
left: 10px;
width: 100%;
height: 100%;
font-size: 16px;
font-family: sans-serif;
transition: all 0.4s ease-in-out;
}
.toggle .labels::after {
content: attr(data-off);
position: absolute;
right: 25px;
color: #4d4d4d;
opacity: 1;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
transition: all 0.4s ease-in-out;
}
.toggle .labels::before {
content: attr(data-on);
position: absolute;
left: 5px;
color: #ffffff;
opacity: 0;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.4);
transition: all 0.4s ease-in-out;
}
.toggle input:checked~.labels::after {
opacity: 0;
}
.toggle input:checked~.labels::before {
opacity: 1;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

96
Zone.py Normal file
View File

@ -0,0 +1,96 @@
import time
class Zone:
def __init__(self, number=0, name="", actualHumidity="100", desiredHumidity="0", autoMode=False, state=False, setState=0, endTimeSetState=0, planedDuration=0):
self.number = number
self.name = name
self.actualHumidity = actualHumidity
self.desiredHumidity = desiredHumidity
self.autoMode = autoMode #False = manual, True = automatic
self.state = state #True = on, False = off
self.setState = setState #0=default, 1=turned off for time, 2=turned on for time, 3=command in pipeline
self.endTimeSetState = endTimeSetState
self.planedDuration = planedDuration
def setZoneManager(self, zoneManager):
self.zoneManager = zoneManager
def timeOver(self):
return time.time() > self.endTimeSetState
def refreshStateAutomode(self):
match self.setState:
case 0:
self.state = False
if(self.planedDuration > 0):
self.setState = 3
elif(self.desiredHumidity > self.actualHumidity):
self.zoneManager.switchZoneState(zone=self, state=True, duration=self.zoneManager.systemSettings.defaultAutoIrrigationDuration, instant=False)
case 1:
if(self.timeOver()):
self.setState = 0
self.refreshStateAutomode()
else:
self.state = False
case 2:
if (self.timeOver()):
self.setState = 0
self.refreshStateAutomode()
else:
self.state = True
case 3:
self.state = False
if (not self.planedDuration > 0):
self.setState = 0
def refreshStateManualmode(self):
if(self.setState > 0):
match self.setState:
case 0:
self.state = False
if (self.planedDuration > 0):
self.setState = 3
case 1:
if(self.timeOver()):
self.setState = 0
self.state = False
case 2:
if (self.timeOver()):
self.setState = 0
self.state = False
else:
self.state = True
case 3:
self.state = False
if (not self.planedDuration > 0):
self.setState = 0
def refreshState(self):
self.planedDuration = self.zoneManager.getPlanedDurationForZone(zone=self)
if(self.autoMode):
self.refreshStateAutomode()
else:
self.refreshStateManualmode()
def switchMode(self, autoMode):
if(self.autoMode != autoMode):
self.autoMode = autoMode
self.state = False
self.refreshState()
def switchState(self, state, duration, instant):
if(instant):
if(not state):
self.setState = 1
else:
self.setState = 2
self.endTimeSetState = time.time() + duration
else:
if(state):
self.setState = 3
self.refreshState()

104
ZoneManager.py Normal file
View File

@ -0,0 +1,104 @@
import threading
from random import Random
from time import time
from FileIO import FileIO
class ZoneManager:
def __init__(self, systemSettings, fileIO):
self.random = Random()
self.systemSettings = systemSettings
self.fileIO = FileIO
self.zones = fileIO.loadZones()
for zone in self.zones:
zone.setZoneManager(self)
self.pipeLine = []
self.piplineMutexLock = threading.Lock()
def getZone(self, number):
for zone in self.zones:
if(zone.number == number):
return zone
def addIrrigationJob(self, job):
with self.piplineMutexLock:
self.pipeLine.append(job)
def deleteIrrigationJobByID(self, id):
i = 0
with self.piplineMutexLock:
while i < (len(self.pipeLine)):
irrigationJob = self.pipeLine[i]
if(irrigationJob.id == id):
self.pipeLine.pop(i)
break
else:
i = i + 1
def deleteIrrigationJobsForZone(self, zone):
i = 0
with self.piplineMutexLock:
while i < (len(self.pipeLine)):
irrigationJob = self.pipeLine[i]
if (irrigationJob.zone == zone):
self.pipeLine.pop(i)
else:
i = i + 1
def isAnyZoneBusy(self):
for zone in self.zones:
if(zone.state):
return True
return False
def switchZoneState(self, zone, state, duration, instant=False):
if(instant or self.systemSettings.multiZoneIrrigation or state==False ): #or (not self.isAnyZoneBusy())
zone.switchState(state=state, duration=duration, instant=True)
else:
self.addIrrigationJob(IrrigationJob(id=self.random.randint(a=100000000, b=999999999), zone=zone, duration=duration))
def switchZoneIndexState(self, zoneIndex, state, duration, instant=False):
zone = self.getZone(zoneIndex)
self.switchZoneState(zone, state, duration, instant)
def getPlanedDurationForZone(self, zone):
totalDuration = 0
with self.piplineMutexLock:
for irrigationJob in self.pipeLine:
if(irrigationJob.zone == zone):
totalDuration = totalDuration + irrigationJob.duration
return totalDuration
def refreshStates(self):
for zone in self.zones:
zone.refreshState()
def processPipeline(self):
self.piplineMutexLock.acquire()
if(len(self.pipeLine) > 0 and (not self.isAnyZoneBusy())):
irrigationJob = self.pipeLine[0]
self.pipeLine.pop(0)
self.piplineMutexLock.release()
irrigationJob.process()
else:
self.piplineMutexLock.release()
def cronJobs(self):
self.refreshStates()
self.processPipeline()
print("Executed Cron Jobs of ZoneManager.")
class IrrigationJob:
def __init__(self, id, zone, duration=0):
self.id = id
self.zone = zone
self.duration = duration
self.zone.switchState(state=True, duration=duration, instant=False)
def process(self):
self.zone.switchState(state=True, duration=self.duration, instant=True)

23
main.py Normal file
View File

@ -0,0 +1,23 @@
import threading
import time
from Webserver import Webserver
from ZoneManager import ZoneManager
from SystemSettings import SystemSettings
from FileIO import FileIO
systemSettings = SystemSettings(dataDir="/Data", multiZoneIrrigation=False, defaultAutoIrrigationDuration=10, defaultManualIrrigationDuration=10, defaultManualOffDuration=10, webDurationOptions=[1, 5, 10, 15, 20, 25, 30, 45, 60])
fileIO = FileIO(systemSettings)
zoneManager = ZoneManager(systemSettings=systemSettings, fileIO=fileIO)
webserver = Webserver(zoneManager=zoneManager, port=80)
def cronJobs():
while True:
zoneManager.cronJobs()
print("Cronjobs done\nactual Time: " + str(time.time()))
time.sleep(0.5)
if __name__ == "__main__":
cronjob_Thread = threading.Thread(target=cronJobs)
cronjob_Thread.start()
webserver.startWebserver()