diff options
| author | s-ol <s+removethis@s-ol.nu> | 2024-03-20 17:38:13 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2024-03-20 17:38:13 +0000 |
| commit | f56bfd50c671fd29b1a442adcfea57ec9f74356e (patch) | |
| tree | 24beb774ad5b43608c84e4440d6ba325383a42ca | |
| parent | simulator: simulate simultaneous heat/cool state (diff) | |
| download | t937-serial-f56bfd50c671fd29b1a442adcfea57ec9f74356e.tar.gz t937-serial-f56bfd50c671fd29b1a442adcfea57ec9f74356e.zip | |
web, simulator: support WebSocket transport for development
| -rw-r--r-- | simulator/README.md | 9 | ||||
| -rw-r--r-- | simulator/__init__.py | 38 | ||||
| -rw-r--r-- | web/README.md | 4 | ||||
| -rw-r--r-- | web/main.js | 143 |
4 files changed, 132 insertions, 62 deletions
diff --git a/simulator/README.md b/simulator/README.md index e3980da..df091d7 100644 --- a/simulator/README.md +++ b/simulator/README.md @@ -4,6 +4,7 @@ This directory contains a 'dummy' oven implemented in Python. It exists mainy to facilitate development of the web client without physical access to an oven (and without wasting energy). The code has been written and tested with Python 3.11 but should work with most if not all Python 3 versions. +Either the `pyserial` or `websockets` python library is required, depending on the operating mode: ## Serial loopback on Linux @@ -16,4 +17,12 @@ You can use tty0tty like this: python simulator/__init__.py /dev/tnt1 # /dev/tnt0 now behaves as if it were a reflow oven +## WebSocket implementation + +Alternatively, WebSockets can be used as a transport for developopment. +To run the simulator in websocket mode, omit the serial port argument: + + python simulator/__init__.py + # a WebSocket server is now accepting connections at 0.0.0.0:8001 + [tty0tty]: https://github.com/freemed/tty0tty diff --git a/simulator/__init__.py b/simulator/__init__.py index e81ae9a..5fc1366 100644 --- a/simulator/__init__.py +++ b/simulator/__init__.py @@ -1,5 +1,4 @@ import random -import serial import time import sys @@ -178,8 +177,37 @@ def run(port): print("unexpected message", msg) +class WSPort: + def __init__(self, ws): + self.ws = ws + self.in_waiting = 0 + + def write(self, data): + self.ws.send(data) + + def read(self, _): + try: + return self.ws.recv(0) + except TimeoutError: + pass + + @staticmethod + def wrap(fn): + return lambda ws: fn(WSPort(ws)) + + if __name__ == "__main__": - port = sys.argv[-1] - print(f"starting Dummy Oven on port {port}") - with serial.Serial(port, baudrate=38400, timeout=1) as ser: - run(ser) + if len(sys.argv) < 2: + import websockets.sync.server as ws + + print("starting Dummy Oven over WebSocket on port :8001") + with ws.serve(WSPort.wrap(run), host="0.0.0.0", port=8001) as server: + server.serve_forever() + + else: + import serial + + port = sys.argv[-1] + print(f"starting Dummy Oven on port {port}") + with serial.Serial(port, baudrate=38400, timeout=1) as ser: + run(ser) diff --git a/web/README.md b/web/README.md index 021f437..6b0d1a4 100644 --- a/web/README.md +++ b/web/README.md @@ -7,3 +7,7 @@ WebSerial is only available on HTTP/S origins, so to run the application locally cd t937-serial/web python -m http.server # now live at http://localhost:8000 + +## WebSocket development mode + +To use WebSocket transport for development, add `#ws` to the site URL: http://localhost:8000/#ws diff --git a/web/main.js b/web/main.js index d164f70..37ef19e 100644 --- a/web/main.js +++ b/web/main.js @@ -16,17 +16,17 @@ for (const key in UTILS) { window.send16 = (func, ...data) => { const msg = Message.from16(ADDR.OVEN, func, data); console.info(msg.toString(), msg); - return PORT.writer.write(msg.buf); + return PORT.write(msg.buf); }; // send a message (uint8 data) window.send8 = (func, ...data) => { const msg = Message.from8(ADDR.OVEN, func, data); console.info(msg.toString(), msg); - return PORT.writer.write(msg.buf); + return PORT.write(msg.buf); }; -let PORT = null; // { port, reader, writer } | null +let PORT = null; // { write(buf), disconnect() } | null let STATUS = null; // null | 'connecting' | 'idle' | 'manual' | 'running' let START_TIME = null; let END_TIME = null; @@ -87,17 +87,6 @@ const updateUI = () => { updateUI(); -const disconnect = async () => { - const port = PORT; - PORT = null; - STATUS = null; - updateUI(); - - await port.reader.cancel().catch(() => null); - await port.writer.close().catch(() => null); - await port.port.close(); -}; - const onMessage = async (msg) => { if (msg.func === FUNC.O_TEMPS) { console.debug(msg.toString(), msg); @@ -116,7 +105,7 @@ const onMessage = async (msg) => { case ARG.CONNECT_CONNECTING: console.log('oven found'); const res = Message.from16(ADDR.OVEN, FUNC.C_ACK_CONNECT, [ARG.CONNECT_CONNECTED]); - await PORT.writer.write(res.buf); + await PORT.write(res.buf); break; case ARG.CONNECT_CONNECTED: @@ -132,7 +121,7 @@ const onMessage = async (msg) => { TIMEOUT = setTimeout(() => { if (!PORT) return; console.error('port timed out'); - disconnect(); + PORT.disconnect(); }, 2500); const temp1 = msg.get16(0); @@ -162,47 +151,6 @@ const onMessage = async (msg) => { updateUI(); } -const loop = async () => { - let buffer = new Uint8Array(); - - while (true) { - const { value, done } = await PORT.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); - } -}; - -el.connect.onclick = async () => { - if (!!PORT) return disconnect(); - - const port = await navigator.serial.requestPort(); - await port.open({ baudRate: 38400 }); - console.log('opened port'); - - let reader, writer; - try { - reader = port.readable.getReader(); - writer = port.writable.getWriter(); - PORT = { port, reader, writer }; - STATUS = 'connecting'; - updateUI(); - - await loop(); - } finally { - console.log('shutting down'); - reader.releaseLock(); - writer.releaseLock(); - } -}; - const pwms = [ [el.heat, el.heat_mode], [el.cool, el.cool_mode], @@ -268,3 +216,84 @@ el.start.onclick = () => { } updateUI(); }; + +if (window.location.hash !== '#ws') { + el.connect.onclick = async () => { + if (!!PORT) return PORT.disconnect(); + + const port = await navigator.serial.requestPort(); + await port.open({ baudRate: 38400 }); + console.log('opened port'); + + let reader, writer; + try { + reader = port.readable.getReader(); + writer = port.writable.getWriter(); + + const write = (buf) => writer.write(buf); + + const disconnect = async () => { + PORT = null; + STATUS = null; + updateUI(); + + await reader.cancel().catch(() => null); + await writer.close().catch(() => null); + await port.close(); + }; + + PORT = { write, disconnect }; + STATUS = 'connecting'; + updateUI(); + + 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); + } + } finally { + console.log('shutting down'); + reader.releaseLock(); + writer.releaseLock(); + } + }; +} else { + el.connect.onclick = async () => { + if (!!PORT) return disconnect(); + + const sock = new WebSocket('ws://localhost:8001'); + sock.binaryType = 'arraybuffer'; + + const write = (buf) => sock.send(buf); + const disconnect = () => sock.close(); + + PORT = { write, disconnect }; + STATUS = 'connecting'; + updateUI(); + + sock.onmessage = (e) => { + const msg = Message.tryParse(new Uint8Array(e.data)); + if (!msg) console.error('invalid WS message', msg); + onMessage(msg); + }; + + sock.onclose = () => { + console.log('shutting down'); + PORT = null; + STATUS = null; + updateUI(); + }; + + console.log('opened websocket'); + }; +} |
