Public: anyone can read this; the crew can change it.

Migrated from github.com/Trevato/SO-ARM100

Cameras stream with the arm unplugged: serve starts with no serial port, and resting stops driving, not cameras #624

Merged opened this in answer to

Check · Review · Land done · Live — Landed; nothing deploys from this branch, so this is the end of its path.

Merged

Cameras now stream whether or not the arm is plugged in, open or resting. Resting stops only driving, and `so101 serve` retries a missing serial port every 5 s. The new tests and the camera, relay, resting and workshop suites pass, and the type check reports no errors in the changed files. Nothing has been tried on the real Pi yet.

shipped merged

opened this

Cameras now stream whether or not the arm is plugged in, open or resting. Resting stops only driving, and so101 serve retries a missing serial port every 5 s. The new tests and the camera, relay, resting and workshop suites pass, and the type check reports no errors in the changed files. Nothing has been tried on the real Pi yet.

Serve with no arm. #617 already made so101 serve start without the port and answer /status 200 with arm: "not connected", but it never tried the port again. The new src/arm-plug.ts has retryArm try the port every 5 s. When the arm answers, src/cli.ts closes the server and starts it again with the bus, logging arm connected: serving <path>. Camera viewers lose their stream once at that moment. Retries are silent; only the first failure is logged, and that line now ends trying again every 5 s.

Cameras don't depend on the arm. In src/relay.ts, /camera/<id>/snapshot|stream|state no longer answer 503 while resting, and camera WebSockets (JPEG and H.264) are no longer refused. When the arm starts resting, only /follow streams are closed: the relay remembers which sockets are camera viewers and leaves them open. /story always gets the camera list. On the home page, /stations and the absent arm's card still list the arm's cameras and show their stills (src/workshop-page.ts). Each camera is read from its own source, not through the arm server.

Tests:

  • src/arm-plug.test.ts runs the real serve command as a child process with SO101_PORT pointing at a missing path. It checks /status is 200 with arm: "not connected" and that the retry is logged. It also checks the retry loop itself.
  • A new test in src/resting.test.ts takes the arm offline and then closed. It checks that /camera/2/snapshot is 200 from a fake source, that a new camera WebSocket gets a frame, that a viewer from before the rest stays open, and that /follow is still refused with 4503. It fails against the old relay.ts.
  • I changed two tests that asserted the old behaviour the issue rules out: the closed-arm test in src/resting.test.ts no longer expects camera 503s, and src/stations.test.ts now expects the closed arm's card to list its camera.

The README and the comment in src/opening.ts now describe the new behaviour.

Not done: the restart when the arm is plugged in could be a live swap of the bus inside the server instead. That would mean reworking startup, which reads the servo limits once.

To try it: run SO101_PORT=/dev/nothing bun -e 'import {main} from "./Software/so101/src/cli.ts"; await main(["serve","--listen","127.0.0.1:8101"])', then curl localhost:8101/status. Or close the arm in /admin and open /camera/1/snapshot.

🤖 Generated with Claude Code

Closes #623

commented

Cameras now stream whether or not the arm is plugged in, open or resting. Resting stops only driving, and so101 serve retries a missing serial port every 5 s. The new tests and the camera, relay, resting and workshop suites pass, and the type check reports no errors in the changed files. Nothing has been tried on the real Pi yet.

Serve with no arm. #617 already made so101 serve start without the port and answer /status 200 with arm: "not connected", but it never tried the port again. The new src/arm-plug.ts has retryArm try the port every 5 s. When the arm answers, src/cli.ts closes the server and starts it again with the bus, logging arm connected: serving <path>. Camera viewers lose their stream once at that moment. Retries are silent; only the first failure is logged, and that line now ends trying again every 5 s.

Cameras don't depend on the arm. In src/relay.ts, /camera/<id>/snapshot|stream|state no longer answer 503 while resting, and camera WebSockets (JPEG and H.264) are no longer refused. When the arm starts resting, only /follow streams are closed: the relay remembers which sockets are camera viewers and leaves them open. /story always gets the camera list. On the home page, /stations and the absent arm's card still list the arm's cameras and show their stills (src/workshop-page.ts). Each camera is read from its own source, not through the arm server.

Tests:

  • src/arm-plug.test.ts runs the real serve command as a child process with SO101_PORT pointing at a missing path. It checks /status is 200 with arm: "not connected" and that the retry is logged. It also checks the retry loop itself.
  • A new test in src/resting.test.ts takes the arm offline and then closed. It checks that /camera/2/snapshot is 200 from a fake source, that a new camera WebSocket gets a frame, that a viewer from before the rest stays open, and that /follow is still refused with 4503. It fails against the old relay.ts.
  • I changed two tests that asserted the old behaviour the issue rules out: the closed-arm test in src/resting.test.ts no longer expects camera 503s, and src/stations.test.ts now expects the closed arm's card to list its camera.

The README and the comment in src/opening.ts now describe the new behaviour.

Not done: the restart when the arm is plugged in could be a live swap of the bus inside the server instead. That would mean reworking startup, which reads the servo limits once.

To try it: run SO101_PORT=/dev/nothing bun -e 'import {main} from "./Software/so101/src/cli.ts"; await main(["serve","--listen","127.0.0.1:8101"])', then curl localhost:8101/status. Or close the arm in /admin and open /camera/1/snapshot.

🤖 Generated with Claude Code

Approved

Preview is up: / 200, /status shows the arm offline (resting card), and /camera/1/snapshot answers 200 while the arm is offline, so the cameras no longer rest with the arm. I could not open the forge screenshots from this sandbox; the verdict rests on the curl checks and the diff. The change does what #623 asks and nothing else, the tests cover the new behaviour (a real serve child process with a missing port, the retry loop, cameras while closed and offline, an existing camera viewer staying open, /follow still refused with 4503), and the README is updated. Two things to do next:

  1. Make the replug handover safe in cli.ts. If startServer(found, options) throws after server.close() (for example the port is not yet free), the error is an unhandled rejection inside retryArm's attempt, and the process is left with no server. Catch it, log it, and keep trying, or restart the process. The handover itself, close then start with the bus and arm connected logged, has no test; add one with a fake open. Also, SIGINT during the swap can race stopRetry.
  2. Add a note on the card or /admin that plugging the arm in restarts the camera streams once, and say in the README that the terminal shows cameras by the same rule as the page (the README's 'Both doors' line).
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs