kopia lustrzana https://github.com/botheredbybees/kilnController
588 wiersze
17 KiB
JavaScript
588 wiersze
17 KiB
JavaScript
var state = "IDLE";
|
|
var state_last = "";
|
|
var graph = [ 'profile', 'live'];
|
|
var points = [];
|
|
var profiles = [];
|
|
var selected_profile = 0;
|
|
var time_mode = 0;
|
|
var selected_profile_name = "leadfree";
|
|
|
|
var host = "ws://" + window.location.hostname + ":8080";
|
|
var ws_status = new WebSocket(host+"/status");
|
|
var ws_control = new WebSocket(host+"/control");
|
|
var ws_storage = new WebSocket(host+"/storage");
|
|
|
|
if(window.webkitRequestAnimationFrame) window.requestAnimationFrame = window.webkitRequestAnimationFrame;
|
|
|
|
graph.profile =
|
|
{
|
|
label: "Profile",
|
|
data: [],
|
|
points: { show: false },
|
|
color: "#75890c",
|
|
draggable: false
|
|
};
|
|
|
|
graph.live =
|
|
{
|
|
label: "Live",
|
|
data: [],
|
|
points: { show: false },
|
|
color: "#d8d3c5",
|
|
draggable: false
|
|
};
|
|
|
|
|
|
function updateProfile(id)
|
|
{
|
|
selected_profile = id;
|
|
job_time = parseInt(profiles[id].data[profiles[id].data.length-1][0]);
|
|
var kwh = (3850*job_time/3600/1000).toFixed(2);
|
|
var cost = (kwh*0.26).toFixed(2);
|
|
var minutes = Math.floor(job_time/60), seconds = job_time-minutes*60;
|
|
job_time = minutes+':'+ (seconds < 10 ? "0" : "") + seconds;
|
|
$('#sel_prof').html(profiles[id].name);
|
|
$('#sel_prof_eta').html(job_time);
|
|
$('#sel_prof_cost').html(kwh + ' kWh (EUR: '+ cost +')');
|
|
graph.profile.data = profiles[id].data;
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
|
}
|
|
|
|
function deleteProfile()
|
|
{
|
|
console.log("Delete profile:" + selected_profile_name);
|
|
// FIXME: Add cmd for socket communication to delete stored profile
|
|
leaveEditMode();
|
|
}
|
|
|
|
|
|
function updateProgress(percentage)
|
|
{
|
|
if(state=="RUNNING")
|
|
{
|
|
if(percentage > 100) percentage = 100;
|
|
$('#progressBar').css('width', percentage+'%');
|
|
if(percentage>5) $('#progressBar').html(parseInt(percentage)+'%');
|
|
}
|
|
else
|
|
{
|
|
$('#progressBar').css('width', 0+'%');
|
|
$('#progressBar').html('');
|
|
}
|
|
}
|
|
|
|
function updateProfileTable()
|
|
{
|
|
var dps = 0;
|
|
var slope = "";
|
|
var color = "";
|
|
|
|
var html = '<h3>Profile Points</h3><div class="table-responsive" style="scroll: none"><table class="table table-striped">';
|
|
html += '<tr><th style="width: 50px">#</th><th>Target Time</th><th>Target Temperature in °C</th><th>Slope in °C/s</th><th></th></tr>';
|
|
|
|
for(var i=0; i<graph.profile.data.length;i++)
|
|
{
|
|
if (i>=1) dps = Math.round( (graph.profile.data[i][1]-graph.profile.data[i-1][1])/(graph.profile.data[i][0]-graph.profile.data[i-1][0]) * 10) / 10;
|
|
if (dps > 0) { slope = "up"; color="rgba(206, 5, 5, 1)"; } else
|
|
if (dps < 0) { slope = "down"; color="rgba(23, 108, 204, 1)"; dps *= -1; } else
|
|
if (dps == 0) { slope = "right"; color="grey"; }
|
|
|
|
html += '<tr><td><h4>' + (i+1) + '</h4></td>';
|
|
html += '<td><input type="text" class="form-control" id="profiletable-0-'+i+'" value="'+ graph.profile.data[i][0] + '" style="width: 60px" /></td>';
|
|
html += '<td><input type="text" class="form-control" id="profiletable-1-'+i+'" value="'+ graph.profile.data[i][1] + '" style="width: 60px" /></td>';
|
|
html += '<td><div class="input-group"><span class="glyphicon glyphicon-circle-arrow-' + slope +
|
|
' input-group-addon ds-trend" style="background: '+color+'"></span><input type="text" class="form-control ds-input" readonly value="' + dps + '" style="width: 50px" /></div></td>';
|
|
html += '<td> </td></tr>';
|
|
}
|
|
|
|
html += '</table></div>';
|
|
|
|
$('#profile_table').html(html);
|
|
|
|
//Link table to graph
|
|
$(".form-control").change(function(e)
|
|
{
|
|
var id = $(this)[0].id; //e.currentTarget.attributes.id
|
|
var value = parseInt($(this)[0].value);
|
|
var fields = id.split("-");
|
|
var col = parseInt(fields[1]);
|
|
var row = parseInt(fields[2]);
|
|
|
|
graph.profile.data[row][col] = value;
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
|
updateProfileTable();
|
|
});
|
|
}
|
|
|
|
function timeTickFormatter(val)
|
|
{
|
|
if (val < 1800)
|
|
{
|
|
return val;
|
|
}
|
|
else
|
|
{
|
|
var hours = Math.floor(val / (3600));
|
|
var div_min = val % (3600);
|
|
var minutes = Math.floor(div_min / 60);
|
|
|
|
if (hours < 10) {hours = "0"+hours;}
|
|
if (minutes < 10) {minutes = "0"+minutes;}
|
|
|
|
return hours+":"+minutes;
|
|
}
|
|
}
|
|
|
|
function runTask()
|
|
{
|
|
var cmd =
|
|
{
|
|
"cmd": "RUN",
|
|
"profile": profiles[selected_profile]
|
|
}
|
|
|
|
graph.live.data = [];
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
|
|
|
ws_control.send(JSON.stringify(cmd));
|
|
|
|
}
|
|
|
|
function runTaskSimulation()
|
|
{
|
|
var cmd =
|
|
{
|
|
"cmd": "SIMULATE",
|
|
"profile": profiles[selected_profile]
|
|
}
|
|
|
|
graph.live.data = [];
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
|
|
|
ws_control.send(JSON.stringify(cmd));
|
|
|
|
}
|
|
|
|
|
|
function abortTask()
|
|
{
|
|
var cmd = {"cmd": "STOP"};
|
|
ws_control.send(JSON.stringify(cmd));
|
|
}
|
|
|
|
function enterNewMode()
|
|
{
|
|
state="EDIT"
|
|
$('#status').slideUp();
|
|
$('#edit').show();
|
|
$('#profile_selector').hide();
|
|
$('#btn_controls').hide();
|
|
$('#form_profile_name').attr('value', '');
|
|
$('#form_profile_name').attr('placeholder', 'Please enter a name');
|
|
graph.profile.points.show = true;
|
|
graph.profile.draggable = true;
|
|
graph.profile.data = [];
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
|
updateProfileTable();
|
|
}
|
|
|
|
function enterEditMode()
|
|
{
|
|
state="EDIT"
|
|
$('#status').slideUp();
|
|
$('#edit').show();
|
|
$('#profile_selector').hide();
|
|
$('#btn_controls').hide();
|
|
$('#form_profile_name').attr('value', profiles[selected_profile].name);
|
|
graph.profile.points.show = true;
|
|
graph.profile.draggable = true;
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
|
updateProfileTable();
|
|
}
|
|
|
|
function leaveEditMode()
|
|
{
|
|
selected_profile_name = $('#form_profile_name').val();
|
|
ws_storage.send('GET');
|
|
state="IDLE";
|
|
$('#edit').hide();
|
|
$('#profile_selector').show();
|
|
$('#btn_controls').show();
|
|
$('#status').slideDown();
|
|
$('#profile_table').slideUp();
|
|
graph.profile.points.show = false;
|
|
graph.profile.draggable = false;
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
|
}
|
|
|
|
function newPoint()
|
|
{
|
|
if(graph.profile.data.length > 0)
|
|
{
|
|
var pointx = parseInt(graph.profile.data[graph.profile.data.length-1][0])+15;
|
|
}
|
|
else
|
|
{
|
|
var pointx = 0;
|
|
}
|
|
graph.profile.data.push([pointx, Math.floor((Math.random()*230)+25)]);
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
|
updateProfileTable();
|
|
}
|
|
|
|
function delPoint()
|
|
{
|
|
graph.profile.data.splice(-1,1)
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ], getOptions());
|
|
updateProfileTable();
|
|
}
|
|
|
|
function toggleTable()
|
|
{
|
|
if($('#profile_table').css('display') == 'none')
|
|
{
|
|
$('#profile_table').slideDown();
|
|
}
|
|
else
|
|
{
|
|
$('#profile_table').slideUp();
|
|
}
|
|
}
|
|
|
|
function saveProfile()
|
|
{
|
|
name = $('#form_profile_name').val();
|
|
var rawdata = graph.plot.getData()[0].data
|
|
var data = [];
|
|
var last = -1;
|
|
|
|
for(var i=0; i<rawdata.length;i++)
|
|
{
|
|
if(rawdata[i][0] > last)
|
|
{
|
|
data.push([rawdata[i][0], rawdata[i][1]]);
|
|
}
|
|
else
|
|
{
|
|
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>ERROR 88:</b><br/>An oven is not a time-machine", {
|
|
ele: 'body', // which element to append to
|
|
type: 'alert', // (null, 'info', 'error', 'success')
|
|
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
|
align: 'center', // ('left', 'right', or 'center')
|
|
width: 385, // (integer, or 'auto')
|
|
delay: 5000,
|
|
allow_dismiss: true,
|
|
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
last = rawdata[i][0];
|
|
}
|
|
|
|
var profile = { "type": "profile", "data": data, "name": name }
|
|
var put = { "cmd": "PUT", "profile": profile }
|
|
|
|
var put_cmd = JSON.stringify(put);
|
|
|
|
ws_storage.send(put_cmd);
|
|
|
|
leaveEditMode();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getOptions()
|
|
{
|
|
|
|
var options =
|
|
{
|
|
|
|
series:
|
|
{
|
|
lines:
|
|
{
|
|
show: true
|
|
},
|
|
|
|
points:
|
|
{
|
|
show: true,
|
|
radius: 5,
|
|
symbol: "circle"
|
|
},
|
|
|
|
shadowSize: 3
|
|
|
|
},
|
|
|
|
xaxis:
|
|
{
|
|
min: 0,
|
|
tickColor: 'rgba(216, 211, 197, 0.2)',
|
|
tickFormatter: timeTickFormatter,
|
|
font:
|
|
{
|
|
size: 14,
|
|
lineHeight: 14, weight: "normal",
|
|
family: "Digi",
|
|
variant: "small-caps",
|
|
color: "rgba(216, 211, 197, 0.85)"
|
|
}
|
|
},
|
|
|
|
yaxis:
|
|
{
|
|
tickSize: 25,
|
|
min: 0,
|
|
max: 250,
|
|
tickDecimals: 0,
|
|
draggable: false,
|
|
tickColor: 'rgba(216, 211, 197, 0.2)',
|
|
font:
|
|
{
|
|
size: 14,
|
|
lineHeight: 14,
|
|
weight: "normal",
|
|
family: "Digi",
|
|
variant: "small-caps",
|
|
color: "rgba(216, 211, 197, 0.85)"
|
|
}
|
|
},
|
|
|
|
grid:
|
|
{
|
|
color: 'rgba(216, 211, 197, 0.55)',
|
|
borderWidth: 1,
|
|
labelMargin: 10,
|
|
mouseActiveRadius: 50
|
|
},
|
|
|
|
legend:
|
|
{
|
|
show: false
|
|
}
|
|
}
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
$(document).ready(function()
|
|
{
|
|
|
|
if(!("WebSocket" in window))
|
|
{
|
|
$('#chatLog, input, button, #examples').fadeOut("fast");
|
|
$('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');
|
|
}
|
|
else
|
|
{
|
|
|
|
// Status Socket ////////////////////////////////
|
|
|
|
ws_status.onopen = function()
|
|
{
|
|
console.log("Status Socket has been opened");
|
|
|
|
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>Yay</b><br/>I'm alive",
|
|
{
|
|
ele: 'body', // which element to append to
|
|
type: 'success', // (null, 'info', 'error', 'success')
|
|
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
|
align: 'center', // ('left', 'right', or 'center')
|
|
width: 385, // (integer, or 'auto')
|
|
delay: 2500,
|
|
allow_dismiss: true,
|
|
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
|
});
|
|
};
|
|
|
|
ws_status.onclose = function()
|
|
{
|
|
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>ERROR 1:</b><br/>Status Websocket not available", {
|
|
ele: 'body', // which element to append to
|
|
type: 'error', // (null, 'info', 'error', 'success')
|
|
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
|
align: 'center', // ('left', 'right', or 'center')
|
|
width: 385, // (integer, or 'auto')
|
|
delay: 5000,
|
|
allow_dismiss: true,
|
|
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
|
});
|
|
};
|
|
|
|
ws_status.onmessage = function(e)
|
|
{
|
|
x = JSON.parse(e.data);
|
|
|
|
if (x.type == "backlog")
|
|
{
|
|
if (x.profile)
|
|
{
|
|
selected_profile_name = x.profile.name;
|
|
}
|
|
|
|
$.each(x.log, function(i,v) {
|
|
graph.live.data.push([v.runtime, v.temperature]);
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
|
});
|
|
}
|
|
|
|
if(state!="EDIT")
|
|
{
|
|
state = x.state;
|
|
|
|
if (x.door == "OPEN")
|
|
{
|
|
|
|
}
|
|
|
|
if (state!=state_last)
|
|
{
|
|
if(state_last == "RUNNING")
|
|
{
|
|
$('#target_temp').html('---');
|
|
updateProgress(0);
|
|
$.bootstrapGrowl("<span class=\"glyphicon glyphicon-exclamation-sign\"></span> <b>Run completed</b>", {
|
|
ele: 'body', // which element to append to
|
|
type: 'success', // (null, 'info', 'error', 'success')
|
|
offset: {from: 'top', amount: 250}, // 'top', or 'bottom'
|
|
align: 'center', // ('left', 'right', or 'center')
|
|
width: 385, // (integer, or 'auto')
|
|
delay: 0,
|
|
allow_dismiss: true,
|
|
stackup_spacing: 10 // spacing between consecutively stacked growls.
|
|
});
|
|
}
|
|
}
|
|
|
|
if(state=="RUNNING")
|
|
{
|
|
$("#nav_start").hide();
|
|
$("#nav_stop").show();
|
|
|
|
graph.live.data.push([x.runtime, x.temperature]);
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
|
|
|
left = parseInt(x.totaltime-x.runtime);
|
|
var minutes = Math.floor(left / 60);
|
|
var seconds = left - minutes * 60;
|
|
eta = minutes+':'+ (seconds < 10 ? "0" : "") + seconds;
|
|
|
|
updateProgress(parseFloat(x.runtime)/parseFloat(x.totaltime)*100);
|
|
$('#state').html('<span class="glyphicon glyphicon-time" style="font-size: 22px; font-weight: normal"></span><span style="font-family: Digi; font-size: 40px;">' + eta + '</span>');
|
|
$('#target_temp').html(parseInt(x.target));
|
|
|
|
}
|
|
else
|
|
{
|
|
$("#nav_start").show();
|
|
$("#nav_stop").hide();
|
|
$('#state').html('<p class="ds-text">'+state+'</p>');
|
|
}
|
|
|
|
$('#act_temp').html(parseInt(x.temperature));
|
|
|
|
if (x.heat > 0.5) { $('#heat').addClass("ds-led-heat-active"); } else { $('#heat').removeClass("ds-led-heat-active"); }
|
|
if (x.cool > 0.5) { $('#cool').addClass("ds-led-cool-active"); } else { $('#cool').removeClass("ds-led-cool-active"); }
|
|
if (x.air > 0.5) { $('#air').addClass("ds-led-air-active"); } else { $('#air').removeClass("ds-led-air-active"); }
|
|
if (x.temperature > 45) { $('#hazard').addClass("ds-led-hazard-active"); } else { $('#hazard').removeClass("ds-led-hazard-active"); }
|
|
|
|
state_last = state;
|
|
|
|
}
|
|
};
|
|
|
|
// Control Socket ////////////////////////////////
|
|
|
|
ws_control.onopen = function()
|
|
{
|
|
|
|
};
|
|
|
|
ws_control.onmessage = function(e)
|
|
{
|
|
//Data from Simulation
|
|
console.log (e.data);
|
|
x = JSON.parse(e.data);
|
|
graph.live.data.push([x.runtime, x.temperature]);
|
|
graph.plot = $.plot("#graph_container", [ graph.profile, graph.live ] , getOptions());
|
|
|
|
}
|
|
|
|
// Storage Socket ///////////////////////////////
|
|
|
|
ws_storage.onopen = function()
|
|
{
|
|
ws_storage.send('GET');
|
|
};
|
|
|
|
|
|
ws_storage.onmessage = function(e)
|
|
{
|
|
message = JSON.parse(e.data);
|
|
|
|
if(message.resp)
|
|
{
|
|
if(message.resp == "FAIL")
|
|
{
|
|
if (confirm('Overwrite?'))
|
|
{
|
|
message.force=true;
|
|
console.log("Sending: " + JSON.stringify(message));
|
|
ws_storage.send(JSON.stringify(message));
|
|
}
|
|
else
|
|
{
|
|
//do nothing
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//the message is an array of profiles
|
|
//FIXME: this should be better, maybe a {"profiles": ...} container?
|
|
profiles = message;
|
|
//delete old options in select
|
|
$('#e2').find('option').remove().end();
|
|
|
|
// fill select with new options from websocket
|
|
for (var i=0; i<profiles.length; i++)
|
|
{
|
|
var profile = profiles[i];
|
|
//console.log(profile.name);
|
|
$('#e2').append('<option value="'+i+'">'+profile.name+'</option>');
|
|
|
|
if (profile.name == selected_profile_name)
|
|
{
|
|
selected_profile = i;
|
|
$('#e2').select2('val', i);
|
|
updateProfile(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
$("#e2").select2(
|
|
{
|
|
placeholder: "Select Profile",
|
|
allowClear: false,
|
|
minimumResultsForSearch: -1
|
|
});
|
|
|
|
|
|
$("#e2").on("change", function(e)
|
|
{
|
|
updateProfile(e.val);
|
|
});
|
|
|
|
}
|
|
});
|