diff --git a/SystemSettings.py b/SystemSettings.py index 1891b36..211f132 100644 --- a/SystemSettings.py +++ b/SystemSettings.py @@ -1,7 +1,8 @@ class SystemSettings: - def __init__(self, dataDir="/Data", multiZoneIrrigation=False, defaultAutoIrrigationDuration=10, defaultManualIrrigationDuration=10, defaultManualOffDuration=10, webDurationOptions=[5, 10, 15, 20, 25, 30, 45, 60]): + def __init__(self, cronJobFrequency=2, dataDir="/Data", multiZoneIrrigation=False, defaultAutoIrrigationDuration=10, defaultManualIrrigationDuration=10, defaultManualOffDuration=10, webDurationOptions=[5, 10, 15, 20, 25, 30, 45, 60]): + self.cronJobFrequency = cronJobFrequency self.dataDir = dataDir self.multiZoneIrrigation = multiZoneIrrigation self.defaultAutoIrrigationDuration = defaultAutoIrrigationDuration diff --git a/Webserver/Templates/action.html b/Webserver/Templates/action.html new file mode 100644 index 0000000..2e5ea85 --- /dev/null +++ b/Webserver/Templates/action.html @@ -0,0 +1,23 @@ + + + + + {{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }} + + + {% include "header.html" %} + +
+ +

Action

+ +
+

+ Sucess: {{ sucess }} +

+
+ +
+ + + \ No newline at end of file diff --git a/Webserver/Templates/dashboard.html b/Webserver/Templates/dashboard.html deleted file mode 100644 index 502b46c..0000000 --- a/Webserver/Templates/dashboard.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - {{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }} - - - - - - {% include "header.html" %} - - -
- -

{{ translater.getTranslation("Dashboard") }}

- - -

{{ translater.getTranslation("planed irrigationjobs") }}

-
- - - - - - - - - - - - {% for job in zoneManager.pipeLine %} - - - - - - - {% endfor %} - -
ID{{ translater.getTranslation("Zone") }}{{ translater.getTranslation("planed duration") }}
{{ job.id }}{{ job.zone.number|string + ": " + job.zone.name }}{{ ((job.duration/60)|int)|string + " " + translater.getTranslation("minutes")}}
-
- - -

{{ translater.getTranslation("irrigation zones") }}

-
- {% for zone in zoneManager.zones %} -
- -

{{ zone.name }}

-
- - - - - - - - - - {% if (zone.setState == 1 or zone.setState == 2) %} - - - - - - - {% endif %} - {% if zone.planedDuration > 0 %} - - - - - - - {% endif %} - - - - - - - - - - - - - - - - - -
- - - - {{ translater.getTranslation("state") }}: - {{ translater.getTranslation("switched on") if zone.state else translater.getTranslation("switched off") }} - - - -
- {{translater.getTranslation("until") + zone.endTimeSetState|string}} - - -
- {{translater.getTranslation("irragation is planed for") + " " + ((zone.planedDuration/60)|int)|string + " " + translater.getTranslation("minutes") + "." }} - - -
- - {{ translater.getTranslation("operating mode") }}:{{translater.getTranslation("automatic mode") if zone.autoMode else translater.getTranslation("manual mode")}} - -
- - - - {{ translater.getTranslation("actual humidity") }}:{{ zone.actualHumidity}}
- - {{ translater.getTranslation("desired humidity") }}:{{ zone.desiredHumidity }}
-

{{ zone.number }}

-
- - {% endfor %} - - - -
- -
-



- - - \ No newline at end of file diff --git a/Webserver/Templates/dashboard/dashboard.html b/Webserver/Templates/dashboard/dashboard.html new file mode 100644 index 0000000..8047d81 --- /dev/null +++ b/Webserver/Templates/dashboard/dashboard.html @@ -0,0 +1,119 @@ +{% include "header.html" %} + + + + + + + {{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }} + + + + + + + +
+ +

{{ translater.getTranslation("Dashboard") }}

+ + +
+
+
+ + + + + + + + + + + + + {% include "dashboard/pipeline.html" %} + +
{{ translater.getTranslation("planed irrigationjobs") }}
+
+ Text unter Table +
+
+ + + +

{{ translater.getTranslation("irrigation zones") }}

+
+ {% for zone in zoneManager.zones %} +
+ {% include "dashboard/zone.html" %} +
+ + {% endfor %} + + + +
+ +
+



+ + + + +{% include "footer.html" %} \ No newline at end of file diff --git a/Webserver/Templates/dashboard/irrigationJob.html b/Webserver/Templates/dashboard/irrigationJob.html new file mode 100644 index 0000000..d552487 --- /dev/null +++ b/Webserver/Templates/dashboard/irrigationJob.html @@ -0,0 +1,7 @@ + + + {{ job.zone.number|string}} + {{job.zone.name }} + {{ ((job.duration/60)|int)|string + " " + translater.getTranslation("minutes")}} + + \ No newline at end of file diff --git a/Webserver/Templates/dashboard/pipeline.html b/Webserver/Templates/dashboard/pipeline.html new file mode 100644 index 0000000..9492586 --- /dev/null +++ b/Webserver/Templates/dashboard/pipeline.html @@ -0,0 +1,9 @@ + +{% for job in zoneManager.pipeLine %} + {% include "dashboard/irrigationJob.html" %} +{% endfor %} + + + + + diff --git a/Webserver/Templates/dashboard/zone.html b/Webserver/Templates/dashboard/zone.html new file mode 100644 index 0000000..6c4c044 --- /dev/null +++ b/Webserver/Templates/dashboard/zone.html @@ -0,0 +1,79 @@ + + +

{{ zone.name }}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + {{ translater.getTranslation("state") }}: + + + +
+ {{translater.getTranslation("until") }}

+
+ +
+ {{translater.getTranslation("irragation is planed for") }}

{{ translater.getTranslation("minutes") + "." }} +
+ +
+ +

+
+
+ + {{ translater.getTranslation("operating mode") }}:
+ + + + {{ translater.getTranslation("actual humidity") }}:
+ + {{ translater.getTranslation("desired humidity") }}:
+

{{ zone.number }}

\ No newline at end of file diff --git a/Webserver/Templates/footer.html b/Webserver/Templates/footer.html new file mode 100644 index 0000000..149a04d --- /dev/null +++ b/Webserver/Templates/footer.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Webserver/Templates/header.html b/Webserver/Templates/header.html index d1eb6a4..5af1b2b 100644 --- a/Webserver/Templates/header.html +++ b/Webserver/Templates/header.html @@ -1,49 +1,91 @@ - - - - - - - + + + + + - - -
- - - - - -

{{ translater.getTranslation("Irrigation") }}-
{{ translater.getTranslation("system") }}

-
- - -
- - + + +
+ + + + + +

{{ translater.getTranslation("Irrigation") }}-
{{ translater.getTranslation("system") }}

+
+ + + +
+ + + diff --git a/Webserver/Templates/zone.html b/Webserver/Templates/zones/zone.html similarity index 100% rename from Webserver/Templates/zone.html rename to Webserver/Templates/zones/zone.html diff --git a/Webserver/Templates/zones.html b/Webserver/Templates/zones/zones.html similarity index 100% rename from Webserver/Templates/zones.html rename to Webserver/Templates/zones/zones.html diff --git a/Webserver/__init__.py b/Webserver/__init__.py index 417866b..bc90e53 100644 --- a/Webserver/__init__.py +++ b/Webserver/__init__.py @@ -33,16 +33,38 @@ class Webserver: return redirect(url_for('showDashboard')) @app.route('/action', methods=['GET', 'POST']) - @app.route('/action/', methods=['GET', 'POST']) - @app.route('/action//', methods=['GET', 'POST']) - @app.route('/action///', 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 + if(request.method == 'POST'): + try: + command = request.form['command'] + except: + pass + try: + index_str = request.form['index'] + index = int(index_str) + except: + pass + try: + value_str = request.form['value'] + value = int(value_str) + except: + pass + elif(request.method == 'GET'): + try: + command = request.args['command'] + except: + pass + try: + index_str = request.args['index'] + index = int(index_str) + except: + pass + try: + value_str = request.args['value'] + value = int(value_str) + except: + pass match command: case "switch_zone_on": if (index and value): @@ -80,28 +102,41 @@ class Webserver: case "delete_job_by_id": if (index): self.zoneManager.deleteIrrigationJobByID(index) - - + case "get_dashboard_zone_html": + if (index): + zone = self.zoneManager.getZone(index) + return render_template('dashboard/zone.html', translater=self.translater, zoneManager=self.zoneManager, zone=zone) + case "get_dashboard_pipeline_html": + return render_template("dashboard/pipeline.html", translater=self.translater, zoneManager=self.zoneManager) + case "get_zone_list": + return self.zoneManager.zonesToJSON(self.translater) + case "get_zone_info": + if (index): + zone = self.zoneManager.getZone(index) + return zone.toJSON(self.translater) + case "get_pipeline": + return self.zoneManager.pipelineToJSON(self.translater) 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) + return render_template('dashboard/dashboard.html', translater=self.translater, zoneManager=self.zoneManager) @app.route('/zones') @app.route('/zones/') def showZones(zoneNumber=False): if (zoneNumber): - return render_template('zone.html', translater=self.translater, zone=self.zoneManager.getZone(zoneNumber)) + return render_template('zones/zone.html', translater=self.translater, zoneManager=self.zoneManager, zone=self.zoneManager.getZone(zoneNumber)) else: - return render_template('zones.html', translater=self.translater, zones=self.zoneManager.zones) + return render_template('zones/zones.html', translater=self.translater, zoneManager=self.zoneManager) @app.route('/times') def showTimes(): - return render_template('times.html', translater=self.translater) + return render_template('times.html', translater=self.translater, zoneManager=self.zoneManager) @app.route('/system') def showSystem(): - return render_template('system.html', translater=self.translater) + return render_template('system.html', translater=self.translater, zoneManager=self.zoneManager) - app.run(debug=True, port=self.port) + app.run(debug=True, host="0.0.0.0", port=self.port) diff --git a/Webserver/static/Styles/dashboard.css b/Webserver/static/Styles/dashboard.css index 7fd7357..b8df3be 100644 --- a/Webserver/static/Styles/dashboard.css +++ b/Webserver/static/Styles/dashboard.css @@ -1,3 +1,5 @@ + + #zones{ display: grid; grid-template-columns: repeat(3, 1fr); @@ -57,6 +59,10 @@ td.value{ text-align: left; position: relative; } +p.end_time_value, p.planed_duration_value { + display: inline; +} + diff --git a/Webserver/static/Styles/scrollingTable.css b/Webserver/static/Styles/scrollingTable.css new file mode 100644 index 0000000..c43fba8 --- /dev/null +++ b/Webserver/static/Styles/scrollingTable.css @@ -0,0 +1,118 @@ +.scrollingtable { + box-sizing: border-box; + display: inline-block; + vertical-align: middle; + overflow: hidden; + width: auto; /*set table width here if using fixed value*/ + /*min-width: 100%;*/ /*set table width here if using %*/ + height: 300px; /*set table height here; can be fixed value or %*/ + /*min-height: 104px;*/ /*if using % height, make this at least large enough to fit scrollbar arrows + captions + thead*/ + font-family: Verdana, Tahoma, sans-serif; + font-size: 15px; + line-height: 20px; + padding-top: 20px; /*this determines top caption height*/ + padding-bottom: 20px; /*this determines bottom caption height*/ + text-align: left; +} +.scrollingtable * {box-sizing: border-box;} +.scrollingtable > div { + position: relative; + border-top: 1px solid black; /*top table border*/ + height: 100%; + padding-top: 20px; /*this determines column header height*/ +} +.scrollingtable > div:before { + top: 0; + background: cornflowerblue; /*column header background color*/ +} +.scrollingtable > div:before, +.scrollingtable > div > div:after { + content: ""; + position: absolute; + z-index: -1; + width: 100%; + height: 50%; + left: 0; +} +.scrollingtable > div > div { + /*min-height: 43px;*/ /*if using % height, make this at least large enough to fit scrollbar arrows*/ + max-height: 100%; + overflow: scroll; /*set to auto if using fixed or % width; else scroll*/ + overflow-x: hidden; + border: 1px solid black; /*border around table body*/ +} +.scrollingtable > div > div:after {background: white;} /*match page background color*/ +.scrollingtable > div > div > table { + width: 100%; + border-spacing: 0; + margin-top: -20px; /*inverse of column header height*/ + /*margin-right: 17px;*/ /*uncomment if using % width*/ +} +.scrollingtable > div > div > table > caption { + position: absolute; + top: -20px; /*inverse of caption height*/ + margin-top: -1px; /*inverse of border-width*/ + width: 100%; + font-weight: bold; + text-align: center; +} +.scrollingtable > div > div > table > * > tr > * {padding: 0;} +.scrollingtable > div > div > table > thead { + vertical-align: bottom; + white-space: nowrap; + text-align: center; +} +.scrollingtable > div > div > table > thead > tr > * > div { + display: inline-block; + padding: 0 6px 0 6px; /*header cell padding*/ +} +.scrollingtable > div > div > table > thead > tr > :first-child:before { + content: ""; + position: absolute; + top: 0; + left: 0; + height: 20px; /*match column header height*/ + border-left: 1px solid black; /*leftmost header border*/ +} +.scrollingtable > div > div > table > thead > tr > * > div[label]:before, +.scrollingtable > div > div > table > thead > tr > * > div > div:first-child, +.scrollingtable > div > div > table > thead > tr > * + :before { + position: absolute; + top: 0; + white-space: pre-wrap; + color: white; /*header row font color*/ +} +.scrollingtable > div > div > table > thead > tr > * > div[label]:before, +.scrollingtable > div > div > table > thead > tr > * > div[label]:after {content: attr(label);} +.scrollingtable > div > div > table > thead > tr > * + :before { + content: ""; + display: block; + min-height: 20px; /*match column header height*/ + padding-top: 1px; + border-left: 1px solid black; /*borders between header cells*/ +} +.scrollingtable .scrollbarhead {float: right;} +.scrollingtable .scrollbarhead:before { + position: absolute; + width: 100px; + top: -1px; /*inverse border-width*/ + background: white; /*match page background color*/ +} +.scrollingtable > div > div > table > tbody > tr:after { + content: ""; + display: table-cell; + position: relative; + padding: 0; + border-top: 1px solid black; + top: -1px; /*inverse of border width*/ +} +.scrollingtable > div > div > table > tbody {vertical-align: top;} +.scrollingtable > div > div > table > tbody > tr {background: white;} +.scrollingtable > div > div > table > tbody > tr > * { + border-bottom: 1px solid black; + padding: 0 6px 0 6px; + height: 20px; /*match column header height*/ +} +.scrollingtable > div > div > table > tbody:last-of-type > tr:last-child > * {border-bottom: none;} +.scrollingtable > div > div > table > tbody > tr:nth-child(even) {background: gainsboro;} /*alternate row color*/ +.scrollingtable > div > div > table > tbody > tr > * + * {border-left: 1px solid black;} /*borders between body cells*/ \ No newline at end of file diff --git a/Webserver/static/js/action.js b/Webserver/static/js/action.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Webserver/static/js/action.js @@ -0,0 +1 @@ + diff --git a/Webserver/static/js/webhook.js b/Webserver/static/js/webhook.js new file mode 100644 index 0000000..4cc0853 --- /dev/null +++ b/Webserver/static/js/webhook.js @@ -0,0 +1,70 @@ +function send_web_request(url, messageString, varString, handleResponse) { + // Browserkompatibles Request-Objekt erzeugen: + r = null; + + if(window.XMLHttpRequest) + { + + r = new XMLHttpRequest(); + } + else if(window.ActiveXObject) + { + + try + { + r = new ActiveXObject('Msxml2.XMLHTTP'); + } + catch(e1) + { + try + { + r = new ActiveXObject('Microsoft.XMLHTTP'); + } + catch(e2) + { + alert("Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."); + } + } + } + + // Wenn Request-Objekt vorhanden, dann Anfrage senden: + if(r != null) + { + + + + r.open('POST', url, true); + + r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + + if(handleResponse != null) { + // HTTP-POST + r.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + handleResponse(this.responseText); + } + }; + } + + r.send(varString); + + + if(messageString != 'no') + { + alert(messageString); + } + sleep(500).then(() => { + + //window.location.href = window.location.href; + //document.location.reload(); + + }); + + } + else + { + alert("Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."); + } + + +} \ No newline at end of file diff --git a/Zone.py b/Zone.py index b86da92..5694dd8 100644 --- a/Zone.py +++ b/Zone.py @@ -1,4 +1,5 @@ import time +import json class Zone: @@ -13,6 +14,21 @@ class Zone: self.endTimeSetState = endTimeSetState self.planedDuration = planedDuration + def toJSON(self, translater): + return { + "number": self.number, + "name": self.name, + "actualHumidity": self.actualHumidity, + "desiredHumidity": self.desiredHumidity, + "autoMode": self.autoMode, + "operationMode_text": translater.getTranslation("automatic mode" if self.autoMode else "manual mode"), + "state": self.state, + "state_text": translater.getTranslation("switched on" if self.state else "switched off"), + "setState": self.setState, + "endTimeSetState" : self.endTimeSetState, + "planedDuration": self.planedDuration, + } + def setZoneManager(self, zoneManager): self.zoneManager = zoneManager diff --git a/ZoneManager.py b/ZoneManager.py index 2ef1d91..114567a 100644 --- a/ZoneManager.py +++ b/ZoneManager.py @@ -1,6 +1,7 @@ +import json import threading from random import Random -from time import time + from FileIO import FileIO @@ -15,6 +16,20 @@ class ZoneManager: self.pipeLine = [] self.piplineMutexLock = threading.Lock() + def zonesToJSON(self, translater): + zoneList = [] + for zone in self.zones: + zoneList.append(zone.toJSON(translater)) + return zoneList + + def pipelineToJSON(self, translater): + jobList = [] + with self.piplineMutexLock: + for irrigationJob in self.pipeLine: + jobList.append(irrigationJob.toJSON(translater)) + return jobList + + def getZone(self, number): for zone in self.zones: if(zone.number == number): @@ -52,7 +67,9 @@ class ZoneManager: return False def switchZoneState(self, zone, state, duration, instant=False): - if(instant or self.systemSettings.multiZoneIrrigation or state==False ): #or (not self.isAnyZoneBusy()) + if(not duration > 0): + zone.switchState(state=False, duration=0, instant=True) + elif(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)) @@ -100,5 +117,12 @@ class IrrigationJob: self.duration = duration self.zone.switchState(state=True, duration=duration, instant=False) + def toJSON(self, translater): + return { + "id": self.id, + "zone": self.zone.toJSON(translater), + "duration": self.duration, + } + def process(self): self.zone.switchState(state=True, duration=self.duration, instant=True) \ No newline at end of file diff --git a/main.py b/main.py index 8645ade..60a44cc 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ 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]) +systemSettings = SystemSettings(cronJobFrequency=0.5, 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) @@ -15,7 +15,7 @@ def cronJobs(): while True: zoneManager.cronJobs() print("Cronjobs done\nactual Time: " + str(time.time())) - time.sleep(0.5) + time.sleep(systemSettings.cronJobFrequency) if __name__ == "__main__": cronjob_Thread = threading.Thread(target=cronJobs)