← Streetlight back to all nodes
node · 2026-05-13 project
active project · a $40 toy robot · driven by an LLM

DASH.
A robot with a marker taped to it.

Laptop owns the BLE socket. Phone is the eyes. A vision model runs the OODA loop: see a frame, pick one verb, drive, look again — until the goal is done.

“Find the fridge and say hello.” — the one-prompt demo, typed live in front of a room · the north star
the machine · three boxes
laptop owns BLE · phone is eyes/voice · dash-pi runs the OODA loop desktop FastAPI :5174 dash-pi orchestrator → pi vision OODA · owns BLE LAPTOP eyes + voice duct-taped to the head PHONE-PWA DASH marker back · eraser front LAN HTTP / WS BLE · bleak-dash OODA: GET /snapshot → vision model picks ONE verb → POST /command → look again
current state · where the project sits
desktop FastAPI + PWA shipped dash-pi OODA wired smiley ▶ one-click launch letter-sim v1 shipped sim never calibrated to metal closed-loop move(0x23) unwired

The arc is a 3-part blog post: Part 1 system prompt on local llama 7B → Part 2 MCP (dashRobotMCP, 12 tools) → Part 3 (now): the letter machine — eraser front, marker back, O → numbers → alphabet, calibrate sim → reality. The simulator exists; the metal hasn't met it yet.

Field truth · 2026-05-18 runs (5 runs, 1 clean win)So what
turn 360 drew a clean ~25cm circle first trythe known-good stroke — the "O" already exists on metal
turn is open-loop timed, no encoder (dash_ble.py:214-232)90°/180° drift; closed-loop move(0x23) is encoded but not wired to /command — the precision unlock
forward drift direction flips run-to-run; backward retraces the last arcprobe with 5cm moves; snapshot before any forward > 10cm
all 4 board edges drop onto carpet/keyboardonce off-board, no software command recovers — refuse moves near edges
▶ the one next thing · when you sit down

Put the sim on metal. Tape marker (back) + eraser (front) to Dash, drive ONE real O (turn_right 360 is the proven stroke), photograph it, then turn the three knobs below until sim-O matches real-O. The open questions — pen mount, erase strategy, letter size, camera mount — get answered by doing this, not by deciding first.

knob 1 · wheelbase knob 2 · deg/s @ spin 200 knob 3 · signed forward-drift
jump in · the letter-sim, live

Same verbs as the robot (dash-pi/system.md: forward/backward 5–50cm, turn 10–180°), eraser-front/marker-back pen logic, the three calibration knobs. Standalone: letter-sim.html.

jump in · run the real robot

Spawns smiley/run.sh via the local-viewer's /api/run-script (auto-starts the desktop server on :5174). Only works through localhost:7373. Manual: cd nodes/robot-framework/smiley && bash run.sh · drive by hand: cd nodes/robot-framework/desktop && bash run.sh → phone on same wifi hits the LAN URL.

API contract · file map · prior art · hardware limits
POST /connect     -> scans, connects to first Dash found (handles unit-swap)
POST /command     {"cmd": str, "params": object} — forward, backward, turn_left,
                  turn_right, head_*, say, stop
GET  /snapshot    -> one webcam JPEG
GET  /telemetry · WS /ws -> live state
Path / repoWhat
desktop/server.py · dash_ble.pyFastAPI + bleak BLE driver, calibration constants
dash-pi/orchestrate.js · system.mdOODA orchestrator (spawns pi per turn) + verb schema
smiley/ · smiley/runs/2026-05-18-*/experiment-1 harness, per-run learnings, calibration-notes.md
android/parked Expo client (camera-on-head milestone)
dashRobotMCP · DriveTheDashWebApp · bleak-dash-Jakeprior art: the Part-2 MCP server, the turn-bug origin, the BLE driver fork

Hardware limits: no arbitrary TTS (15-char firmware asset names; route speech out laptop speakers) · 48 noises in constants.py, ~26 shipped · unused BLE verbs: eye-ring (100), synth (304), launcher (400/401), closed-loop move(0x23) · macOS: first Connect needs a one-time Bluetooth permission for your terminal.


nodes/robot-framework · streetlampBeta · super-node since 2026-05-13 · briefing refresh 2026-06-09