This commit is contained in:
Marko Burazin 2022-06-16 22:21:50 +10:00 committed by GitHub
commit 4d5bd9a0cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 168 additions and 14 deletions

View File

@ -4,6 +4,7 @@ import os
import sys
import logging
import json
from datetime import datetime
import bottle
import gevent
@ -67,7 +68,7 @@ def handle_api():
# start at a specific minute in the schedule
# for restarting and skipping over early parts of a schedule
startat = 0;
startat = 0
if 'startat' in bottle.request.json:
startat = bottle.request.json['startat']
@ -79,8 +80,7 @@ def handle_api():
# FIXME juggling of json should happen in the Profile class
profile_json = json.dumps(profile)
profile = Profile(profile_json)
oven.run_profile(profile,startat=startat)
ovenWatcher.record(profile)
run_profile(profile,startat=startat)
if bottle.request.json['cmd'] == 'stop':
log.info("api stop command received")
@ -115,6 +115,11 @@ def find_profile(wanted):
return profile
return None
def run_profile(profile, startat=0):
oven.run_profile(profile, startat)
ovenWatcher.record(profile)
@app.route('/picoreflow/:filename#.*#')
def send_static(filename):
log.debug("serving %s" % filename)
@ -145,8 +150,26 @@ def handle_control():
if profile_obj:
profile_json = json.dumps(profile_obj)
profile = Profile(profile_json)
oven.run_profile(profile)
ovenWatcher.record(profile)
run_profile(profile)
elif msgdict.get("cmd") == "SCHEDULED_RUN":
log.info("SCHEDULED_RUN command received")
scheduled_start_time = msgdict.get('scheduledStartTime')
profile_obj = msgdict.get('profile')
if profile_obj:
profile_json = json.dumps(profile_obj)
profile = Profile(profile_json)
start_datetime = datetime.fromisoformat(
scheduled_start_time,
)
oven.scheduled_run(
start_datetime,
profile,
lambda: ovenWatcher.record(profile),
)
elif msgdict.get("cmd") == "SIMULATE":
log.info("SIMULATE command received")
#profile_obj = msgdict.get('profile')
@ -279,7 +302,7 @@ def get_config():
"time_scale_slope": config.time_scale_slope,
"time_scale_profile": config.time_scale_profile,
"kwh_rate": config.kwh_rate,
"currency_type": config.currency_type})
"currency_type": config.currency_type})
def main():

View File

@ -6,6 +6,8 @@ import logging
import json
import config
from threading import Timer
log = logging.getLogger(__name__)
@ -181,10 +183,16 @@ class Oven(threading.Thread):
self.daemon = True
self.temperature = 0
self.time_step = config.sensor_time_wait
self.scheduled_run_timer = None
self.start_datetime = None
self.reset()
def reset(self):
self.state = "IDLE"
if self.scheduled_run_timer and self.scheduled_run_timer.is_alive():
log.info("Cancelling previously scheduled run")
self.scheduled_run_timer.cancel()
self.start_datetime = None
self.profile = None
self.start_time = 0
self.runtime = 0
@ -218,6 +226,32 @@ class Oven(threading.Thread):
log.info("Running schedule %s starting at %d minutes" % (profile.name,startat))
log.info("Starting")
def scheduled_run(self, start_datetime, profile, run_trigger, startat=0):
self.reset()
seconds_until_start = (
start_datetime - datetime.datetime.now()
).total_seconds()
if seconds_until_start <= 0:
return
self.state = "SCHEDULED"
self.start_datetime = start_datetime
self.scheduled_run_timer = Timer(
seconds_until_start,
self._timeout,
args=[profile, run_trigger, startat],
)
self.scheduled_run_timer.start()
log.info(
"Scheduled to run the kiln at %s",
self.start_datetime,
)
def _timeout(self, profile, run_trigger, startat):
self.run_profile(profile, startat)
if run_trigger:
run_trigger()
def abort_run(self):
self.reset()
@ -276,6 +310,9 @@ class Oven(threading.Thread):
self.reset()
def get_state(self):
scheduled_start = None
if self.start_datetime:
scheduled_start = self.start_datetime.strftime("%Y-%m-%d %H:%M")
state = {
'runtime': self.runtime,
'temperature': self.board.temp_sensor.temperature + config.thermocouple_offset,
@ -287,6 +324,7 @@ class Oven(threading.Thread):
'currency_type': config.currency_type,
'profile': self.profile.name if self.profile else None,
'pidstats': self.pid.pidstats,
'scheduled_start': scheduled_start,
}
return state
@ -307,7 +345,8 @@ class Oven(threading.Thread):
class SimulatedOven(Oven):
def __init__(self):
self.reset()
# call parent init
Oven.__init__(self)
self.board = BoardSimulated()
self.t_env = config.sim_t_env
@ -322,9 +361,6 @@ class SimulatedOven(Oven):
self.t = self.t_env # deg C temp of oven
self.t_h = self.t_env #deg C temp of heating element
# call parent init
Oven.__init__(self)
# start thread
self.start()
log.info("SimulatedOven started")
@ -401,11 +437,11 @@ class RealOven(Oven):
def __init__(self):
self.board = Board()
self.output = Output()
self.reset()
# call parent init
Oven.__init__(self)
self.reset()
# start thread
self.start()

View File

@ -32,6 +32,11 @@ body {
box-shadow: 0 0 1.1em 0 rgba(0,0,0,0.55);
}
#schedule-status {
width: auto;
padding-right: 4px;
}
.display {
display: inline-block;
text-align: right;
@ -187,6 +192,23 @@ body {
background: radial-gradient(ellipse at center, rgba(221,221,221,1) 0%,rgba(221,221,221,0.26) 100%); /* W3C */
}
.ds-led-timer-active {
color: rgb(74, 159, 255);
animation: blinker 1s linear infinite;
background: -moz-radial-gradient(center, ellipse cover, rgba(124,197,239,1) 0%, rgba(48,144,209,0.26) 100%); /* FF3.6+ */
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(124,197,239,1)), color-stop(100%,rgba(48,144,209,0.26))); /* Chrome,Safari4+ */
background: -webkit-radial-gradient(center, ellipse cover, rgba(124,197,239,1) 0%,rgba(48,144,209,0.26) 100%); /* Chrome10+,Safari5.1+ */
background: -o-radial-gradient(center, ellipse cover, rgba(124,197,239,1) 0%,rgba(48,144,209,0.26) 100%); /* Opera 12+ */
background: -ms-radial-gradient(center, ellipse cover, rgba(124,197,239,1) 0%,rgba(48,144,209,0.26) 100%); /* IE10+ */
background: radial-gradient(ellipse at center, rgba(124,197,239,1) 0%,rgba(48,144,209,0.26) 100%); /* W3C */
}
@keyframes blinker {
50% {
opacity: 0;
}
}
.ds-trend {
top: 0;
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.25), -1px -1px 0 rgba(0, 0, 0, 0.4);
@ -364,6 +386,17 @@ body {
top: 10%;
}
.schedule-group {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.schedule-group > input {
margin-right: 5px;
text-align: right;
}
.alert {
background-image: -webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));
background-image: -webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);

View File

@ -222,6 +222,24 @@ function runTask()
}
function scheduleTask()
{
const startTime = document.getElementById('scheduled-run-time').value;
console.log(startTime);
var cmd =
{
"cmd": "SCHEDULED_RUN",
"profile": profiles[selected_profile],
"scheduledStartTime": startTime,
}
graph.live.data = [];
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
ws_control.send(JSON.stringify(cmd));
}
function runTaskSimulation()
{
var cmd =
@ -440,10 +458,37 @@ function getOptions()
}
function formatDateInput(date)
{
var dd = date.getDate();
var mm = date.getMonth() + 1; //January is 0!
var yyyy = date.getFullYear();
var hh = date.getHours();
var mins = date.getMinutes();
if (dd < 10) {
dd = '0' + dd;
}
if (mm < 10) {
mm = '0' + mm;
}
const formattedDate = yyyy + '-' + mm + '-' + dd + 'T' + hh + ':' + mins;
return formattedDate;
}
function initDatetimePicker() {
const now = new Date();
const inThirtyMinutes = new Date();
inThirtyMinutes.setMinutes(inThirtyMinutes.getMinutes() + 10);
$('#scheduled-run-time').attr('value', formatDateInput(inThirtyMinutes));
$('#scheduled-run-time').attr('min', formatDateInput(now));
}
$(document).ready(function()
{
initDatetimePicker();
if(!("WebSocket" in window))
{
@ -538,6 +583,8 @@ $(document).ready(function()
{
$("#nav_start").hide();
$("#nav_stop").show();
$("#timer").removeClass("ds-led-timer-active");
$('#schedule-status').hide()
graph.live.data.push([x.runtime, x.temperature]);
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
@ -550,12 +597,22 @@ $(document).ready(function()
$('#target_temp').html(parseInt(x.target));
}
else if (state === "SCHEDULED") {
$("#nav_start").hide();
$("#nav_stop").show();
$('#timer').addClass("ds-led-timer-active"); // Start blinking timer symbol
$('#state').html('<p class="ds-text">'+state+'</p>');
$('#schedule-status').html('Start at: ' + x.scheduled_start);
$('#schedule-status').show()
}
else
{
$("#nav_start").show();
$("#nav_stop").hide();
$("#timer").removeClass("ds-led-timer-active");
$('#state').html('<p class="ds-text">'+state+'</p>');
$('#schedule-status').hide()
}
$('#act_temp').html(parseInt(x.temperature));

View File

@ -29,6 +29,7 @@
<div class="ds-title-panel">
<div class="ds-title">Sensor Temp</div>
<div class="ds-title">Target Temp</div>
<div id="schedule-status" class="ds-title"></div>
<div class="ds-title ds-state pull-right" style="border-left: 1px solid #ccc;">Status</div>
</div>
<div class="clearfix"></div>
@ -36,7 +37,7 @@
<div class="display ds-num"><span id="act_temp">25</span><span class="ds-unit" id="act_temp_scale" >&deg;C</span></div>
<div class="display ds-num ds-target"><span id="target_temp">---</span><span class="ds-unit" id="target_temp_scale">&deg;C</span></div>
<div class="display ds-num ds-text" id="state"></div>
<div class="display pull-right ds-state" style="padding-right:0"><span class="ds-led" id="heat">&#92;</span><span class="ds-led" id="cool">&#108;</span><span class="ds-led" id="air">&#91;</span><span class="ds-led" id="hazard">&#73;</span><span class="ds-led" id="door">&#9832;</span></div>
<div class="display pull-right ds-state" style="padding-right:0"><span class="ds-led" id="heat">&#92;</span><span class="ds-led" id="cool">&#108;</span><span class="ds-led" id="air">&#91;</span><span class="ds-led" id="hazard">&#73;</span><span class="ds-led" id="timer">&#x29D6;</span></div>
</div>
<div class="clearfix"></div>
<div>
@ -107,7 +108,11 @@
<div class="modal-footer">
<div class="btn-group" style="width: 100%">
<button type="button" class="btn btn-danger" style="width: 50%" data-dismiss="modal">No, take me back</button>
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="runTask()">Yes, start the Run</button>
<button type="button" class="btn btn-success" style="width: 50%" data-dismiss="modal" onclick="runTask()">Yes, start the Run now</button>
</div>
<div class="schedule-group">
<input type="datetime-local" id="scheduled-run-time">
<button type="button" class="btn btn-primary" style="width: 50%" data-dismiss="modal" onclick="scheduleTask()">Schedule start for later</button>
</div>
</div>
</div>