aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2024-02-19 21:42:24 +0000
committers-ol <s+removethis@s-ol.nu>2024-02-19 21:42:24 +0000
commiteb48e150d184db175724ed824c6ef3611ecb6d2a (patch)
tree23d987603dcc022ef15b6b8f92613ab68ad0d381
parentdissectors/t937: rename constants, dissect PWM_*, STARTSTOP, REQ_READ, READ, ... (diff)
downloadt937-serial-eb48e150d184db175724ed824c6ef3611ecb6d2a.tar.gz
t937-serial-eb48e150d184db175724ed824c6ef3611ecb6d2a.zip
initial web control
-rw-r--r--web/index.html9
-rw-r--r--web/main.js240
-rw-r--r--web/utils.js34
3 files changed, 283 insertions, 0 deletions
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..286d42b
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>T-937/M Reflow Oven</title>
+ </head>
+ <body>
+ <script type="module" src="main.js" ></script>
+ </body>
+</html>
diff --git a/web/main.js b/web/main.js
new file mode 100644
index 0000000..c889eaa
--- /dev/null
+++ b/web/main.js
@@ -0,0 +1,240 @@
+import { hex, concat } from './utils.js';
+import * as UTILS from './utils.js';
+
+// a lot of these are useful for fuzzing
+for (const key in UTILS) {
+ window[key] = UTILS[key];
+}
+
+const ADDR = {
+ CTRL: 0xdb96,
+ OVEN: 0xaa55,
+};
+
+const FUNC = {
+ // used by oven
+ O_CONNECT: 0x01,
+ O_TEMPS: 0x02,
+ O_READ: 0x03,
+ O_ACK_WRITE: 0x06,
+
+ // used by CTRL
+ C_PWM_HEAT: 0x08,
+ C_PWM_COOL: 0x0d,
+ C_STARTSTOP: 0x10,
+ C_REQ_READ: 0x11,
+ C_WRITE: 0x20,
+ C_ACK_CONNECT: 0xf1,
+ C_ACK_READ: 0xf2,
+};
+
+const ARG = {
+ // for STARSTOP, REQUEST_READ
+ STARTSTOP_START: 0x11,
+ STARTSTOP_STOP: 0x22,
+ REQUEST_READ: 0x11,
+
+ // for CONNECT / ACK_CONNECT
+ CONNECT_CONNECTING: 0x0100,
+ CONNECT_CONNECTED: 0x0200,
+
+ // for *_PWM
+ PWM_ON: 0x44,
+ PWM_OFF: 0x88,
+};
+
+window.ADDR = ADDR;
+window.FUNC = FUNC;
+window.ARG = ARG;
+
+const connect = document.createElement('button');
+connect.innerText = 'connect';
+document.body.append(connect);
+
+const status = document.createElement('textarea');
+status.disabled = true;
+status.innerText = '';
+status.rows = 1;
+status.style.fontFamily = 'monospace';
+document.body.append(status);
+
+class Message {
+ constructor(buffer) {
+ this.buf = buffer;
+ this.view = new DataView(this.buf);
+ this.data = new DataView(this.buf, 4, this.buf.byteLength - 6);
+ }
+
+ get16(i) { return this.data.getUint16(i, false); }
+ set16(i, v) { this.data.setUint16(i, v, false); }
+
+ get address() { return this.view.getUint16(0, false); }
+ set address(val) { this.view.setUint16(0, val, false); }
+
+ get func() { return this.view.getUint8(2); }
+ set func(val) { this.view.setUint8(2, val)}
+
+ get data_len() { return this.view.getUint8(3); }
+ set data_len(val) { this.view.setUint8(3, val); }
+
+ get length() { return this.buf.byteLength; }
+
+ get checksum() { return this.view.getUint16(this.buf.byteLength-2, false); }
+ set checksum(val) { this.view.setUint16(this.buf.byteLength-2, val, false); }
+
+ calculateSum() {
+ let sum = 0;
+ for (let i = 0; i < this.length - 2; i++) {
+ sum += this.view.getUint8(i);
+ }
+ return sum & 0xffff;
+ }
+
+ static from8(addr, func, data) {
+ const len = 2 + 2 + data.length + 2;
+
+ const msg = new Message(new ArrayBuffer(len));
+ msg.address = addr;
+ msg.func = func;
+ msg.data_len = data.length;
+
+ for (let i = 0; i < data.length; i++) {
+ msg.data.setUint8(i, data[i]);
+ }
+
+ msg.checksum = msg.calculateSum();
+ return msg;
+ }
+
+ static from16(addr, func, data) {
+ const len = 2 + 2 + 2*data.length + 2;
+
+ const msg = new Message(new ArrayBuffer(len));
+ msg.address = addr;
+ msg.func = func;
+ msg.data_len = 2*data.length;
+
+ for (let i = 0; i < data.length; i++) {
+ msg.data.setUint16(2*i, data[i], false);
+ }
+
+ msg.checksum = msg.calculateSum();
+ return msg;
+ }
+
+ static empty(addr, func, data_len) {
+ const len = 2 + 2 + 2*data_len + 2;
+
+ const msg = new Message(new ArrayBuffer(len));
+ msg.address = addr;
+ msg.func = func;
+ msg.data_len = data_len;
+
+ return msg;
+ }
+
+ static tryParse(buffer) {
+ if (buffer.length < 4 + 2) return;
+
+ const data_len = buffer[3];
+ if (buffer.length < 4 + data_len + 2) return;
+
+ const msg = new Message(buffer.slice(0, 4 + data_len + 2).buffer);
+ if (msg.calculateSum() !== msg.checksum) {
+ throw new Error(`checksum mismatch: expected ${hex(msg.checksum)} but calculated ${hex(msg.calculateSum())}`);
+ }
+ return msg;
+ };
+}
+
+const readLoop = async (reader, onMessage) => {
+ let buffer = new Uint8Array();
+
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) throw new Error("reader cancelled");
+
+ buffer = concat(buffer, value);
+ do {
+ const msg = Message.tryParse(buffer);
+ if (!msg) break;
+
+ await onMessage(msg);
+ buffer = buffer.slice(msg.length);
+ } while (buffer.length);
+ }
+};
+
+const wrapLoop = async (fn, stream, ...args) => {
+};
+
+connect.onclick = async () => {
+ const port = await navigator.serial.requestPort();
+ window.PORT = port;
+ console.log('got port', port);
+
+ await port.open({ baudRate: 38400 });
+ console.log('opened port');
+
+ let reader, writer;
+
+ const onMessage = async (msg) => {
+ if (msg.address !== ADDR.CTRL) {
+ console.warn(`unknown address ${hex(msg.address)}`);
+ return;
+ }
+
+ switch (msg.func) {
+ case FUNC.O_CONNECT: {
+ switch (msg.get16(0)) {
+ case ARG.CONNECT_CONNECTING:
+ console.log('oven found');
+ const res = Message.from16(ADDR.OVEN, FUNC.C_ACK_CONNECT, [ARG.CONNECT_CONNECTED]);
+ await writer.write(res.buf);
+ break;
+
+ case ARG.CONNECT_CONNECTED:
+ console.log('connection established');
+ break;
+ }
+ break;
+ }
+
+ case FUNC.O_TEMPS: {
+ const temp1 = msg.get16(0);
+ const temp2 = msg.get16(2);
+ const temp3 = msg.get16(4);
+ status.innerText = `${temp1}°C - ${temp2}°C // ${temp3}°C`;
+ break;
+ }
+
+ default:
+ console.warn(`unknown func ${hex(msg.func)}`, msg);
+ break;
+ }
+ }
+
+ try {
+ reader = port.readable.getReader();
+ writer = port.writable.getWriter();
+ window.writer = writer;
+
+ await readLoop(reader, onMessage);
+ } finally {
+ console.log('shutting down');
+ reader.releaseLock();
+ writer.releaseLock();
+ }
+};
+
+// send a message (uint16 data)
+window.send16 = (func, ...data) => {
+ const msg = Message.from16(ADDR.OVEN, func, data);
+ return window.writer.write(msg.buf);
+};
+
+// send a message (uint8 data)
+window.send8 = (func, ...data) => {
+ const msg = Message.from8(ADDR.OVEN, func, data);
+ return window.writer.write(msg.buf);
+};
diff --git a/web/utils.js b/web/utils.js
new file mode 100644
index 0000000..d7cf657
--- /dev/null
+++ b/web/utils.js
@@ -0,0 +1,34 @@
+// format as hex number
+export const hex = (n) => '0x' + n.toString(16);
+
+// concatenate Uint8Array
+export const concat = (a, b) => {
+ const arr = new Uint8Array(a.length + b.length);
+ arr.set(a);
+ arr.set(b, a.length);
+ return arr;
+};
+
+// like Array.reduce() but await each iteration
+export const areduce = async (array, fn, state=null) => {
+ for (const val of array) {
+ state = await fn(val, state);
+ }
+ return state;
+};
+
+// generate array with range [a, b[ or [0, a[
+export const range = (a, b=null) => {
+ if (b === null) {
+ b = a;
+ a = 0;
+ }
+
+ const res = [];
+ for (let i = a; i < b; i++)
+ res.push(i);
+ return res;
+};
+
+// return a Promise that resolves in n seconds
+export const sleep = (n) => new Promise(res => setTimeout(res, n*1000));