diff options
| author | s-ol <s+removethis@s-ol.nu> | 2024-02-19 21:42:24 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2024-02-19 21:42:24 +0000 |
| commit | eb48e150d184db175724ed824c6ef3611ecb6d2a (patch) | |
| tree | 23d987603dcc022ef15b6b8f92613ab68ad0d381 | |
| parent | dissectors/t937: rename constants, dissect PWM_*, STARTSTOP, REQ_READ, READ, ... (diff) | |
| download | t937-serial-eb48e150d184db175724ed824c6ef3611ecb6d2a.tar.gz t937-serial-eb48e150d184db175724ed824c6ef3611ecb6d2a.zip | |
initial web control
| -rw-r--r-- | web/index.html | 9 | ||||
| -rw-r--r-- | web/main.js | 240 | ||||
| -rw-r--r-- | web/utils.js | 34 |
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)); |
