Merge ebc4c89143 into 358679321a
This commit is contained in:
commit
4d5bd9a0cd
@ -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():
|
||||
|
||||
48
lib/oven.py
48
lib/oven.py
@ -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()
|
||||
|
||||
|
||||
@ -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%);
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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" >°C</span></div>
|
||||
<div class="display ds-num ds-target"><span id="target_temp">---</span><span class="ds-unit" id="target_temp_scale">°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">\</span><span class="ds-led" id="cool">l</span><span class="ds-led" id="air">[</span><span class="ds-led" id="hazard">I</span><span class="ds-led" id="door">♨</span></div>
|
||||
<div class="display pull-right ds-state" style="padding-right:0"><span class="ds-led" id="heat">\</span><span class="ds-led" id="cool">l</span><span class="ds-led" id="air">[</span><span class="ds-led" id="hazard">I</span><span class="ds-led" id="timer">⧖</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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user