Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
30abfc7
Chore: Upgrade webpack 4 to 5
WinstonPoh Jan 25, 2026
82b013b
Chore: Fixing some errors in config
WinstonPoh Jan 29, 2026
91d0b76
Fix: Get it to build
WinstonPoh Jan 29, 2026
476870a
Fix: Resolve all eslint errors
WinstonPoh Jun 13, 2026
02693ce
Chore: Sync package-lock after installing webpack 5 deps
WinstonPoh Jun 13, 2026
4c4d3d5
Docs: Add Wi-Fi reliability audit engagement design spec
WinstonPoh Jun 13, 2026
ad09e68
Docs: Add Wi-Fi reliability audit implementation plan
WinstonPoh Jun 13, 2026
ec1bc49
Chore: Scaffold audit working area
WinstonPoh Jun 13, 2026
8eea971
Docs: Audit raw findings - four dimensions (58 findings)
WinstonPoh Jun 13, 2026
9268949
Docs: Firmware + adversarial cross-verification of P0/P1 findings
WinstonPoh Jun 13, 2026
f54faef
Docs: Wi-Fi connection reliability audit report (ranked findings + ro…
WinstonPoh Jun 13, 2026
5a98c7d
Docs: Record live transport finding - A350/V1.21.0 uses HTTP over Wi-Fi
WinstonPoh Jun 13, 2026
dd08235
Docs: Live HTTP status capture + SACP protocol reference
WinstonPoh Jun 13, 2026
bfacc53
Docs: Add HTTP-path Phase 2 fix plan (R4,R9,R20,R5,04-F9,R13,R6,R7,R10)
WinstonPoh Jun 13, 2026
9d0faaa
Fix: Don't kill HTTP status poller on every new client socket (R4/01-…
WinstonPoh Jun 13, 2026
356bb31
Fix: Report laser on/off from laserPower; stop coercing headStatus (R…
WinstonPoh Jun 13, 2026
359e377
Fix: Guard HTTP enclosure poll from wiping good redux state on error …
WinstonPoh Jun 13, 2026
19d0448
Fix: Emit homing start/finish in order on HTTP; always restore G54 (R…
WinstonPoh Jun 13, 2026
153d3e9
Fix: Include zero-valued axes when setting work origin (R13/02-F11)
WinstonPoh Jun 13, 2026
f0a11f8
Fix: Surface HTTP executeGcode failures; abort job start on failed pr…
WinstonPoh Jun 13, 2026
ddbc3b3
Fix: Always restore G54 after camera-aid G53; never leak machine-spac…
WinstonPoh Jun 13, 2026
b8a8d5b
Fix: Sequence Z on Go-To-Work-Origin to avoid diagonal bed descent (R…
WinstonPoh Jun 13, 2026
ec474fc
Fix: Confirm before Go-To-Work-Origin descends below current Z (R10/0…
WinstonPoh Jun 13, 2026
59de7c5
Fix: Treat HTTP 202/203 as non-fatal in executeGcode result (review f…
WinstonPoh Jun 13, 2026
ba6e2b0
Docs: Feature design specs - curved-surface laser etching + camera im…
WinstonPoh Jun 13, 2026
0971025
Fix: Don't break multi-line gcode on a failed/empty line; skip blanks…
WinstonPoh Jun 13, 2026
1ebc806
Fix: Stop mutating snapmaker-react-icon namespace for MainToolbarAbPo…
WinstonPoh Jun 20, 2026
fe58dd9
Feature: Fast homing option (opt-in) via M1028 feedrate bump
WinstonPoh Jun 20, 2026
1938f53
Feature: Diagonal (simultaneous) Go-To-Work-Origin toggle (opt-in)
WinstonPoh Jun 20, 2026
0f8f327
Improvement: Set Fast Home to per-axis max feedrate (X120 Y120 Z40)
WinstonPoh Jun 20, 2026
df83ce5
Docs: Real-time jog feasibility report
WinstonPoh Jun 20, 2026
32083fc
Docs: Real-time jog firmware project scoping + transport analysis
WinstonPoh Jun 20, 2026
923de1d
Docs: ESP32 real-time jog bridge design (Wi-Fi/TCP-WS -> UART, bypass…
WinstonPoh Jun 20, 2026
c7645a1
Improvement: Move fast-home / diagonal-origin toggles into confirmati…
WinstonPoh Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions docs/superpowers/audits/2026-06-13-wifi-connection-audit.md

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions docs/superpowers/audits/2026-06-20-realtime-jog-feasibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Real-Time Jog Feasibility — Snapmaker 2.0 (A350, 10W laser)

**Date:** 2026-06-20
**Question:** Can Luban do true press-and-hold continuous jog (move while held, stop promptly on release), and over which connection?
**Method:** read-only research across Luban, `Snapmaker2-Controller`, `Snapmaker2-Modules`, the SACP SDK, and the screen HTTP API. File:line evidence below.

## Bottom line

**True real-time hold-to-jog is NOT achievable on a stock Snapmaker 2.0 over either HTTP/Wi-Fi or USB-serial SACP — it requires a firmware change.** Neither the SACP protocol nor the HTTP screen API exposes a continuous-jog or a move-cancel command for X/Y/Z; every host-reachable move is a discrete, fire-and-wait absolute/relative move. The firmware already contains the exact primitive needed (an "infinity move + abort-and-resync" pair) **but it is wired only to the extruder (E) axis** and is not surfaced over either host transport.

- **Over HTTP (this machine's Wi-Fi path):** not possible; only streamed-small-step *emulation*, and even the stop isn't out-of-band.
- **Over USB-serial SACP:** also not true real-time, but the *emulation* is meaningfully better — lower latency and a prompt, position-safe `M410` stop.
- **With a firmware change:** yes — mirror the E-axis infinity-move/stop for X/Y/Z and expose it via SACP. The hardware/firmware can clearly do it; the capability just isn't exposed.

## Evidence

### Firmware (Snapmaker2-Controller)
- **Touchscreen XYZ jog is itself discrete + blocking.** `DoXYZMove` brackets each absolute/relative move in `planner.synchronize()` (queues one move, blocks until done, then ACKs) — `snapmaker/src/hmi/event_handler.cpp:932-980` (synchronize at :956, :971). There is no continuous XYZ jog opcode even for the screen.
- **The "move-while-held + stop" primitive exists — E axis only.**
- `MOTION_OPC_DO_E_INFINITY_MOVE` → `DoEInfinityMove` queues a 100000 mm E move ("move forever") — `event_handler.cpp:1041-1069`.
- `MOTION_OPC_STOP_AXES_MOVE` → `StopEMoves` → `stepper.e_moves_quick_stop_triggered()`, wait ≤300 ms, then **resync position from steppers** — `event_handler.cpp:1071-1103`.
- The stepper-ISR abort only drops the current block for E: `if (abort_e_moves) … TEST(axis_did_move, E_AXIS)` — `Marlin/src/module/stepper.cpp:1363-1383`. So this does **not** stop an XYZ move; it's E-only by construction.
- **`M410` quickstop IS position-safe here (no re-home).** `quickstop_stepper()` = `planner.quick_stop(); synchronize(); set_current_from_steppers_for_axis(ALL_AXES); sync_plan_position()` — `Marlin/src/Marlin.cpp:420-425`. It reads actual stepper counts and resyncs.
- **M410/M112 act mid-stream, ahead of the queue.** `EMERGENCY_PARSER` is disabled (`Configuration_adv.h:1232`), so `get_serial_commands` special-cases them at read time: `M112→kill()`, `M410→quickstop_stepper()` — `Marlin/src/gcode/queue.cpp:671-681`. So an M410 on the serial wire stops immediately (not after the queue drains). Over HTTP it has to ride the same queued POST path, so it is **not** out-of-band there.
- `QuickStopService` (`snapmaker/src/service/quick_stop.cpp`) is a heavier pause/stop-button/power-loss path with parking/retract — job-oriented, not a lightweight jog-cancel.

### SACP protocol
- Motion set `0x01` exposes only `0x34` (absolute/relative move, sent `isRTO=true`, awaits ACK — `SacpClient.ts:684-688`) and `0x35` (home). No "stop motion", "cancel move", "jog", or "continuous" verb. Only stop-like verbs are job-control `0xac`: `pausePrint 0x04`, `stopPrint 0x06` (heavy QuickStop park). The E-axis infinity/stop opcodes are SSTP/HMI-only and not in SACP.

### Marlin G-code
- No GRBL-style continuous jog: **no `$J`, no `0x85` jog-cancel** in this firmware. Stop levers are `M410` (quickstop, position-safe here), `M112` (kill — needs reset), `M0/M1` (pause), `M400` (wait). Jog must be emulated with `G91/G0/G90` discrete moves (what Luban does today).

### Connection / latency
- **HTTP** `/api/v1/execute_code` is discrete request/response, serial per line (`SstpHttpChannel.ts:394-454`); each jog = 3 POSTs (`G91`,`G0`,`G90`). No streaming/websocket/realtime jog endpoint on the screen API. M410 isn't out-of-band here.
- **USB-serial SACP** — same discrete move command, but lower latency and M410 can be injected mid-stream for a prompt, position-safe stop.
- **No realtime path to the machine** — the only socket.io is Luban-UI ↔ local Node server; the server still talks discrete HTTP/SACP to the controller.

## Current bug (independent of the above)
The keyboard jog uses Mousetrap, which fires on **OS key auto-repeat** — holding an arrow floods the planner with discrete buffered moves, causing the stutter and the **dangerous overrun after release** (the buffer keeps draining). `JogPadShortcut.tsx` binds `relativeMove` to arrow keydown with no repeat-guard and no one-in-flight limit. This must be fixed regardless of which jog approach is chosen.

## Options

| Option | Real-time? | Effort | Stop promptness | Notes |
|---|---|---|---|---|
| **A. Safety fix only** | No | S | n/a | Ignore key auto-repeat + one-move-in-flight. Removes the flood/overrun danger. Stays discrete (one bounded step per press). Should be done in all cases. |
| **B. Streamed small-step emulation** | Approx | M | ≤1 step overrun | Hold = short relative moves, one in flight; release = stop (optionally M410). Best over USB-serial (prompt, position-safe stop); laggier over HTTP. Closest non-firmware feel. Includes A. |
| **C. Firmware change (true real-time)** | **Yes** | L | Excellent | Add XYZ infinity-move + stop-axes opcodes mirroring `DoEInfinityMove`/`StopEMoves`, extend the ISR abort to XYZ, expose via SACP, build matching Luban client. Requires building/flashing Snapmaker2-Controller (USB) + new SACP wiring. The firmware already proves the pattern (E axis). |

## Recommendation
- **Always do A** (it's the safety fix for a real hazard).
- For the user's goal of true hold-to-move: only **C** delivers it, and it's a firmware project (feasible — the E-axis code is the template). **B** is the best you can get without touching firmware and is much better over USB-serial than HTTP.
89 changes: 89 additions & 0 deletions docs/superpowers/audits/SACP-protocol-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# SACP (Snapmaker A-Series Communication Protocol) — Reference Notes

**Purpose:** Notes for the deferred SACP findings (R1/R2/R3 and the SACP legs of the motion/origin findings). Your A350 on V1.21.0 uses **HTTP over Wi-Fi**, so SACP is currently exercised only over **USB-serial** (`SacpOverSerialPort`) and would matter if Luban's Wi-Fi SACP path were ever reached (Artisan/J1/Ray, or a future SM2 firmware that opens TCP 8888 / answers UDP 8889).

**Sources:** the protocol is **not** in `Snapmaker2-Controller` (the SACP server lives on the closed-source touchscreen). Everything here is reconstructed from Luban's client: the vendored SDK `@snapmaker/snapmaker-sacp-sdk@0.1.1` (`node_modules/.../dist/`) and `src/server/services/machine/sacp/SacpClient.ts`.

---

## Wire format

```
+--------+--------+--------+---------+-----+----------+----------+-----------+--------+---------+----------+
| SOF hi | SOF lo | len(LE u16) | ver | rcv | CRC8(0..5)| sndr | attr | seq(LE u16) | cmdSet | cmdId | payload… | chksum(LE u16) |
| 0xAA | 0x55 | 2 bytes | 0x01 | u8 | u8 | u8 | u8 | 2 bytes | u8 | u8 | N bytes | 2 bytes |
+--------+--------+-------------+------+-----+-----------+----------+-----------+-------------+--------+-------+----------+----------------+
offset: 0 1 2-3 4 5 6 7 8 9-10 11 12 13… last 2
```

- **SOF** = `0xAA55` (`Header.js writeSOF`). Header is **13 bytes** (`Header.byteLength`), CRC8 covers header bytes 0–5 (`calcCRC8`), payload checksum is `calcChecksum(buffer, 7, len-7)` written LE u16 (`Packet.toBuffer`).
- **length** field = `payload.byteLength + 8` (`Dispatcher.send`). Minimum on-wire packet = 15 bytes (13 header + 0 payload + 2 checksum); `Communication.send` rejects buffers `< 15` as `'invalid SACP packet'`.
- **receiverId / senderId** = `PeerId` enum: `LUBAN=0, CONTROLLER=1, SCREEN=2`. Luban→machine commands target `CONTROLLER` (default).
- **attribute** = `REQUEST=0` / `ACK=1`.
- **sequence** = shared uint16 counter, `seq = (seq+1) % 0xffff` per send (`Communication.getSequence`). Correlation key = `"<commandSet*256+commandId>-<sequence>"` (a.k.a. `businessId-sequence`).
- **Transports:** TCP (port 8888, stream — needs reassembly, see 04-F7), UDP (port 8889, datagram = whole packet), or serial. Discovery is UDP broadcast on 20054.

## Request / response / subscription model

- **Request → ACK:** `Dispatcher.send(commandSet, commandId, peerId, payload, isRTO?, sequence?)`. Resolves `{response, packet}` where `response = Response.fromBuffer(packet.payload)`; `response.result` (u8) `=== 0` means success.
- **Timeouts/retry:** only when `isRTO === true` (2 s timer, up to 2 resends, then resolves a **fabricated** `result=2` packet). **Default `isRTO=false` → no timeout, promise can pend forever** (audit 04-F2; only 7 of ~90 methods are RTO).
- **Subscriptions:** `Dispatcher.subscribe(commandSet, commandId, interval, callback)` registers an EventEmitter listener on the `businessId`; the machine then pushes periodic reports (attribute carries the subscription). Unmatched/late ACKs can be mis-delivered to subscription listeners (04-F6).

## Command-set map (extracted from `SacpClient.ts`)

Grouped by `commandSet`. Format `cmdId → method` (Luban-side name).

**`0x01` — System / Motion / Coordinates**
- `0x02` executeGcode · `0x05` (machine-close ack/0x06 machine-initiated close handler) · `0x06` wifiConnectionClose
- `0x10` logFeedbackLevel · `0x15` getNetworkConfiguration · `0x16` getNetworkStationState
- `0x20` getModuleInfo · `0x21` getMachineInfo · `0x22` getMachineSize
- `0x25` configureNetwork · `0x26` (network station) · `0x30` getCurrentCoordinateInfo · `0x31` updateCoordinate (set coordinate-system type: MACHINE/WORKSPACE)
- `0x32` setWorkOrigin · `0x34` **requestAbsoluteCooridateMove / moveAbsolutely / movementInstruction** (RTO) · `0x35` **requestHome** (no timeout — 04-F2) · `0x36` **home-complete report handler** (registered only in legacy heartbeat — 04-F3) · `0x3b` getEmergencyStopInfo · `0x48` setMotorPowerHoldMode
- subscriptions: coordinate info `0x01/0xa2`, heartbeat/status.

**`0x04` — Error reporting**
- `0x02` getErrorReports (RTO) · error-report push handler `0x04/0x00` (`registerErrorReportHandler`).

**`0x10` — FDM / extruder**
- `0x01` GetFDMInfo · `0x02`/`0x04` nozzle info sub/unsub · `0x05` SwitchExtruder(?) · `0x06` · `0x08` GetExtruderOffset · `0x09` ExtruderMovement · offset `0xa0/0x15` (SetExtruderOffset, RTO).

**`0x11` — CNC**
- `0x02` setCncPower · `0x03` setToolHeadSpeed · `0x05` switchCNC.

**`0x12` — Laser**
- `0x01` SetLaserPower · `0x02` SetBrightness · `0x03` SetFocalLength · `0x04` TemperatureProtect · `0x05`/`0x07` SetLaserLock / getLaserLockStatus · `0x0a`/`0x07` laserCalibration(Save) · `0x0d`/`0x0e` get/setFireSensorSensitivity · `0x10`/`0x11` get/setCrosshairOffset · `0x1a`? · laser-power-state subscription `0x12/0xa1`.

**`0x14` — Hot bed**
- `0x01` GetHotBed · `0x02` setHotBedTemperature · hotbed-temp subscription.

**`0x15` — Enclosure**
- `0x01` getEnclousreInfo · `0x02` setEnclosureLight · `0x03` setEnclosureDoorEnabled · `0x04` setEnclosureFan · enclosure-info / light-info subscriptions.

**`0x17` — Air purifier**
- `0x01` getAirPurifierInfo · `0x02` setPurifierSpeed · `0x03` setPurifierSwitch · purifier-info subscription.

**`0xa8` — File**
- `0x02` getGocdeFile · `0x03` laserCalibrationSave.

**`0xac` — Print/job control**
- `0x00` (batch buffer) · `0x03` startPrint (RTO) · `0x04` pausePrint (RTO) · `0x05` resumePrint (RTO) · `0x06` stopPrint (RTO) · `0x0e`/`0x0f` set work origin / coordinate (CoordinateInfo) · `0x1a` line-number / printing-time / progress subscriptions.

**`0xad` — Firmware upgrade**
- `0x00` upgradeFirmwareFromFile.

**`0xb0` — Wi-Fi connection / camera / file upload**
- `0x01` wifiConnection (handshake; explicit `isRTO=false`) · `0x02` subscribeHeartbeat · `0x0b` wifiConnectionHeartBeat (sent once per connect — 03 open-Q#4) · `0x00` uploadFile · `0x10` uploadFileCompressed · `0x0a` resumePrintForScreen
- camera (10W laser module): `0x03` getCameraCalibration · `0x04` takePhoto · `0x05` getPhoto · `0x06` getCalibrationPhoto · `0x07` setMatrix · `0x08` startScreenPrint · `0x09` getLaserMaterialThickness.

> Note `0x34` is reused by three encoders (`requestAbsoluteCooridateMove`, `moveAbsolutely`, `movementInstruction`) with **different payload layouts** — see audit 02-F12. The coordinate-type byte (MACHINE=0/WORKSPACE) is appended by `MovementInstruction.toArrayBuffer`; whether the closed server honors it is unverified (02-F1).

## Why the live UDP probe got no reply (and why HTTP wins anyway)

`SacpUdpChannel.test()` (and our faithful replica) sends a bare `getMachineInfo` (`0x01/0x21`) with **no prior `wifiConnection` (`0xb0/0x01`) handshake**. The A350/V1.21.0 screen did not answer on UDP 8889 (and refused TCP 8888). Even if SACP-UDP required a handshake first, **Luban's own detector also probes without one**, so it would still fall through to HTTP. Operationally: **this machine = HTTP over Wi-Fi.**

## Deferred SACP fixes (when/if SACP path is used — e.g. USB-serial)

- **R1 (04-F2):** add a default per-request timeout in the `SacpClient` wrapper; reject in-flight handlers on `dispose()`/socket close. Cheap, high value for any SACP transport.
- **R2 (01-F3 / 03-F8):** emit `ChannelEvent.Ready` unconditionally (decoded `machineIdentifier`) in `SacpTcpChannel`. Only matters if a machine opens TCP 8888.
- **R3 (01-F4 / 04-F3):** give `SM2Instance` the full subscription set + `0x01/0x36` home-complete handler + `connection:connected` + error-report handler. Matters for SM2 over SACP-serial/UDP.
- SACP legs of motion/origin: 02-F1 (MACHINE coord), 04-F10 (origin-setup swallow), 02-F12 (`0x34` encoder mismatch).
Empty file.
Loading