adding state page
This commit is contained in:
parent
168204bdb3
commit
f2cf1675cb
@ -44,6 +44,10 @@ oven.set_ovenwatcher(ovenWatcher)
|
||||
def index():
|
||||
return bottle.redirect('/picoreflow/index.html')
|
||||
|
||||
@app.route('/state')
|
||||
def state():
|
||||
return bottle.redirect('/picoreflow/state.html')
|
||||
|
||||
@app.get('/api/stats')
|
||||
def handle_api():
|
||||
log.info("/api/stats command received")
|
||||
|
||||
@ -342,6 +342,7 @@ class Oven(threading.Thread):
|
||||
self.heat_rate = 0
|
||||
self.heat_rate_temps = []
|
||||
self.pid = PID(ki=config.pid_ki, kd=config.pid_kd, kp=config.pid_kp)
|
||||
self.catching_up = False
|
||||
|
||||
@staticmethod
|
||||
def get_start_from_temperature(profile, temp):
|
||||
@ -407,10 +408,15 @@ class Oven(threading.Thread):
|
||||
if self.target - temp > config.pid_control_window:
|
||||
log.info("kiln must catch up, too cold, shifting schedule")
|
||||
self.start_time = self.get_start_time()
|
||||
self.catching_up = True;
|
||||
return
|
||||
# kiln too hot, wait for it to cool down
|
||||
if temp - self.target > config.pid_control_window:
|
||||
log.info("kiln must catch up, too hot, shifting schedule")
|
||||
self.start_time = self.get_start_time()
|
||||
self.catching_up = True;
|
||||
return
|
||||
self.catching_up = False;
|
||||
|
||||
def update_runtime(self):
|
||||
|
||||
@ -473,6 +479,7 @@ class Oven(threading.Thread):
|
||||
'currency_type': config.currency_type,
|
||||
'profile': self.profile.name if self.profile else None,
|
||||
'pidstats': self.pid.pidstats,
|
||||
'catching_up': self.catching_up,
|
||||
}
|
||||
return state
|
||||
|
||||
|
||||
47
public/assets/css/state.css
Normal file
47
public/assets/css/state.css
Normal file
@ -0,0 +1,47 @@
|
||||
.container {
|
||||
border-radius: 5px;
|
||||
background: #888888;
|
||||
//background: #CCCCCC;
|
||||
padding: 2px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: max-content;
|
||||
font-family: sans-serif;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.stat {
|
||||
padding: 2px;
|
||||
border-radius: 5px;
|
||||
font-size: 40pt;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.stattxt {
|
||||
padding: 7px;
|
||||
border-radius: 5px;
|
||||
font-size: 40pt;
|
||||
background: #BBBBBB;
|
||||
//color: #FFFFFF;
|
||||
color: #888888;
|
||||
margin: 1px 2px 1px 2px;
|
||||
}
|
||||
|
||||
.top {
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
background: #0000CC;
|
||||
padding: 4px;
|
||||
color: #CCCCCC;
|
||||
text-align: center;
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
border-radius: 0px 0px 5px 5px;
|
||||
background: #BBBBBB;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
color: #0000CC;
|
||||
font-size: 20pt;
|
||||
}
|
||||
302
public/assets/js/state.js
Normal file
302
public/assets/js/state.js
Normal file
@ -0,0 +1,302 @@
|
||||
config = "";
|
||||
all = [];
|
||||
var table = "";
|
||||
|
||||
var protocol = 'ws:';
|
||||
if (window.location.protocol == 'https:') {
|
||||
protocol = 'wss:';
|
||||
}
|
||||
var host = "" + protocol + "//" + window.location.hostname + ":" + window.location.port;
|
||||
var ws_status = new WebSocket(host+"/status");
|
||||
var ws_config = new WebSocket(host+"/config");
|
||||
|
||||
ws_status.onmessage = function(e) {
|
||||
x = JSON.parse(e.data);
|
||||
if (x.pidstats) {
|
||||
x.pidstats["datetime"]=unix_to_yymmdd_hhmmss(x.pidstats.time);
|
||||
x.pidstats.err = x.pidstats.err*-1;
|
||||
x.pidstats.out = x.pidstats.out*100;
|
||||
x.pidstats.catching_up = x.catching_up
|
||||
if (x.catching_up == true) {
|
||||
x.pidstats.catchingup = x.pidstats.ispoint;
|
||||
}
|
||||
all.push(x.pidstats);
|
||||
}
|
||||
var str = JSON.stringify(x, null, 2);
|
||||
document.getElementById("state").innerHTML = "<pre>"+str+"</pre>"
|
||||
table.replaceData(latest(20));
|
||||
drawall(all);
|
||||
|
||||
document.getElementById("error-current").innerHTML = rnd(x.pidstats.err);
|
||||
document.getElementById("error-1min").innerHTML = rnd(average("err",1,all));
|
||||
document.getElementById("error-5min").innerHTML = rnd(average("err",5,all));
|
||||
document.getElementById("error-15min").innerHTML = rnd(average("err",15,all));
|
||||
|
||||
document.getElementById("temp").innerHTML = rnd(x.pidstats.ispoint);
|
||||
document.getElementById("target").innerHTML = rnd(x.pidstats.setpoint);
|
||||
|
||||
document.getElementById("heat-pct").innerHTML = rnd(x.pidstats.out);
|
||||
|
||||
document.getElementById("catching-up").innerHTML = rnd(percent_catching_up(all));
|
||||
};
|
||||
|
||||
ws_config.onopen = function() {
|
||||
ws_config.send('GET');
|
||||
};
|
||||
|
||||
ws_config.onmessage = function(e) {
|
||||
config = JSON.parse(e.data);
|
||||
//console.log(e);
|
||||
};
|
||||
|
||||
create_table(all);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function rnd(number) {
|
||||
return Number(number).toFixed(2);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
function average(field,minutes,data) {
|
||||
if(data[0]!=null) {
|
||||
var t = data[data.length - 1].time;
|
||||
var oldest = t-(60*minutes);
|
||||
var q = "SELECT AVG("+ field + ") from ? where time>=" + oldest.toString();
|
||||
var avg = alasql(q,[data]);
|
||||
return avg[0]["AVG(err)"];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function drawall(data) {
|
||||
draw_temps(data);
|
||||
draw_error(data);
|
||||
draw_heat(data);
|
||||
draw_p(data);
|
||||
draw_i(data);
|
||||
draw_d(data);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function draw_heat(data) {
|
||||
var traces=[];
|
||||
var rows = alasql('SELECT datetime, out from ?',[data]);
|
||||
var title = 'Heating Percent';
|
||||
|
||||
var trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'out'),
|
||||
name: 'heat',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(255,0,0)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
spot = document.getElementById('heat');
|
||||
var layout = {
|
||||
title: title,
|
||||
showlegend: true,
|
||||
};
|
||||
Plotly.newPlot(spot, traces, layout, {displayModeBar: false});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function draw_p(data) {
|
||||
var traces=[];
|
||||
var rows = alasql('SELECT datetime, p from ?',[data]);
|
||||
var title = 'Proportional';
|
||||
|
||||
var trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'p'),
|
||||
name: 'p',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(0,0,255)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
spot = document.getElementById('p');
|
||||
var layout = {
|
||||
title: title,
|
||||
showlegend: true,
|
||||
};
|
||||
Plotly.newPlot(spot, traces, layout, {displayModeBar: false});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function draw_i(data) {
|
||||
var traces=[];
|
||||
var rows = alasql('SELECT datetime, i from ?',[data]);
|
||||
var title = 'Integral';
|
||||
|
||||
var trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'i'),
|
||||
name: 'i',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(0,0,255)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
spot = document.getElementById('i');
|
||||
var layout = {
|
||||
title: title,
|
||||
showlegend: true,
|
||||
};
|
||||
Plotly.newPlot(spot, traces, layout, {displayModeBar: false});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function draw_d(data) {
|
||||
var traces=[];
|
||||
var rows = alasql('SELECT datetime, d from ?',[data]);
|
||||
var title = 'Derivative';
|
||||
|
||||
var trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'd'),
|
||||
name: 'd',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(0,0,255)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
spot = document.getElementById('d');
|
||||
var layout = {
|
||||
title: title,
|
||||
showlegend: true,
|
||||
};
|
||||
Plotly.newPlot(spot, traces, layout, {displayModeBar: false});
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function draw_error(data) {
|
||||
var traces=[];
|
||||
var rows = alasql('SELECT datetime, err from ?',[data]);
|
||||
var title = 'Error';
|
||||
|
||||
var trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'err'),
|
||||
name: 'error',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(255,0,0)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
spot = document.getElementById('error');
|
||||
var layout = {
|
||||
title: title,
|
||||
showlegend: true,
|
||||
//xaxis : { tickformat:'%b' },
|
||||
};
|
||||
Plotly.newPlot(spot, traces, layout, {displayModeBar: false});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function draw_temps(data) {
|
||||
var traces=[];
|
||||
var rows = alasql('SELECT datetime, ispoint, setpoint, catchingup from ?',[data]);
|
||||
var title = 'Temperature and Target';
|
||||
|
||||
var trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'setpoint'),
|
||||
name: 'target',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(0,0,255)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'ispoint'),
|
||||
name: 'temp',
|
||||
mode: 'lines',
|
||||
line: { color: 'rgb(255,0,0)', width:2 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
trace = {
|
||||
x: unpack(rows, 'datetime'),
|
||||
y: unpack(rows, 'catchingup'),
|
||||
name: 'catchup',
|
||||
mode: 'markers',
|
||||
marker: { color: 'rgb(0,255,0)', width:3 }
|
||||
};
|
||||
|
||||
traces.push(trace);
|
||||
|
||||
spot = document.getElementById('temps');
|
||||
var layout = {
|
||||
title: title,
|
||||
showlegend: true,
|
||||
//xaxis : { tickformat:'%b' },
|
||||
};
|
||||
Plotly.newPlot(spot, traces, layout, {displayModeBar: false});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function unpack(rows, key) {
|
||||
return rows.map(function(row) { return row[key]; });
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function unix_to_yymmdd_hhmmss(t) {
|
||||
var date = new Date(t * 1000);
|
||||
var newd = new Date(date.getTime() - date.getTimezoneOffset()*60000);
|
||||
//return date.toLocaleString('en-US',{hour12:false}).replace(',','');
|
||||
return newd.toISOString().replace("T"," ").substring(0, 19);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
function latest(n) {
|
||||
//sql = "select * from ? order by time desc limit " + n;
|
||||
sql = "select * from ? order by time desc";
|
||||
results = alasql(sql,[all]);
|
||||
return results;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function percent_catching_up(data) {
|
||||
var sql = "select sum(timeDelta) as slip from ? where catching_up=true";
|
||||
var a = alasql(sql,[data]);
|
||||
a = a[0]["slip"];
|
||||
sql = "select sum(timeDelta) as [all] from ?";
|
||||
var b = alasql(sql,[data]);
|
||||
b = b[0]["all"];
|
||||
return a/b*100;
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
function create_table(data) {
|
||||
table = new Tabulator("#state-table", {
|
||||
height:300,
|
||||
data:data, //assign data to table
|
||||
//layout:"fitColumns", //fit columns to width of table (optional)
|
||||
columns:[
|
||||
{title:"DateTime", field:"datetime"},
|
||||
{title:"Target", field:"setpoint"},
|
||||
{title:"Temp", field:"ispoint"},
|
||||
{title:"Error", field:"err"},
|
||||
{title:"P", field:"p"},
|
||||
{title:"I", field:"i"},
|
||||
{title:"D", field:"d"},
|
||||
{title:"Heat", field:"out"},
|
||||
{title:"Catching Up", field:"catching_up"},
|
||||
{title:"Time Delta", field:"timeDelta"},
|
||||
]});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
function csv_string() {
|
||||
table.download("csv", "kiln-state.csv");
|
||||
}
|
||||
|
||||
74
public/state.html
Normal file
74
public/state.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Kiln Controller</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="assets/css/state.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container" id="temp-stats">
|
||||
<div class="stattxt">TEMP</div>
|
||||
<div class="stat">
|
||||
<div class="top">temp</div>
|
||||
<div class="bottom" id="temp"></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="top">target</div>
|
||||
<div class="bottom" id="target"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container" id="error-stats">
|
||||
<div class="stattxt">ERROR</div>
|
||||
<div class="stat">
|
||||
<div class="top">now</div>
|
||||
<div class="bottom" id="error-current"></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="top">1 min</div>
|
||||
<div class="bottom" id="error-1min"></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="top">5 min</div>
|
||||
<div class="bottom" id="error-5min"></div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="top">15 min</div>
|
||||
<div class="bottom" id="error-15min"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container" id="heat-stats">
|
||||
<div class="stattxt">HEAT</div>
|
||||
<div class="stat">
|
||||
<div class="top">%</div>
|
||||
<div class="bottom" id="heat-pct"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container" id="catchingup">
|
||||
<div class="stattxt">CATCH UP</div>
|
||||
<div class="stat">
|
||||
<div class="top">%</div>
|
||||
<div class="bottom" id="catching-up"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="temps"></div>
|
||||
<div id="error"></div>
|
||||
<div id="heat"></div>
|
||||
<div id="p"></div>
|
||||
<div id="i"></div>
|
||||
<div id="d"></div>
|
||||
<div id="state-table"></div>
|
||||
<div id="state-table-controls"><a href="javascript: void(0)" onclick="csv_string();" >csv</a></div>
|
||||
<div id="state"></div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/alasql/4.3.2/alasql.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/2.32.0/plotly.min.js" charset="utf-8"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tabulator/5.6.1/css/tabulator.min.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tabulator/5.6.1/js/tabulator.min.js"></script>
|
||||
<script src="assets/js/state.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user