diff --git a/.gitignore b/.gitignore index 55be276..7f162e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# ---> Python +# ---> Webserver # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/FileIO/__init__.py b/FileIO/__init__.py new file mode 100644 index 0000000..9d205c0 --- /dev/null +++ b/FileIO/__init__.py @@ -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 diff --git a/SystemSettings.py b/SystemSettings.py new file mode 100644 index 0000000..1891b36 --- /dev/null +++ b/SystemSettings.py @@ -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 \ No newline at end of file diff --git a/Webserver/Templates/dashboard.html b/Webserver/Templates/dashboard.html new file mode 100644 index 0000000..502b46c --- /dev/null +++ b/Webserver/Templates/dashboard.html @@ -0,0 +1,161 @@ + + + + + + {{ 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/header.html b/Webserver/Templates/header.html new file mode 100644 index 0000000..d1eb6a4 --- /dev/null +++ b/Webserver/Templates/header.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + +
+ + + + + +

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

+
+ + + +
+ + + + diff --git a/Webserver/Templates/system.html b/Webserver/Templates/system.html new file mode 100644 index 0000000..58d6f66 --- /dev/null +++ b/Webserver/Templates/system.html @@ -0,0 +1,52 @@ + + + + + {{ translater.getTranslation("Irrigation") }}{{ translater.getTranslation("system") }} + + + {% include "header.html" %} + + +
+ +

Systemeinstellungen

+ +
+

+ 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! +

+

+ 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! +

+

+ 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! +

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

Sperrzeiteneinstellung

+ +
+

+ 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! +

+

+ 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! +

+

+ 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! +

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

Zone

+ +
+

+ 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! +

+

+ 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! +

+

+ 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! +

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

Zonen

+ +
+

+ 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! +

+

+ 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! +

+

+ 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! +

+
+ +
+ + + \ No newline at end of file diff --git a/Webserver/Translater.py b/Webserver/Translater.py new file mode 100644 index 0000000..90212af --- /dev/null +++ b/Webserver/Translater.py @@ -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" diff --git a/Webserver/__init__.py b/Webserver/__init__.py new file mode 100644 index 0000000..417866b --- /dev/null +++ b/Webserver/__init__.py @@ -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/') + # 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/', 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 + 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/') + 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) diff --git a/Webserver/static/Styles/dashboard.css b/Webserver/static/Styles/dashboard.css new file mode 100644 index 0000000..7fd7357 --- /dev/null +++ b/Webserver/static/Styles/dashboard.css @@ -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; + } +} + + + diff --git a/Webserver/static/Styles/header.css b/Webserver/static/Styles/header.css new file mode 100644 index 0000000..690ed9e --- /dev/null +++ b/Webserver/static/Styles/header.css @@ -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%; +} + diff --git a/Webserver/static/Styles/main.css b/Webserver/static/Styles/main.css new file mode 100644 index 0000000..4ed0a3a --- /dev/null +++ b/Webserver/static/Styles/main.css @@ -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; +} \ No newline at end of file diff --git a/Webserver/static/Styles/switch.css b/Webserver/static/Styles/switch.css new file mode 100644 index 0000000..39c5ce7 --- /dev/null +++ b/Webserver/static/Styles/switch.css @@ -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; + } \ No newline at end of file diff --git a/Webserver/static/img/header/Blumenbeet.jpg b/Webserver/static/img/header/Blumenbeet.jpg new file mode 100644 index 0000000..4da8f42 Binary files /dev/null and b/Webserver/static/img/header/Blumenbeet.jpg differ diff --git a/Webserver/static/img/header/wassertropfen.png b/Webserver/static/img/header/wassertropfen.png new file mode 100644 index 0000000..8b0a307 Binary files /dev/null and b/Webserver/static/img/header/wassertropfen.png differ diff --git a/Zone.py b/Zone.py new file mode 100644 index 0000000..b86da92 --- /dev/null +++ b/Zone.py @@ -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() + + diff --git a/ZoneManager.py b/ZoneManager.py new file mode 100644 index 0000000..2ef1d91 --- /dev/null +++ b/ZoneManager.py @@ -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) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..8645ade --- /dev/null +++ b/main.py @@ -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()