Merge pull request 'Webserver_Dashboard and basic functionality' (#1) from Webserver_Layout into master
Reviewed-on: #1
This commit is contained in:
commit
0363682d7c
|
@ -1,4 +1,4 @@
|
|||
# ---> Python
|
||||
# ---> Webserver
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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%;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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 |
|
@ -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()
|
||||
|
||||
|
|
@ -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)
|
|
@ -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()
|
Loading…
Reference in New Issue