Compare commits

..

8 Commits

Author SHA1 Message Date
schrom01 0135609919 implemented alert to delete all jobs
#8
2022-08-27 22:06:58 +02:00
schrom01 0ed508f70d styling of scrolling Table 2022-08-27 21:01:50 +02:00
schrom01 ab12bf7b02 implemented Text to show if there are no Jobs.
fixed Bug #11
2022-08-27 20:47:03 +02:00
schrom01 18e78b7783 implemented to cancel all jobs if all jobs are deleted. 2022-08-27 20:02:49 +02:00
schrom01 cb9a3b6830 implemented Time formating
solved #5
2022-08-27 19:41:01 +02:00
schrom01 0264375d3f implemented Buttons to switch all Operation Modes
#7
2022-08-27 19:16:23 +02:00
schrom01 d5a9e4781c Fixed Bug #2 2022-08-27 19:08:50 +02:00
schrom01 279c9e74be implemented Action to switch Mode of all Zones.
Fixed Bug #2
2022-08-27 13:54:23 +02:00
9 changed files with 136 additions and 56 deletions

View File

@ -34,6 +34,20 @@
executeAction('get_zone_info', {{ zone.number }}, '0', refreshZone); executeAction('get_zone_info', {{ zone.number }}, '0', refreshZone);
{% endfor %} {% endfor %}
} }
function buttonDeleteJobById(jobId) {
if(jobId == "all") {
if(confirm('{{ translater.getTranslation("delete all planed and cancel running jobs?")}}')){
document.getElementById("jobListBody").innerHTML = "";
} else {
return
}
} else {
var jobToDelete = document.getElementById("job_" + jobId);
document.getElementById("jobListBody").removeChild(jobToDelete);
}
deleteJobById('delete_job_by_id',jobId);
}
</script> </script>
</head> </head>
@ -46,11 +60,11 @@
<div id="pipeline" class="scrollingtable"> <div id="pipeline" class="scrollingtable">
<div> <div id="jobListTable">
<div> <div>
<table> <table>
<caption>{{ translater.getTranslation("planed irrigationjobs") }}</caption> <caption>{{ translater.getTranslation("planed irrigationjobs") }}</caption>
<thead> <thead id="jobListHead">
<tr> <tr>
<th><div label="Nr."></div></th> <th><div label="Nr."></div></th>
<th><div label="{{ translater.getTranslation("Zone") }}"></div></th> <th><div label="{{ translater.getTranslation("Zone") }}"></div></th>
@ -64,13 +78,16 @@
</tbody> </tbody>
</table> </table>
</div> </div>
Text unter Table <p id="text_no_jobs">{{ translater.getTranslation("currently there are no planned jobs.")}}</p>
<button id="button_delete_all_jobs" onclick="buttonDeleteJobById('all')">{{ translater.getTranslation("delete and cancel all jobs")}}</button>
</div> </div>
</div> </div>
<h3>{{ translater.getTranslation("irrigation zones") }}</h3> <h3>{{ translater.getTranslation("irrigation zones") }}</h3>
<button onclick="switchZoneMode(false, 'all')">{{ translater.getTranslation("switch all to")}} {{ translater.getTranslation("manual mode") }}</button>
<button onclick="switchZoneMode(true, 'all')">{{ translater.getTranslation("switch all to")}} {{ translater.getTranslation("automatic mode") }}</button>
<div id="zones"> <div id="zones">
{% for zone in zoneManager.zones %} {% for zone in zoneManager.zones %}
<div id="zone_{{ zone.number }}" class="zone"> <div id="zone_{{ zone.number }}" class="zone">

View File

@ -2,6 +2,6 @@
<!-- <td>{{ job.id }}</td> --> <!-- <td>{{ job.id }}</td> -->
<td>{{ job.zone.number|string}}</td> <td>{{ job.zone.number|string}}</td>
<td>{{job.zone.name }}</td> <td>{{job.zone.name }}</td>
<td>{{ ((job.duration/60)|int)|string + " " + translater.getTranslation("minutes")}}</td> <td>{{ ((job.duration/60))|string + " " + translater.getTranslation("minutes")}}</td>
<td><button onclick="buttonDeleteJobById('{{ job.id }}')">{{ translater.getTranslation("delete") }}</button></td> <td><button onclick="buttonDeleteJobById('{{ job.id }}')">{{ translater.getTranslation("delete") }}</button></td>
</tr> </tr>

View File

@ -36,7 +36,7 @@
<td></td> <td></td>
<td></td> <td></td>
<td> <td>
{{translater.getTranslation("irragation is planed for") }} <p id="planed_duration_zone{{ zone.number }}" class="planed_duration_value"></p> {{ translater.getTranslation("minutes") + "." }} {{translater.getTranslation("irrigation is planed.") }}<br>{{ translater.getTranslation("planed duration")}}: <p id="planed_duration_zone{{ zone.number }}" class="planed_duration_value"></p> {{ translater.getTranslation("minutes") + "." }}
</td> </td>
<td> <td>
<button onclick="deleteJobsForZone('delete_jobs_for_zone','{{ zone.number }}')">{{ translater.getTranslation("delete") }}</button> <button onclick="deleteJobsForZone('delete_jobs_for_zone','{{ zone.number }}')">{{ translater.getTranslation("delete") }}</button>

View File

@ -1,4 +1,6 @@
from enum import Enum from enum import Enum
from time import strftime
from time import gmtime
class Language(Enum): class Language(Enum):
ENGLISH = 1 ENGLISH = 1
GERMAN = 2 GERMAN = 2
@ -27,11 +29,15 @@ class Translater:
"switch to automatic mode": "Auf Automatikbetrieb umstellen", "switch to automatic mode": "Auf Automatikbetrieb umstellen",
"minutes": "Minuten", "minutes": "Minuten",
"until": "bis", "until": "bis",
"irragation is planed for": "Bewässerung ist geplant für", "irrigation is planed.": "Bewässerung ist geplant.",
"planed irrigationjobs": "geplante Bewässerungsaufträge", "planed irrigationjobs": "geplante Bewässerungsaufträge",
"planed duration": "geplante Dauer", "planed duration": "geplante Dauer",
"cancel": "abbrechen", "cancel": "abbrechen",
"delete": "löschen", "delete": "löschen",
"delete and cancel all jobs": "alle Aufträge löschen und abbrechen",
"switch all to": "stelle alle um auf",
"currently there are no planned jobs.": "momentan sind keine Aufträge geplant.",
"delete all planed and cancel running jobs?": "Sollen alle geplanten Aufträge gelöscht und laufende abgerochen werden?",
} }
@ -43,3 +49,14 @@ class Translater:
return self.dict_german[english_String] return self.dict_german[english_String]
case _: case _:
return "no translations for these language" return "no translations for these language"
def formatTime(self, timeInt):
format = ""
match self.language:
case Language.ENGLISH:
format = "%I.%S %p"
case Language.GERMAN:
format = "%H:%M:%S"
case _:
format = ""
return strftime(format, gmtime(timeInt))

View File

@ -34,7 +34,6 @@ class Webserver:
@app.route('/action', methods=['GET', 'POST']) @app.route('/action', methods=['GET', 'POST'])
def executeAction(command=False, index_str=False, value_str=False): def executeAction(command=False, index_str=False, value_str=False):
sucess = False
if(request.method == 'POST'): if(request.method == 'POST'):
try: try:
command = request.form['command'] command = request.form['command']
@ -69,39 +68,52 @@ class Webserver:
case "switch_zone_on": case "switch_zone_on":
if (index and value): if (index and value):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=True, duration=value) self.zoneManager.switchZoneIndexState(zoneIndex=index, state=True, duration=value)
sucess = True return "True"
elif(index): elif(index):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=True, duration=self.zoneManager.systemSettings.defaultManualIrrigationDuration) self.zoneManager.switchZoneIndexState(zoneIndex=index, state=True, duration=self.zoneManager.systemSettings.defaultManualIrrigationDuration)
sucess = True return "True"
case "switch_zone_off": case "switch_zone_off":
if (index and value): if (index and value):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=False, duration=value, instant=True) self.zoneManager.switchZoneIndexState(zoneIndex=index, state=False, duration=value, instant=True)
sucess = True return "True"
elif(index): elif(index):
self.zoneManager.switchZoneIndexState(zoneIndex=index, state=False, duration=self.zoneManager.systemSettings.defaultManualOffDuration, instant=True) self.zoneManager.switchZoneIndexState(zoneIndex=index, state=False, duration=self.zoneManager.systemSettings.defaultManualOffDuration, instant=True)
sucess = True return "True"
case "switch_zone_mode": case "switch_zone_mode":
if (index and value_str): zonesToSwitch = []
zone = self.zoneManager.getZone(index) if(index_str == "all" and value_str):
match value_str: zonesToSwitch = self.zoneManager.zones
case "automatic": elif (index and value_str):
zone.switchMode(autoMode=True) zonesToSwitch.append(self.zoneManager.getZone(index))
sucess = True else:
case "manual": return "False"
zone.switchMode(autoMode=False) automode = False
sucess = True if(value_str == "automatic"):
automode = True
elif(value_str == "manual"):
automode = False
else:
return "False"
for zone in zonesToSwitch:
zone.switchMode(autoMode=automode)
return "True"
case "set_desired_humidity": case "set_desired_humidity":
if (index and value): if (index and value):
zone = self.zoneManager.getZone(index) zone = self.zoneManager.getZone(index)
zone.desiredHumidity = value zone.desiredHumidity = value
sucess = True return "True"
case "delete_jobs_for_zone": case "delete_jobs_for_zone":
if (index): if (index):
zone = self.zoneManager.getZone(index) zone = self.zoneManager.getZone(index)
self.zoneManager.deleteIrrigationJobsForZone(zone) self.zoneManager.deleteIrrigationJobsForZone(zone)
return "True"
case "delete_job_by_id": case "delete_job_by_id":
if(index_str == "all"):
self.zoneManager.clearIrrigationJobs()
return "True"
if (index): if (index):
self.zoneManager.deleteIrrigationJobByID(index) self.zoneManager.deleteIrrigationJobByID(index)
return "True"
case "get_dashboard_zone_html": case "get_dashboard_zone_html":
if (index): if (index):
zone = self.zoneManager.getZone(index) zone = self.zoneManager.getZone(index)
@ -116,7 +128,7 @@ class Webserver:
return zone.toJSON(self.translater) return zone.toJSON(self.translater)
case "get_pipeline": case "get_pipeline":
return self.zoneManager.pipelineToJSON(self.translater) return self.zoneManager.pipelineToJSON(self.translater)
return render_template('action.html', translater=self.translater, zones=self.zoneManager.zones, sucess=sucess) return "False"
@app.route('/dashboard') @app.route('/dashboard')

View File

@ -4,22 +4,25 @@
vertical-align: middle; vertical-align: middle;
overflow: hidden; overflow: hidden;
width: auto; /*set table width here if using fixed value*/ width: auto; /*set table width here if using fixed value*/
/*min-width: 100%;*/ /*set table width here if using %*/ min-width: 100%; /*set table width here if using %*/
height: 300px; /*set table height here; can be fixed value or %*/ 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*/ 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-family: Verdana, Tahoma, sans-serif;
font-size: 15px; font-size: 15px;
line-height: 20px; line-height: 25px;
padding-top: 20px; /*this determines top caption height*/ padding-top: 25px; /*this determines top caption height*/
padding-bottom: 20px; /*this determines bottom caption height*/ padding-bottom: 25px; /*this determines bottom caption height*/
text-align: left; text-align: left;
} }
#button_delete_all_jobs{
margin-top: 25px;
}
.scrollingtable * {box-sizing: border-box;} .scrollingtable * {box-sizing: border-box;}
.scrollingtable > div { .scrollingtable > div {
position: relative; position: relative;
border-top: 1px solid black; /*top table border*/ border-top: 1px solid black; /*top table border*/
height: 100%; height: 100%;
padding-top: 20px; /*this determines column header height*/ padding-top: 25px; /*this determines column header height*/
} }
.scrollingtable > div:before { .scrollingtable > div:before {
top: 0; top: 0;
@ -36,7 +39,7 @@
} }
.scrollingtable > div > div { .scrollingtable > div > div {
/*min-height: 43px;*/ /*if using % height, make this at least large enough to fit scrollbar arrows*/ /*min-height: 43px;*/ /*if using % height, make this at least large enough to fit scrollbar arrows*/
max-height: 100%; max-height: 200px;
overflow: scroll; /*set to auto if using fixed or % width; else scroll*/ overflow: scroll; /*set to auto if using fixed or % width; else scroll*/
overflow-x: hidden; overflow-x: hidden;
border: 1px solid black; /*border around table body*/ border: 1px solid black; /*border around table body*/
@ -45,22 +48,22 @@
.scrollingtable > div > div > table { .scrollingtable > div > div > table {
width: 100%; width: 100%;
border-spacing: 0; border-spacing: 0;
margin-top: -20px; /*inverse of column header height*/ margin-top: -25px; /*inverse of column header height*/
/*margin-right: 17px;*/ /*uncomment if using % width*/ /*margin-right: 17px;*/ /*uncomment if using % width*/
} }
.scrollingtable > div > div > table > caption { .scrollingtable > div > div > table > caption {
position: absolute; position: absolute;
top: -20px; /*inverse of caption height*/ top: -25px; /*inverse of caption height*/
margin-top: -1px; /*inverse of border-width*/ margin-top: -1px; /*inverse of border-width*/
width: 100%; width: 100%;
font-weight: bold; font-weight: bold;
text-align: center; text-align: left;
} }
.scrollingtable > div > div > table > * > tr > * {padding: 0;} .scrollingtable > div > div > table > * > tr > * {padding: 0;}
.scrollingtable > div > div > table > thead { .scrollingtable > div > div > table > thead {
vertical-align: bottom; vertical-align: bottom;
white-space: nowrap; white-space: nowrap;
text-align: center; text-align: left;
} }
.scrollingtable > div > div > table > thead > tr > * > div { .scrollingtable > div > div > table > thead > tr > * > div {
display: inline-block; display: inline-block;
@ -71,7 +74,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
height: 20px; /*match column header height*/ height: 25px; /*match column header height*/
border-left: 1px solid black; /*leftmost header border*/ border-left: 1px solid black; /*leftmost header border*/
} }
.scrollingtable > div > div > table > thead > tr > * > div[label]:before, .scrollingtable > div > div > table > thead > tr > * > div[label]:before,
@ -87,7 +90,7 @@
.scrollingtable > div > div > table > thead > tr > * + :before { .scrollingtable > div > div > table > thead > tr > * + :before {
content: ""; content: "";
display: block; display: block;
min-height: 20px; /*match column header height*/ min-height: 25px; /*match column header height*/
padding-top: 1px; padding-top: 1px;
border-left: 1px solid black; /*borders between header cells*/ border-left: 1px solid black; /*borders between header cells*/
} }
@ -111,7 +114,7 @@
.scrollingtable > div > div > table > tbody > tr > * { .scrollingtable > div > div > table > tbody > tr > * {
border-bottom: 1px solid black; border-bottom: 1px solid black;
padding: 0 6px 0 6px; padding: 0 6px 0 6px;
height: 20px; /*match column header height*/ height: 25px; /*match column header height*/
} }
.scrollingtable > div > div > table > tbody:last-of-type > tr:last-child > * {border-bottom: none;} .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:nth-child(even) {background: gainsboro;} /*alternate row color*/

View File

@ -4,7 +4,7 @@ function refreshZone(zone_json) {
document.getElementById("inner_icon_state_zone_" + zone.number).className = 'inner_icon ' + (zone.state ? 'dot_green' : 'dot_red'); document.getElementById("inner_icon_state_zone_" + zone.number).className = 'inner_icon ' + (zone.state ? 'dot_green' : 'dot_red');
document.getElementById("state_text_zone_" + zone.number).innerHTML = zone.state_text; document.getElementById("state_text_zone_" + zone.number).innerHTML = zone.state_text;
document.getElementById("end_time_row_zone_" + zone.number).style.display = ((zone.setState == 1 || zone.setState == 2) ? 'table-row' : 'none'); document.getElementById("end_time_row_zone_" + zone.number).style.display = ((zone.setState == 1 || zone.setState == 2) ? 'table-row' : 'none');
document.getElementById("end_time_zone_" + zone.number).innerHTML = zone.endTimeSetState; document.getElementById("end_time_zone_" + zone.number).innerHTML = zone.endTimeSetStateFormated;
document.getElementById("planed_duration_row_zone_" + zone.number).style.display = ((zone.planedDuration > 0) ? 'table-row' : 'none'); document.getElementById("planed_duration_row_zone_" + zone.number).style.display = ((zone.planedDuration > 0) ? 'table-row' : 'none');
document.getElementById("planed_duration_zone" + zone.number).innerHTML = zone.planedDuration/60; document.getElementById("planed_duration_zone" + zone.number).innerHTML = zone.planedDuration/60;
document.getElementById("inner_icon_mode_zone_" + zone.number).innerHTML = zone.autoMode ? 'A' : 'M'; document.getElementById("inner_icon_mode_zone_" + zone.number).innerHTML = zone.autoMode ? 'A' : 'M';
@ -15,13 +15,18 @@ function refreshZone(zone_json) {
document.getElementById("desired_humidity_zone_" + zone.number).innerHTML = zone.desiredHumidity; document.getElementById("desired_humidity_zone_" + zone.number).innerHTML = zone.desiredHumidity;
} }
function buttonDeleteJobById(jobId) {
deleteJobById('delete_job_by_id',jobId);
var jobToDelete = document.getElementById("job_" + jobId);
document.getElementById("jobListBody").removeChild(jobToDelete);
}
function refreshPipeline(pipeline_html) { function refreshPipeline(pipeline_html) {
document.getElementById("jobListBody").innerHTML = pipeline_html; //alert("refreshing Pipeline");
var jobListBody = document.getElementById("jobListBody");
jobListBody.innerHTML = pipeline_html;
if(!jobListBody.childElementCount){
document.getElementById("button_delete_all_jobs").style.display = 'none';
document.getElementById("text_no_jobs").style.display = 'block';
} else {
document.getElementById("button_delete_all_jobs").style.display = 'block';
document.getElementById("text_no_jobs").style.display = 'none';
}
} }

17
Zone.py
View File

@ -26,6 +26,7 @@ class Zone:
"state_text": translater.getTranslation("switched on" if self.state else "switched off"), "state_text": translater.getTranslation("switched on" if self.state else "switched off"),
"setState": self.setState, "setState": self.setState,
"endTimeSetState" : self.endTimeSetState, "endTimeSetState" : self.endTimeSetState,
"endTimeSetStateFormated" : translater.formatTime(self.endTimeSetState),
"planedDuration": self.planedDuration, "planedDuration": self.planedDuration,
} }
@ -45,12 +46,18 @@ class Zone:
self.zoneManager.switchZoneState(zone=self, state=True, duration=self.zoneManager.systemSettings.defaultAutoIrrigationDuration, instant=False) self.zoneManager.switchZoneState(zone=self, state=True, duration=self.zoneManager.systemSettings.defaultAutoIrrigationDuration, instant=False)
case 1: case 1:
if(self.timeOver()): if(self.timeOver()):
if (self.planedDuration > 0):
self.setState = 3
else:
self.setState = 0 self.setState = 0
self.refreshStateAutomode() self.refreshStateAutomode()
else: else:
self.state = False self.state = False
case 2: case 2:
if (self.timeOver()): if (self.timeOver()):
if (self.planedDuration > 0):
self.setState = 3
else:
self.setState = 0 self.setState = 0
self.refreshStateAutomode() self.refreshStateAutomode()
else: else:
@ -70,10 +77,16 @@ class Zone:
self.setState = 3 self.setState = 3
case 1: case 1:
if(self.timeOver()): if(self.timeOver()):
if (self.planedDuration > 0):
self.setState = 3
else:
self.setState = 0 self.setState = 0
self.state = False self.state = False
case 2: case 2:
if (self.timeOver()): if (self.timeOver()):
if (self.planedDuration > 0):
self.setState = 3
else:
self.setState = 0 self.setState = 0
self.state = False self.state = False
else: else:
@ -105,7 +118,9 @@ class Zone:
self.setState = 2 self.setState = 2
self.endTimeSetState = time.time() + duration self.endTimeSetState = time.time() + duration
else: else:
if(state): if(self.setState == 1 or self.setState == 2):
pass
elif(state):
self.setState = 3 self.setState = 3
self.refreshState() self.refreshState()

View File

@ -60,6 +60,12 @@ class ZoneManager:
else: else:
i = i + 1 i = i + 1
def clearIrrigationJobs(self):
with self.piplineMutexLock:
self.pipeLine = []
for zone in self.zones:
self.switchZoneState(zone=zone, state=False, duration=-1, instant=True)
def isAnyZoneBusy(self): def isAnyZoneBusy(self):
for zone in self.zones: for zone in self.zones:
if(zone.state): if(zone.state):
@ -76,7 +82,7 @@ class ZoneManager:
def switchZoneIndexState(self, zoneIndex, state, duration, instant=False): def switchZoneIndexState(self, zoneIndex, state, duration, instant=False):
zone = self.getZone(zoneIndex) zone = self.getZone(zoneIndex)
self.switchZoneState(zone, state, duration, instant) self.switchZoneState(zone=zone, state=state, duration=duration, instant=instant)
def getPlanedDurationForZone(self, zone): def getPlanedDurationForZone(self, zone):
totalDuration = 0 totalDuration = 0
@ -92,12 +98,17 @@ class ZoneManager:
def processPipeline(self): def processPipeline(self):
self.piplineMutexLock.acquire() self.piplineMutexLock.acquire()
if(len(self.pipeLine) > 0 and (not self.isAnyZoneBusy())): index = 0
irrigationJob = self.pipeLine[0] while(len(self.pipeLine) > index and (not self.isAnyZoneBusy())):
self.pipeLine.pop(0) irrigationJob = self.pipeLine[index]
if(not irrigationJob.zone.setState == 1):
self.pipeLine.pop(index)
self.piplineMutexLock.release() self.piplineMutexLock.release()
irrigationJob.process() irrigationJob.process()
return
else: else:
index = index + 1
self.piplineMutexLock.release() self.piplineMutexLock.release()