var state = {};
var localstate = {
manual: {}, recipe: 0, page: ['General', 'Dashboard'],
maxpoints: 1000,
editing: {}
};
var menu_items = [
{name: "General",
subitems:[
{name: "Dashboard"},
{name: "Sensors & Plots"}
]
},
{name: "Recipes",
subitems:[
{name: "Recipes Editor", link: 'edit-recipe'},
{name: "New Recipe"}
]
},
{name: "Configuration",
subitems:[
{name: "Settings"}
]
}
];
function applyState(newstate) {
if (newstate !== undefined) {
state = JSON.parse(JSON.stringify(newstate));
}
// preprocess
state.manual["dismissed"] = localstate.manual["dismissed"];
let selected_recipe = null;
for (let i = 0; i < state.recipes.length; i++) {
state.recipes[i].idx = i;
if (localstate.recipe == i) selected_recipe = state.recipes[i];
state.recipes[i].selected = localstate.recipe == i;
}
state.selected_recipe = JSON.parse(JSON.stringify(selected_recipe));
for (let i = 0; i < menu_items.length; i++) {
let group_match = menu_items[i].name == localstate.page[0];
for (let sub = 0; sub < menu_items[i].subitems.length; sub++) {
let sub_match = menu_items[i].subitems[sub].name == localstate.page[1];
menu_items[i].subitems[sub].current = group_match && sub_match;
menu_items[i].subitems[sub].path = JSON.stringify([
menu_items[i].name, menu_items[i].subitems[sub].name]);
}
}
state.menu_items = menu_items;
console.log('Apply state '+JSON.stringify(state));
// apply
render_actuators(state.actuators);
render_sidebar(state);
manual_modal(state.manual);
current_recipe(Object.assign({}, state.state, state.recipe));
render_load_recipe(state);
render_plot();
localstate.editing.recipe = state.recipes.findIndex(
function (el) {return el.name == localstate.editing.recipe_name;});
}
var enable_draw = true;
var plot_data = {};
function toggle_update_plot() {
enable_draw = !enable_draw;
}
function pause_plotting() {enable_draw = false;}
function resume_plotting() {enable_draw = true;}
function set_plot_points(value) {
let current = localstate.maxpoints;
if (value === undefined) {
value = parseInt(document.getElementById('point-number').value);
}
localstate.maxpoints = value;
if (value <= current) {
// we alredy have all the points we want just set it and let other
// function discard extra points
return;
}
// Ask for more data
socket.emit('get sensors history', value);
}
function add_plot_data(newpoints, render=true) {
for (var i = 0; i < newpoints.length; ++i) {
let point = newpoints[i]
let key = point[0];
if (!(key in plot_data)) {
plot_data[key] = {
x: [],
y: [],
type: 'scatter',
name: key
}
}
plot_data[key].x.push(point[1]);
plot_data[key].y.push(point[2]);
// This is different since we want maxpoints to be the absolute max, not
// for each variable, but good enough for now (or the opposite?)
while (plot_data[key].x.length > localstate.maxpoints) {
plot_data[key].x.shift();
plot_data[key].y.shift();
}
}
if (render) render_plot();
}
function render_plot() {
if (enable_draw && document.getElementById("data-plot") !== null) {
Plotly.newPlot('data-plot', Object.values(plot_data));
}
}
function render_actuators(data) {
let html = document.getElementById("actuator-list");
if (data === undefined || html === null) return;
let template = `
Total Consumption: {{total}}Wh
{{#consumption}}
{{name}}: {{Wh}}Wh
{{/consumption}}
`;
let total = 0;
let out = [];
for (var key in data.consumption){
let val = data.consumption[key];
total += val;
out.push({name: key, Wh: val.toFixed(2)});
}
html.innerHTML = Mustache.render(template, { total: total.toFixed(2),
consumption: out });
}
function render_sensors(sensordata) {
let html = document.getElementById("sensor-list");
if (state.sensors === undefined || html === null) return;
let template = `
{{#sensors}}
{{what}}
{{^data}}No sensors found{{/data}}
{{#data}}
- {{0}} -- {{1}} {{unit}}
{{/data}}
{{/sensors}}
`;
let data = [];
for (let i = 0, k = Object.keys(state.sensors.units); i < k.length; ++i) {
let what = k[i];
let unit = state.sensors.units[k[i]];
let sensors = Object.keys(
Object.keys(state.sensors.found).reduce(function (filtered, key) {
if (state.sensors.found[key] == what) filtered[key] = state.sensors.found[key];
return filtered;
}, {}));
let req_data = [];
for (let j = 0; j < sensordata.length; ++j) {
if (sensors.includes(sensordata[j][0])) {
req_data.push([sensordata[j][0], sensordata[j][2]]);
}
}
data.push({what: what, unit: unit, data: req_data});
}
html.innerHTML = Mustache.render(template, {sensors:data});
}
function navigate(path, link) {
if (link !== undefined) {
window.location.href = "/" + link
return;
}
localstate.page = path;
applyState();
}
function render_sidebar(data) {
let html = document.getElementById("sidebar");
let template = ``
html.innerHTML = Mustache.render(template, data);
}
function respond_manual(response) {
socket.emit('manual response', response);
// Hide the modal, it will appear again if needed
manual_modal({'dismissed': true});
}
function eval_scheme(id) {
socket.emit('eval scheme', document.getElementById(id).value)
}
function select_recipe(idx) {
localstate.recipe = idx;
applyState();
}
function load_recipe() {
socket.emit('load recipe idx', localstate.recipe);
}
function edit_recipe(recipeid) {
let recipe = recipeid === undefined ? localstate.recipe : recipeid;
window.location.href = '/edit-recipe/'+recipe;
}
function currently_editing_recipe(name) {
localstate.editing.recipe_name = name;
}
function update_field(fieldname, newvalue) {
state.recipes[localstate.editing.recipe].phases[localstate.editing.phase][fieldname] = newvalue;
console.log(state.recipes[localstate.editing.recipe].phases[localstate.editing.phase]);
console.log(fieldname, newvalue);
}
function save_phase() {
let edited_phase = state.recipes[localstate.editing.recipe].phases[
localstate.editing.phase];
console.log(edited_phase);
socket.emit('update recipe phase',
localstate.editing.recipe, localstate.editing.phase, edited_phase)
}
function edit_phase(phaseid) {
console.log(localstate);
let html = document.getElementById("phase-editor");
if (html === null) return;
localstate.editing.phase = phaseid;
data = state.recipes[localstate.editing.recipe].phases[phaseid];
console.log(data.text);
let template = `
`;
html.innerHTML = Mustache.render(template, data);
}
function stop_recipe() {
socket.emit('stop recipe');
}
function maybe_stop_recipe() {
question_modal({required: true, dismissed: false, label: "Stoppiamo?", options: [
{action: "stop_recipe();", tag:"Sicuro"},
{action: "alert('non interrompiamo')", tag:"No"}]});
}
function next_phase() {
socket.emit('next phase');
}
function dismiss_modal() {
applyState(state);
}
function reload_recipes() {
socket.emit('reload recipes');
}
function render_load_recipe(data) {
let html = document.getElementById("load-recipe");
if (html === null) return;
let template = `
{{#selected_recipe}}
{{description}}
Controllers:
{{#controllers}}
- {{.}}
{{/controllers}}
{{#phases}}
{{name}}
{{text}}
On Load: {{onload}}
Exit When: {{nextcond}}
On Exit: {{onexit}}
{{/phases}}
{{/selected_recipe}}
`
html.innerHTML = Mustache.render(template, data);
}
function stodate(value) {
var date = new Date(0);
date.setSeconds(value);
return date.toISOString().substring(11, 19);
}
function current_recipe(data) {
let html = document.getElementById("current-recipe");
if (html === null) return;
if (data === null || data.phases === undefined) {
html.innerHTML = '';
return;
}
data.current_phase = data.phases.find(function (p) { return p.current; });
let template = `
{{description}}
{{recipe-consumption}}
{{current_phase.text}}
{{phase-consumption}}
Next Cond: {{current_phase.nextcond}}
On Load: {{current_phase.onload}}
On Exit: {{current_phase.onexit}}
`;
data['recipe-time'] = stodate(data['recipe-time'])
data['phase-time'] = stodate(data['phase-time'])
html.innerHTML = Mustache.render(template, data);
}
function manual_modal(data) {
let modal = document.getElementById("manual-modal");
let template = ``;
modal.innerHTML = Mustache.render(template, data);
}
function question_modal(data) {
let modal = document.getElementById("question-modal");
let template = ``;
modal.innerHTML = Mustache.render(template, data);
}
let items = document.querySelectorAll('#items-list > li')
items.forEach(item => {
$(item).prop('draggable', true)
item.addEventListener('dragstart', dragStart)
item.addEventListener('drop', dropped)
item.addEventListener('dragenter', cancelDefault)
item.addEventListener('dragover', cancelDefault)
})
function dragStart (e) {
var index = $(e.target).index()
e.dataTransfer.setData('text/plain', index)
}
function dropped (e) {
cancelDefault(e)
// get new and old index
let oldIndex = e.dataTransfer.getData('text/plain')
let target = $(e.target)
let newIndex = target.index()
// remove dropped items at old place
let dropped = $(this).parent().children().eq(oldIndex).remove()
// insert the dropped items at new place
if (newIndex < oldIndex) {
target.before(dropped)
} else {
target.after(dropped)
}
}
function cancelDefault (e) {
e.preventDefault()
e.stopPropagation()
return false
}