Forráskód Böngészése

Add webserial demo (#2623)

* Initial commit of demo files

* run autopep8

* add requirements.txt

* Simplified Arduino code. Receives a 1/0 to toggle and LED on/off; pressing button sends a '1'

* simplify GUI (LED toggle buttons; message pop-up on button click)

* some JS clean-up

* cleaned up connect()

* change update_plot to custom_event (generalization)

* update Arduino code to send button press counts

* run autopep8

* code review

* more code review

* add link to website

---------

Co-authored-by: BlankAdventure <>
Co-authored-by: Falko Schindler <falko@zauberzeug.com>
BlankAdventure 1 éve
szülő
commit
9e6e0a8622

+ 40 - 0
examples/webserial/main.py

@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+from pathlib import Path
+
+from nicegui import ui
+
+
+@ui.page('/')
+def page():
+    async def connect() -> None:
+        if not await ui.run_javascript('"serial" in navigator'):
+            ui.notify('WebSerial is not available in this browser.')
+            return
+
+        if not await ui.run_javascript('connect()', timeout=100):
+            ui.notify('Could not connect to the device.')
+            return
+
+        ui.run_javascript('readLoop()')
+        state['connected'] = True
+
+    async def disconnect() -> None:
+        ui.run_javascript('disconnect()', timeout=5)
+        state['connected'] = False
+
+    state = {
+        'connected': False,
+        'button': False,
+    }
+
+    ui.add_body_html(f'<script>{(Path(__file__).parent / "script.js").read_text()}</script>')
+
+    ui.button('Connect', on_click=connect).bind_visibility_from(state, 'connected', value=False)
+    ui.button('Disconnect', on_click=disconnect).bind_visibility_from(state, 'connected')
+    ui.switch('LED', on_change=lambda e: ui.run_javascript(f'send({e.value:d})')).bind_enabled_from(state, 'connected')
+    ui.switch('Button').props('disable').bind_value_from(state, 'button')
+
+    ui.on('read', lambda e: state.update(button=e.args == 'LOW'))
+
+
+ui.run()

+ 71 - 0
examples/webserial/script.js

@@ -0,0 +1,71 @@
+let port;
+let outputDone;
+let outputStream;
+let inputDone;
+let inputStream;
+let reader;
+
+async function connect() {
+  try {
+    port = await navigator.serial.requestPort();
+    await port.open({ baudRate: 115200 });
+  } catch (err) {
+    console.log(err);
+    return false;
+  }
+
+  const encoder = new TextEncoderStream();
+  outputDone = encoder.readable.pipeTo(port.writable);
+  outputStream = encoder.writable;
+
+  const decoder = new TextDecoderStream();
+  inputDone = port.readable.pipeTo(decoder.writable);
+  inputStream = decoder.readable;
+  reader = inputStream.getReader();
+
+  return true;
+}
+
+async function disconnect() {
+  if (reader) {
+    await reader.cancel();
+    await inputDone.catch(() => {});
+    reader = null;
+    inputDone = null;
+  }
+  if (outputStream) {
+    await outputStream.getWriter().close();
+    await outputDone;
+    outputStream = null;
+    outputDone = null;
+  }
+  if (port) {
+    await port.close();
+    port = null;
+  }
+}
+
+function send(message) {
+  const writer = outputStream.getWriter();
+  writer.write(message + "\n");
+  writer.releaseLock();
+}
+
+async function readLoop() {
+  let fullStr = "";
+  while (true) {
+    const { value, done } = await reader.read();
+    if (value) {
+      fullStr += value;
+      const res = fullStr.match(/(.*)\r\n/);
+      if (res) {
+        fullStr = "";
+        emitEvent("read", res[1]);
+      }
+    }
+    if (done) {
+      reader.releaseLock();
+      break;
+    }
+  }
+}

+ 31 - 0
examples/webserial/sketch/sketch.ino

@@ -0,0 +1,31 @@
+const int BUTTON = 12;
+const int LED = 13;
+
+int lastButtonState = HIGH;
+
+void setup()
+{
+  pinMode(BUTTON, INPUT_PULLUP);
+  pinMode(LED, OUTPUT);
+  Serial.begin(115200);
+}
+
+void loop() {
+  if (Serial.available() > 0) {
+    switch (Serial.read()) {
+      case '1':
+        digitalWrite(LED, HIGH);
+        break;
+      case '0':
+        digitalWrite(LED, LOW);
+        break;
+    }
+  }
+
+  int buttonState = digitalRead(BUTTON);
+  if (lastButtonState != buttonState) {
+    Serial.println(buttonState == LOW ? "LOW" : "HIGH");
+    delay(50); // avoid bouncing
+  }
+  lastButtonState = digitalRead(BUTTON);
+}

+ 1 - 0
website/examples.py

@@ -62,5 +62,6 @@ examples: List[Example] = [
     Example('FullCalendar', 'show an interactive calendar using the [FullCalendar library](https://fullcalendar.io/)'),
     Example('Pytest', 'test a NiceGUI app with pytest'),
     Example('Pyserial', 'communicate with a serial device'),
+    Example('Webserial', 'communicate with a serial device using the WebSerial API'),
     Example('Websockets', 'use [websockets library](https://websockets.readthedocs.io/) to start a websocket server'),
 ]