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}} {{/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 = `

Load Recipe

{{#selected_recipe}} {{description}}

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); // Workaround an incompatibility with a temporary version, you can remove this "if" if (value !== undefined) date.setSeconds(value); date.setSeconds(value); var timeString = date.toISOString().substring(11, 19); return timeString; } 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 = `

Active Recipe ({{recipe-time}}): {{name}}

{{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 }