Compare commits
4 Commits
bd4d2060ae
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2961ad2a29 | |||
| 35c0df8f88 | |||
| 5c97fa0d0b | |||
| 179ac9c540 |
@@ -20,13 +20,14 @@ Connection is always via **`-p` / `--port`** (default `/dev/ttyACM0`). There is
|
|||||||
| `-o`, `--order` | LED colour order (`rgb`, `grb`, …) |
|
| `-o`, `--order` | LED colour order (`rgb`, `grb`, …) |
|
||||||
| `--preset` / `--pattern` | Create or replace a named preset in **led-driver** `presets.json` |
|
| `--preset` / `--pattern` | Create or replace a named preset in **led-driver** `presets.json` |
|
||||||
| `--default` | Startup preset name |
|
| `--default` | Startup preset name |
|
||||||
| `--transport` | `espnow` or `wifi` (`transport_type` on device) |
|
| `--transport` | `espnow` or `wifi` (`transport_type` on led-driver) |
|
||||||
|
| `--serial-baudrate` | Bridge UART1 baud for Pi serial link (e.g. `921600`; also sets GPIO UART pins) |
|
||||||
| `--ssid`, `--wifi-password`, `--wifi-channel` | Wi-Fi / channel fields for the driver |
|
| `--ssid`, `--wifi-password`, `--wifi-channel` | Wi-Fi / channel fields for the driver |
|
||||||
| `-r`, `--reset` | Reset the device |
|
| `-r`, `--reset` | Reset the device |
|
||||||
| `-f`, `--follow` | Follow serial output (optional timeout seconds) |
|
| `-f`, `--follow` | Follow serial output (optional timeout seconds) |
|
||||||
| `--pause` | Sleep N seconds (for chained actions) |
|
| `--pause` | Sleep N seconds (for chained actions) |
|
||||||
| `-u`, `--upload` | Recursive upload: `-u SRC [DEST]` (skips unchanged files via `file_hashes.json` on device) |
|
| `-u`, `--upload` | Recursive upload: `-u SRC [DEST]` (skips unchanged files via `file_hashes.json` on device) |
|
||||||
| `--src`, `--lib`, `--all` | Deploy led-driver trees to flash root, `patterns/`, and `lib/` |
|
| `--src`, `--lib`, `--all` | Deploy `src/` to device root and `lib/` to `/lib` (led-driver, espnow-sender, …) |
|
||||||
| `--force-upload` | Upload every file; ignore `file_hashes.json` |
|
| `--force-upload` | Upload every file; ignore `file_hashes.json` |
|
||||||
| `-e`, `--erase` | Erase everything at device root (including `settings.json` and `presets.json`) |
|
| `-e`, `--erase` | Erase everything at device root (including `settings.json` and `presets.json`) |
|
||||||
| `--rm` | Remove a path on the device |
|
| `--rm` | Remove a path on the device |
|
||||||
@@ -51,7 +52,7 @@ python web.py
|
|||||||
# open http://<host>:5000/editor
|
# open http://<host>:5000/editor
|
||||||
```
|
```
|
||||||
|
|
||||||
**Embedded in led-controller:** open **LED Tool** in the main UI, or visit **`/led-tool/editor`**.
|
**Embedded in led-controller:** open **Settings → LED Tool** in the main UI (Edit mode), or visit **`/led-tool/editor`**.
|
||||||
|
|
||||||
Legacy Flask form UI remains at **`/`** on port 5000; prefer **`/editor`** for Web Serial support.
|
Legacy Flask form UI remains at **`/`** on port 5000; prefer **`/editor`** for Web Serial support.
|
||||||
|
|
||||||
|
|||||||
25
cli.py
25
cli.py
@@ -150,7 +150,8 @@ _FLAGS_WITH_VALUE = frozenset({
|
|||||||
'-p', '--port', '-n', '--name', '--pin', '-b', '--brightness',
|
'-p', '--port', '-n', '--name', '--pin', '-b', '--brightness',
|
||||||
'-l', '--leds', '-d', '-debug', '--debug', '-o', '--order',
|
'-l', '--leds', '-d', '-debug', '--debug', '-o', '--order',
|
||||||
'--preset', '--pattern', '--default', '--transport', '--ssid',
|
'--preset', '--pattern', '--default', '--transport', '--ssid',
|
||||||
'--wifi-password', '--wifi-channel', '--src', '--lib', '--patterns', '--paterns',
|
'--wifi-password', '--wifi-channel', '--serial-baudrate',
|
||||||
|
'--src', '--lib', '--patterns', '--paterns',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -321,6 +322,9 @@ Examples:
|
|||||||
|
|
||||||
# Reset logical device name to firmware default (STA MAC based)
|
# Reset logical device name to firmware default (STA MAC based)
|
||||||
%(prog)s --reset-device-name
|
%(prog)s --reset-device-name
|
||||||
|
|
||||||
|
# ESP-NOW bridge: Pi on GPIO UART1 (USB-serial adapter)
|
||||||
|
%(prog)s -p /dev/ttyUSB0 --serial-baudrate 921600
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -413,6 +417,13 @@ Examples:
|
|||||||
help="led-driver transport_type",
|
help="led-driver transport_type",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--serial-baudrate",
|
||||||
|
type=int,
|
||||||
|
metavar="BAUD",
|
||||||
|
help="bridge: UART1 baud for Pi serial link (default 921600)",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ssid",
|
"--ssid",
|
||||||
help="led-driver ssid (Wi-Fi network in wifi mode)",
|
help="led-driver ssid (Wi-Fi network in wifi mode)",
|
||||||
@@ -747,6 +758,18 @@ Examples:
|
|||||||
if args.transport is not None:
|
if args.transport is not None:
|
||||||
edits["transport_type"] = args.transport
|
edits["transport_type"] = args.transport
|
||||||
|
|
||||||
|
if args.serial_baudrate is not None:
|
||||||
|
edits["uplink_transport"] = "serial"
|
||||||
|
edits["serial_usb"] = False
|
||||||
|
edits["serial_uart_id"] = 1
|
||||||
|
edits["serial_tx_pin"] = 2
|
||||||
|
edits["serial_rx_pin"] = 3
|
||||||
|
baud = int(args.serial_baudrate)
|
||||||
|
if baud < 9600 or baud > 3000000:
|
||||||
|
print("Error: --serial-baudrate must be 9600–3000000", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
edits["serial_baudrate"] = baud
|
||||||
|
|
||||||
if args.ssid is not None:
|
if args.ssid is not None:
|
||||||
edits["ssid"] = args.ssid
|
edits["ssid"] = args.ssid
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,14 @@
|
|||||||
<label for="wifi_channel">WiFi channel</label>
|
<label for="wifi_channel">WiFi channel</label>
|
||||||
<input id="wifi_channel" data-setting="wifi_channel" type="number" min="1" max="11" />
|
<input id="wifi_channel" data-setting="wifi_channel" type="number" min="1" max="11" />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="ap_ip">Bridge AP IP</label>
|
||||||
|
<input id="ap_ip" data-setting="ap_ip" type="text" placeholder="192.168.4.1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="ap_password">Bridge AP password</label>
|
||||||
|
<input id="ap_password" data-setting="ap_password" type="password" placeholder="min 8 chars, or empty for open" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="default">Default preset</label>
|
<label for="default">Default preset</label>
|
||||||
<input id="default" data-setting="default" type="text" />
|
<input id="default" data-setting="default" type="text" />
|
||||||
|
|||||||
69
static/web_serial_readuntil_test.mjs
Normal file
69
static/web_serial_readuntil_test.mjs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Node regression tests for Web Serial readUntil buffer handling.
|
||||||
|
* Run: node led-tool/static/web_serial_readuntil_test.mjs
|
||||||
|
*/
|
||||||
|
|
||||||
|
function bytesIndexOf(buf, suffix) {
|
||||||
|
if (!suffix.length) return 0;
|
||||||
|
if (buf.length < suffix.length) return -1;
|
||||||
|
for (let i = 0; i <= buf.length - suffix.length; i += 1) {
|
||||||
|
let ok = true;
|
||||||
|
for (let j = 0; j < suffix.length; j += 1) {
|
||||||
|
if (buf[i + j] !== suffix[j]) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readUntilConsume(rxBuf, suffixBytes) {
|
||||||
|
const idx = bytesIndexOf(rxBuf, suffixBytes);
|
||||||
|
if (idx < 0) return null;
|
||||||
|
const end = idx + suffixBytes.length;
|
||||||
|
const matched = rxBuf.slice(0, end);
|
||||||
|
rxBuf.splice(0, end);
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enc(s) {
|
||||||
|
return [...new TextEncoder().encode(s)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(cond, msg) {
|
||||||
|
if (!cond) throw new Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RAW = 'raw REPL; CTRL-B to exit\r\n';
|
||||||
|
const SOFT = 'soft reboot\r\n';
|
||||||
|
|
||||||
|
// Old bug: one chunk with soft reboot + banner; suffix-at-end + clear-all loses banner.
|
||||||
|
{
|
||||||
|
const rx = enc(`MPY: ${SOFT}${RAW}boot.py line\r\n`);
|
||||||
|
const soft = enc(SOFT);
|
||||||
|
const m1 = readUntilConsume(rx, soft);
|
||||||
|
assert(m1 !== null, 'soft reboot should match');
|
||||||
|
const banner = enc(RAW);
|
||||||
|
const m2 = readUntilConsume(rx, banner);
|
||||||
|
assert(m2 !== null, 'banner must remain in buffer after soft reboot match');
|
||||||
|
assert(rx.length > 0, 'boot.py tail should remain after banner');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Banner anywhere in buffer, not only at end.
|
||||||
|
{
|
||||||
|
const rx = enc(`noise${RAW}>>> `);
|
||||||
|
const banner = enc(RAW);
|
||||||
|
const m = readUntilConsume(rx, banner);
|
||||||
|
assert(m !== null, 'banner should match mid-buffer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// MPY: soft reboot marker
|
||||||
|
{
|
||||||
|
const rx = enc(`MPY: ${SOFT}`);
|
||||||
|
const soft = enc('MPY: soft reboot\r\n');
|
||||||
|
assert(readUntilConsume(rx, soft) !== null, 'MPY soft reboot marker');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('web_serial_readuntil_test: ok');
|
||||||
Reference in New Issue
Block a user