aboutsummaryrefslogtreecommitdiffstats
path: root/web/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/main.js')
-rw-r--r--web/main.js156
1 files changed, 134 insertions, 22 deletions
diff --git a/web/main.js b/web/main.js
index b93b2da..6d29a50 100644
--- a/web/main.js
+++ b/web/main.js
@@ -1,5 +1,5 @@
import { ADDR, FUNC, ARG, Message } from './protocol.js';
-import { hex, concat } from './utils.js';
+import { hex, concat, hash } from './utils.js';
import * as plot from './plot.js';
import * as PROTOCOL from './protocol.js';
import * as UTILS from './utils.js';
@@ -26,6 +26,60 @@ window.send8 = (func, ...data) => {
return PORT.write(msg.buf);
};
+const samplePlot = (data, interval=4) => {
+ if (data.length < 2) return data;
+ data = data.slice();
+
+ const result = [];
+
+ let i = 0;
+ let prev;
+ while (data.length) {
+ while (data.length && data[0][0] <= i) {
+ prev = data.shift();
+ }
+
+ const next = data[0];
+ if (!next) {
+ result.push([ i, prev[1] ]);
+ break;
+ }
+
+ const dtemp = next[1] - prev[1];
+ const dtime = next[0] - prev[0];
+ const t = (i - prev[0]) / dtime;
+ result.push([ i, Math.round(prev[1] + t * dtemp) ]);
+
+ i += interval;
+ }
+
+ return result;
+};
+
+const unsamplePlot = (data, tolerance_interval=4) => {
+ if (data.length < 2) return data;
+
+ const result = [];
+ result.push(data[0]);
+
+ const tolerance = 1.1 / tolerance_interval;
+
+ for (let i = 1; i < data.length - 1; i++) {
+ const prev = data[i - 1];
+ const curr = data[i];
+ const next = data[i + 1];
+
+ const left = (curr[1] - prev[1]) / (curr[0] - prev[0]);
+ const right = (next[1] - curr[1]) / (next[0] - curr[0]);
+
+ if (Math.abs(left - right) < tolerance) continue;
+ result.push(curr);
+ }
+
+ result.push(data[data.length - 1]);
+ return result;
+};
+
let PORT = null; // { write(buf), disconnect() } | null
let STATUS = null; // null | 'connecting' | 'idle' | 'manual' | 'running'
let START_TIME = null;
@@ -48,12 +102,11 @@ plot.addPlot('temp3', { color: 'sienna' });
plot.addPlot('tempavg', { color: 'rebeccapurple' });
const elements = [
- 'temps_chamber', 'temps_controller',
- 'connect', 'status',
+ 'connect', 'status', 'temps_chamber', 'temps_controller',
'profile', 'start', 'read', 'write',
- 'profile_load', 'profile_save', 'profile_create', 'profile_show', 'profile_edit',
- 'paste_load', 'paste_save', 'paste_create', 'paste_show', 'paste_edit',
'heat', 'heat_mode', 'cool', 'cool_mode',
+ 'profile_load', 'profile_save', 'profile_delete', 'profile_show', 'profile_edit', 'profile_clear', 'profile_stored',
+ 'paste_load', 'paste_save', 'paste_delete', 'paste_show', 'paste_edit', 'paste_clear', 'paste_stored',
];
const el = Object.fromEntries(elements.map(id => [id, document.getElementById(id)]));
@@ -101,6 +154,28 @@ const updateUI = () => {
plot.update();
};
+window.onstorage = () => {
+ const profile = el.profile_stored.value;
+ const paste = el.paste_stored.value;
+
+ el.profile_stored.innerHTML = '<option value="" class="new">(new profile)</option>';
+ el.paste_stored.innerHTML = '<option value="" class="new">(new paste)</option>';
+
+ for (const key of Object.keys(window.localStorage)) {
+ if (key.startsWith('profile-')) {
+ const data = JSON.parse(window.localStorage[key]);
+ el.profile_stored.add(new Option(data.name, key));
+ } else if (key.startsWith('paste-')) {
+ const data = JSON.parse(window.localStorage[key]);
+ el.paste_stored.add(new Option(data.name, key));
+ }
+ }
+
+ el.profile_stored.value = profile;
+ el.paste_stored.value = paste;
+};
+
+window.onstorage();
updateUI();
const onMessage = async (msg) => {
@@ -312,20 +387,57 @@ if (window.location.hash !== '#ws') {
console.log('opened websocket');
};
}
-el.profile_show.onclick = () => {
- plot.PLOTS.profile.visible = !plot.PLOTS.profile.visible;
- updateUI();
-};
-el.paste_show.onclick = () => {
- plot.PLOTS.pase.visible = !plot.PLOTS.pase.visible;
- updateUI();
-};
-el.profile_edit.onclick = () => {
- plot.toggleActive('profile');
- updateUI();
-};
-el.paste_edit.onclick = () => {
- plot.toggleActive('paste');
- updateUI();
-};
-};
+
+for (const p of ['profile', 'paste']) {
+ el[p + '_show'].onclick = () => {
+ plot.PLOTS[p].visible = !plot.PLOTS[p].visible;
+ updateUI();
+ };
+ el[p + '_edit'].onclick = () => {
+ plot.toggleActive(p);
+ updateUI();
+ };
+ el[p + '_clear'].onclick = () => {
+ plot.PLOTS[p].data = [[0, 24]];
+ updateUI();
+ };
+
+ el[p + '_load'].onclick = () => {
+ const key = el[p + '_stored'].value;
+ let data = window.localStorage[key];
+ if (!data) return;
+
+ plot.PLOTS[p].data = JSON.parse(data).data;
+ updateUI();
+ };
+
+ el[p + '_save'].onclick = () => {
+ let key = el[p + '_stored'].value;
+ let name;
+ if (key === '') {
+ name = window.prompt('please enter the new profile name');
+ if (!name) return;
+ } else {
+ name = JSON.parse(window.localStorage[key]).name;
+ delete window.localStorage[key];
+ }
+
+ const data = plot.PLOTS[p].data;
+ if (p === 'profile') {
+ const sampled = new Uint8Array(samplePlot(data).map(d => Math.round(d[1])));
+ key = `profile-${hash(sampled)}`;
+ } else {
+ key = `paste-${name}`;
+ }
+
+ window.localStorage[key] = JSON.stringify({ name, data });
+ window.onstorage();
+ el[p + '_stored'].value = key;
+ };
+
+ el[p + '_delete'].onclick = () => {
+ const key = el[p + '_stored'].value;
+ delete window.localStorage[key];
+ window.onstorage();
+ };
+}