aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2024-03-20 17:38:13 +0000
committers-ol <s+removethis@s-ol.nu>2024-03-20 17:38:13 +0000
commitf56bfd50c671fd29b1a442adcfea57ec9f74356e (patch)
tree24beb774ad5b43608c84e4440d6ba325383a42ca
parentsimulator: simulate simultaneous heat/cool state (diff)
downloadt937-serial-f56bfd50c671fd29b1a442adcfea57ec9f74356e.tar.gz
t937-serial-f56bfd50c671fd29b1a442adcfea57ec9f74356e.zip
web, simulator: support WebSocket transport for development
-rw-r--r--simulator/README.md9
-rw-r--r--simulator/__init__.py38
-rw-r--r--web/README.md4
-rw-r--r--web/main.js143
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');
+ };
+}