Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
207 commits
Select commit Hold shift + click to select a range
4e84671
robotd --sim: the same daemon, with its body in MuJoCo
apirrone Aug 30, 2026
47f4003
The simulation design, with the two measurements that shaped it
apirrone Aug 30, 2026
59c2590
The twin stands up, and three ways to pick the wrong model
apirrone Aug 30, 2026
26347f6
scripts/duck-sim: a duck in MuJoCo, in one command
apirrone Aug 30, 2026
8bbf693
duck-sim: stop the duck it actually started, and say when it did not …
apirrone Aug 30, 2026
907a9e2
duck-sim: measure whether the duck stood up, not whether it is upright
apirrone Aug 30, 2026
3892b0f
duck-sim: hand the duck to the policy, do not ramp it to a pose
apirrone Aug 30, 2026
75c32d1
duck-sim boot: the duck as a machine you log into
apirrone Aug 30, 2026
e548766
robotctl robot enable, and a container you can actually work in
apirrone Aug 30, 2026
ed47bf8
duck-sim boot N: a container per duck, all in one world
apirrone Aug 30, 2026
21cc83c
duck-sim: enable every duck, and say how fast the world is running
apirrone Aug 30, 2026
3e2ab95
Give each duck its own body, in its own overlay
apirrone Aug 30, 2026
b527ce5
An apostrophe in a comment deleted the boot function
apirrone Aug 30, 2026
35917ba
A duck should not think it is a laptop
apirrone Aug 30, 2026
2581e62
duck-ether: two ducks in a room, singing together
apirrone Aug 30, 2026
831dd49
The ether reaches a duck in a container
apirrone Aug 30, 2026
2541794
duck-sim down killed a login session; a pid is not an identity
apirrone Aug 31, 2026
e897560
tofd --sim: depth from a simulated body
apirrone Aug 31, 2026
7612818
duck-sim ctl: pass every socket, not just robotd's
apirrone Aug 31, 2026
f96ebde
Every daemon's users, or its unit will not start
apirrone Aug 31, 2026
36e7115
Create the users during staging, because the unit that would is skipped
apirrone Aug 31, 2026
a5cfffc
tofd in a container needs one address family it never needs on a robot
apirrone Aug 31, 2026
dde9b7a
duck-sim: DUCK_SIM_SCENE, so the ducks can have a room
apirrone Aug 31, 2026
4744fd7
Give the containers a voice: aplay, and the host's speakers
apirrone Aug 31, 2026
01e6719
boot: always ask whether the rootfs is still the right one
apirrone Aug 31, 2026
751c852
The device robotd asks aplay for does not exist off a robot
apirrone Aug 31, 2026
b35c5a8
duck-ether: be a bad radio, and the chorale bug appears
apirrone Sep 1, 2026
c536bfd
Write down the reproduction, and what it does not prove
apirrone Sep 1, 2026
4681738
fix
apirrone Sep 2, 2026
ad92c07
mediad --sim-camera: the third source, and the duck can see
apirrone Sep 2, 2026
3810543
duck-sim: DUCK_SIM_CAMERAS, and a console per duck
apirrone Sep 2, 2026
0cd676d
--rotate becomes an override, and its default stays 90
apirrone Sep 2, 2026
bd732ba
btd, mediad: a restarted daemon gets the next request, not the one after
Nixxx19 Sep 2, 2026
3ea7729
A duck registers with the rendezvous service, which is half of reachi…
pierre-rouanet Sep 3, 2026
f2823b9
padd: Start once stands the robot up, Start again drives
RemiFabre Sep 3, 2026
7ddeb36
padd: Select press = robot.relax (torque off)
RemiFabre Sep 3, 2026
f732cad
robot.rebootMotors: reboot servos in place, `robotctl robot reboot-mo…
RemiFabre Sep 3, 2026
7c45b52
A clock every stream shares, and what a map needs from the robot (API…
Sep 6, 2026
40c1d0b
Merge remote-tracking branch 'origin/sim-remote-io' into vslam-timest…
apirrone Sep 6, 2026
8819ba8
mediad: socket-path flags, so the twin's control channel reaches its …
Sep 6, 2026
78e6833
robotd: claim the IPC socket before starting control
tianrking Sep 7, 2026
e72a489
robotd: cover concurrent startup and saturated listeners
tianrking Sep 7, 2026
b2158af
tofd: read the head IMU (BMI088) and serve it as imu.stream
Sep 7, 2026
648d7d2
route imu.stream: over WebRTC (mediad), refused on BLE (btd) and upda…
Sep 7, 2026
2b112cb
head IMU: unambiguous head_imu.* naming + publish the sensor mount pose
Sep 7, 2026
8088ac4
Signing a robot out has to reach the relay, or it does not mean anything
pierre-rouanet Sep 7, 2026
18eb409
Merge pull request #209 from pollen-robotics/relay-producer
pierre-rouanet Sep 7, 2026
8308ba1
proto+robotd: serve the head camera's intrinsics in robot.model (v25)
apirrone Sep 7, 2026
57bc41b
The console page learns a second way in, and §5 stops being open
pierre-rouanet Sep 7, 2026
8e7aaba
Merge remote-tracking branch 'origin/main' into relay-session
pierre-rouanet Sep 7, 2026
9da3905
proto+robotd: serve the whole skeleton's pose per tick (v26)
apirrone Sep 7, 2026
f46d9a0
The console signs in, and it took a Docker Space to do it
pierre-rouanet Sep 7, 2026
4eb40ef
The bridge carries a session refusal from a board to a browser
pierre-rouanet Sep 7, 2026
30b0f69
A remote peer gets a session, because the relay plays the consumer fo…
pierre-rouanet Sep 7, 2026
98381eb
`POST /send` answers with a message, and dropping it cost the session
pierre-rouanet Sep 7, 2026
b615fce
STUN on the remote console, and three answers written where they belong
pierre-rouanet Sep 7, 2026
8cd85cd
The robot offers a relay candidate, so a consumer behind a NAT can re…
pierre-rouanet Sep 7, 2026
4ef3aee
A credential cache is not a setting, and CI compiled what my laptop c…
pierre-rouanet Sep 7, 2026
4c6d8db
The TURN endpoint has no DNS, and our error was hiding why
pierre-rouanet Sep 7, 2026
bd0ba36
`media.video` says where the optical axis is, so a consumer can use t…
pierre-rouanet Sep 7, 2026
ca14a4a
A Python consumer gets frames from a duck, with nothing added on eith…
pierre-rouanet Sep 7, 2026
e1befe8
A Space that processes a duck's camera on Hugging Face hardware
pierre-rouanet Sep 7, 2026
b66b048
Keep bytecode out of the tree
pierre-rouanet Sep 7, 2026
6e80292
Merge pull request #219 from pollen-robotics/relay-session
pierre-rouanet Sep 7, 2026
c08f14b
Merge pull request #203 from pollen-robotics/reboot-motors
pierre-rouanet Sep 7, 2026
f9762b2
Merge main: Start's two presses and DPad-Right's reboot in one pad
pierre-rouanet Sep 7, 2026
d3997f8
Merge main: DPad-Right reboots the servos, Select drops torque
pierre-rouanet Sep 7, 2026
034c1a5
robotd: hold the endpoint lock throughout standalone init
tianrking Sep 7, 2026
4eb5ec5
Merge pull request #212 from pollen-robotics/select-relax
pierre-rouanet Sep 7, 2026
7c3fe03
Merge main: Select's torque off and Start's two presses in one pad
pierre-rouanet Sep 7, 2026
9697804
Merge pull request #218 from tianrking/fix/robotd-single-instance
pierre-rouanet Sep 7, 2026
b6b9f16
Merge pull request #210 from pollen-robotics/start-twice
pierre-rouanet Sep 7, 2026
562d330
Merge pull request #207 from Nixxx19/a-restarted-daemon-gets-the-next…
pierre-rouanet Sep 7, 2026
a7870dd
Prepare release 0.11.0
pierre-rouanet Sep 7, 2026
5984efb
Merge pull request #222 from pollen-robotics/prepare-release-0.11.0
pierre-rouanet Sep 7, 2026
8ac21f9
robotd: refuse a non-socket path before bind, on every platform
Nixxx19 Sep 7, 2026
159a315
Support explicit-state LSTM ONNX policies in the robot runtime
Vottivott Sep 7, 2026
ac466dd
tofd: bmi088 from its tagged release, not a sibling checkout
apirrone Sep 7, 2026
995715f
Merge main into vslam (0.11.0: remote access, reboot-motors, select-r…
apirrone Sep 7, 2026
598cdf1
Camera intrinsics come from media.video, not robot.model (API v25)
apirrone Sep 7, 2026
9f7783c
The alpha family's camera calibration is the [media.intrinsics] default
apirrone Sep 7, 2026
00f61f8
rustfmt over this branch's files
apirrone Sep 7, 2026
1ab6f20
docs: how to use the simulated duck, linked from the README and the i…
apirrone Sep 8, 2026
2bfc06b
docs: up or boot, side by side, and when to pick which
apirrone Sep 8, 2026
1dcce68
the head IMU is Linux-only, and a Mac builds the workspace again
pierre-rouanet Sep 8, 2026
b3ccb98
duck-sim runs on a Mac, and `down` stops every duck
pierre-rouanet Sep 8, 2026
b04339b
the camera flags go with the cameras
pierre-rouanet Sep 8, 2026
78a9e58
the viewer needs mjpython, and the container half says so first
pierre-rouanet Sep 8, 2026
a4d0042
Merge pull request #235 from pollen-robotics/the-head-imu-is-linux-only
pierre-rouanet Sep 8, 2026
2b9e875
Address PR #232 review (Pierre Rouanet)
apirrone Sep 8, 2026
0e4e221
The alpha family calibration is None until a robot is solved in the s…
apirrone Sep 8, 2026
43d87ef
Restore the alpha family calibration -- 1920x1080@30 is the full 62 d…
apirrone Sep 8, 2026
ed6576c
mediad camera: the nominal model is the full 62 deg field, not a 39 d…
apirrone Sep 8, 2026
5e8213e
Merge pull request #232 from pollen-robotics/vslam
apirrone Sep 8, 2026
250234d
A Space that puts a policy from the Hub onto a duck in one click
pierre-rouanet Sep 7, 2026
246457e
A second way in, because the click needs a candidate pair and TURN ha…
pierre-rouanet Sep 7, 2026
5c52a8b
The camera was streamed and unchecked, which is not the same as working
pierre-rouanet Sep 7, 2026
a18891c
The 401 was Gradio's mock token, and the log now says so without aski…
pierre-rouanet Sep 7, 2026
1171fba
A venv beside the Space, so `uv run app.py` is the command it looked …
pierre-rouanet Sep 7, 2026
716ad4d
The page says which tab a Space can serve, because that is not guessable
pierre-rouanet Sep 7, 2026
cf0a476
The robot dials a Space and pushes H.264, because a relay candidate i…
pierre-rouanet Sep 8, 2026
e7dda09
Two Spaces speak one protocol, so there is one copy of the client
pierre-rouanet Sep 8, 2026
4e3506b
vision-demo receives the frames instead of pulling them, and six find…
pierre-rouanet Sep 8, 2026
889e9e2
policy-shop: an empty picture box says why, and the geometry is asked…
pierre-rouanet Sep 8, 2026
2f02e88
rustfmt and clippy over this branch's files
pierre-rouanet Sep 8, 2026
815feb9
Take the mount angle from where `main` resolves it, not from the flag
pierre-rouanet Sep 8, 2026
7d1f63d
The linux-only encoders go before the test module, not after it
pierre-rouanet Sep 8, 2026
5620aa2
mediad: publish the MuJoCo twin's camera intrinsics (self-describing …
apirrone Sep 8, 2026
f4704ab
robotd: drop non-finite command targets instead of poisoning the comm…
hadelan Sep 2, 2026
9bd7f24
kinematics: an empty band is no hand even when min_zones is 0
hadelan Sep 2, 2026
59c39d3
The valve is the valve, not the queue in front of it
pierre-rouanet Sep 9, 2026
28c4b17
The frame poll follows the stream, so an idle page costs the Space no…
pierre-rouanet Sep 9, 2026
f2bda50
`duckctl logs`: a daemon's last words, over the radio
pierre-rouanet Sep 9, 2026
6c309f0
mediad: answer one raw frame on a local socket (media.frame)
larai-w Sep 9, 2026
3658fa0
Adopt a factory-fresh servo as the one joint that stopped answering
apirrone Sep 9, 2026
59998e1
btd: size a reply for the link, and stop calling a full queue a disco…
pierre-rouanet Sep 9, 2026
aca90a1
Take the Hugging Face login from hf-robot-account rather than carryin…
pierre-rouanet Sep 9, 2026
686b3de
Merge pull request #243 from pollen-robotics/motor-replacement
apirrone Sep 9, 2026
fa8bf0f
robotd: one line a minute for an ordinary dropped bus read, not one a…
pierre-rouanet Sep 9, 2026
e251b8c
Depend on hf-robot-account from crates.io rather than from a commit
pierre-rouanet Sep 9, 2026
f7d05bd
system.logs: say where the daemon restarted
pierre-rouanet Sep 9, 2026
40cc5f4
Merge pull request #240 from pollen-robotics/duckctl-logs
pierre-rouanet Sep 9, 2026
a927000
Merge pull request #244 from pollen-robotics/use-hf-robot-account
pierre-rouanet Sep 9, 2026
08b8c40
Merge main: the account token now comes from `hf-robot-account`
pierre-rouanet Sep 9, 2026
16141d8
configd: a classic (BR/EDR) pad is paired before it is connected
apirrone Sep 9, 2026
94adbbd
Merge pull request #236 from pollen-robotics/policy-space
pierre-rouanet Sep 9, 2026
30d7d09
Merge pull request #238 from hadelan/fix/nonfinite-command-targets
pierre-rouanet Sep 9, 2026
5b8ee31
Merge pull request #239 from hadelan/fix/empty-band-min-zones
pierre-rouanet Sep 9, 2026
2767d32
configd: a pad that presents as two devices is one candidate, and the…
apirrone Sep 9, 2026
4d287d1
The theremin ships off, like the chorale
pierre-rouanet Sep 9, 2026
5ed271a
configd: a name-only gamepad match never ends the search early
apirrone Sep 9, 2026
3477301
configd: the classic pairing path has now run on hardware, and the do…
apirrone Sep 9, 2026
93b7e21
Spec: the head sensors when nobody is looking
pierre-rouanet Sep 9, 2026
ca16e30
Merge pull request #247 from pollen-robotics/theremin-off-by-default
pierre-rouanet Sep 9, 2026
510394e
The board says it is the IMU, not the laser
pierre-rouanet Sep 9, 2026
e2438d2
setup-board: classic gamepads go through the kernel's hidp, not bluet…
apirrone Sep 9, 2026
cfb6461
Merge pull request #248 from pollen-robotics/classic-pad-pairing
apirrone Sep 9, 2026
c71961c
A head-IMU sample is two I²C reads, not three
pierre-rouanet Sep 9, 2026
9ab1737
The pad's IMU: tapped by padd, drawn live in robotctl monitor
apirrone Sep 9, 2026
6944e8f
The head IMU is off until something reads it
pierre-rouanet Sep 9, 2026
0d0e9f7
pad imu: one zoom for every attitude, framed on the pad's bounding sp…
apirrone Sep 9, 2026
0646197
pad imu: one report per kernel read, not per sample
apirrone Sep 9, 2026
b60bd64
Merge pull request #250 from pollen-robotics/head-imu-off-by-default
pierre-rouanet Sep 9, 2026
e1b96eb
proto: the measured cost of the IMU stream, after batching
apirrone Sep 9, 2026
65c904a
Controller-IMU head control: Y hands the head to the pad's tilt
apirrone Sep 9, 2026
a6ebe6b
imu head: pitch and roll signs as the robot showed them
apirrone Sep 9, 2026
dfa2dec
Merge pull request #252 from pollen-robotics/pad-imu
apirrone Sep 9, 2026
bb324e9
Merge pull request #253 from pollen-robotics/imu-head-control
apirrone Sep 9, 2026
0f7ec57
duckctl ssh: the address and the login as one step
apirrone Sep 9, 2026
e5d140c
padd: Select stops on release, and a two-second hold is the whole shu…
apirrone Sep 9, 2026
78239bd
Merge pull request #254 from pollen-robotics/duckctl-ssh
apirrone Sep 9, 2026
86a1700
cargo fmt
apirrone Sep 9, 2026
233502e
Merge remote-tracking branch 'origin/main' into select-shutdown
apirrone Sep 9, 2026
d7b697e
cargo fmt after merging main
apirrone Sep 9, 2026
ad38eee
Merge pull request #255 from pollen-robotics/select-shutdown
apirrone Sep 9, 2026
c87d47b
Merge upstream main for media.frame review follow-up
larai-w Sep 9, 2026
291bd90
Expose fresh camera snapshots through robotctl and HTTP
larai-w Sep 9, 2026
69ccd5f
Merge pull request #249 from pollen-robotics/tof-on-demand-spec
pierre-rouanet Sep 10, 2026
8a4d224
configure: `[head_imu]` restarts tofd, not robotd
pierre-rouanet Sep 10, 2026
b0ed653
Pin bmi088 to the released v0.1.2, not the PR branch rev
pierre-rouanet Sep 10, 2026
54bb524
Merge pull request #251 from pollen-robotics/head-imu-two-reads
pierre-rouanet Sep 10, 2026
94bdb45
configure: restart, reload, or nothing — per key
pierre-rouanet Sep 10, 2026
06304ba
Merge pull request #256 from pollen-robotics/head-imu-restarts-tofd
pierre-rouanet Sep 10, 2026
ee282ae
Prepare release 0.12.0
pierre-rouanet Sep 10, 2026
213a5ce
Merge pull request #257 from pollen-robotics/prepare-release-0.12.0
pierre-rouanet Sep 10, 2026
1fa3ff7
Merge pull request #231 from Vottivott/feat/recurrent-onnx-policies
apirrone Sep 10, 2026
291799e
mediad(turn): the relay endpoint is the Space, not the alias in front…
pierre-rouanet Sep 10, 2026
d924f4f
Take apt off the boot path
apirrone Sep 10, 2026
afc8e9b
Rename [imu_head] to [pad_imu_head_control]
apirrone Sep 10, 2026
fb2d8a8
The test pattern was drawn at the camera's resolution
pierre-rouanet Sep 10, 2026
50164b8
`[media] camera` is now `source`, because false did not mean off
pierre-rouanet Sep 10, 2026
68244c9
Merge pull request #259 from pollen-robotics/quiet-boot-apt
apirrone Sep 10, 2026
4f795a6
Merge pull request #264 from pollen-robotics/rename-imu-head-config
apirrone Sep 10, 2026
9e35e03
configure: ctrl+f fuzzy search, and edit (auto) keys from their resol…
apirrone Sep 10, 2026
d9ef963
configure: fuzzy-match the key name only, verbatim everywhere else
apirrone Sep 10, 2026
e597877
Merge pull request #265 from pollen-robotics/configure-fuzzy-search
apirrone Sep 10, 2026
c0479c1
The duck detector comes from the Hub, the way the policies do
apirrone Sep 10, 2026
c49fcd8
Rename [detect] to [duck_detector], and robotctl detect to duck-detector
apirrone Sep 10, 2026
3d97b45
The valved H.264 sink held the whole pipeline in PAUSED, starving the…
apirrone Sep 10, 2026
089269e
Merge pull request #261 from pollen-robotics/cheap-test-pattern
pierre-rouanet Sep 10, 2026
8bfe92f
Merge pull request #268 from pollen-robotics/raw-branch-starved
apirrone Sep 10, 2026
35edb84
Merge remote-tracking branch 'origin/main' into detector-from-hub
apirrone Sep 10, 2026
006de19
ci: shellcheck seed-detector.sh with the other release scripts
apirrone Sep 10, 2026
773a67d
duckctl: scp, with `:path` for the robot
pierre-rouanet Sep 10, 2026
50de863
Merge pull request #267 from pollen-robotics/detector-from-hub
apirrone Sep 10, 2026
6507d2e
Merge pull request #269 from pollen-robotics/duckctl-scp
pierre-rouanet Sep 10, 2026
1e0a34a
padd: an unreadable sibling does not end the IMU search
hadelan Sep 12, 2026
8fb2268
tof: back off before reopening the head IMU after a read failure
hadelan Sep 12, 2026
8f47f4f
padd: only the driving pad's buttons may act
hadelan Sep 12, 2026
09fd7e9
odometry: anchor sets sampled on the alpha sole mesh
apirrone Sep 13, 2026
dd0f8c2
odometry: ALPHA16 is the default anchor set
apirrone Sep 13, 2026
e9cca62
Merge pull request #278 from pollen-robotics/odom-anchor-sets
apirrone Sep 13, 2026
ba2e417
policies: velstand is the default walk, set v5, the pin is a minimum
apirrone Sep 14, 2026
f8ba931
Merge pull request #279 from pollen-robotics/velstand-default
apirrone Sep 14, 2026
9aed222
console: offer a relay of this page's own, for a consumer with no IPv4
pierre-rouanet Sep 14, 2026
f92f9a1
ci: ask daily whether the relay endpoint still answers
pierre-rouanet Sep 14, 2026
fe44838
Merge pull request #226 from Nixxx19/refuse-a-non-socket-path-before-…
pierre-rouanet Sep 14, 2026
b9b6ef2
Merge pull request #277 from hadelan/padd-one-pad-drives
pierre-rouanet Sep 14, 2026
68f984c
Merge pull request #274 from hadelan/padd-imu-sibling-search
pierre-rouanet Sep 14, 2026
6927cc1
Merge pull request #258 from pollen-robotics/live-turn-endpoint
pierre-rouanet Sep 14, 2026
4d40c85
Merge pull request #280 from pollen-robotics/console-offers-its-own-r…
pierre-rouanet Sep 14, 2026
2dd630a
docs: WebRTC is the default transport, and a FAQ that says so
pierre-rouanet Sep 14, 2026
8cd3645
Merge pull request #281 from pollen-robotics/webrtc-first-and-a-faq
pierre-rouanet Sep 14, 2026
61a746b
Merge pull request #276 from hadelan/tof-imu-reopen-backoff
pierre-rouanet Sep 14, 2026
a180788
Merge remote-tracking branch 'origin/main' into feat/media-frame
pierre-rouanet Sep 14, 2026
7856145
media.frame: carry the mount angle, and serve an upright PNG
pierre-rouanet Sep 14, 2026
f23ad80
mediad: pass the mount angle at the two test call sites that miss it
pierre-rouanet Sep 14, 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
4 changes: 2 additions & 2 deletions .github/workflows/_build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,14 @@ jobs:
--include "robotd/systemd/robotd.service=systemd/robotd.service" \
--include "hooks/postinstall=hooks/postinstall" \
--include "scripts/setup-gstreamer.sh=scripts/setup-gstreamer.sh" \
--include "duck-detect/models/duck_detect.rknn=models/duck_detect.rknn" \
--include "duck-detect/models/duck_detect.onnx=models/duck_detect.onnx" \
--include "scripts/setup-npu.sh=scripts/setup-npu.sh" \
--include "deploy/overlays/rk3568-npu-enable.dts=deploy/overlays/rk3568-npu-enable.dts" \
--include "scripts/setup-rkaiq.sh=scripts/setup-rkaiq.sh" \
--include "scripts/rkaiq-modinfo-shim.c=scripts/rkaiq-modinfo-shim.c" \
--include "scripts/setup-login.sh=scripts/setup-login.sh" \
--include "scripts/setup-quiet-boot.sh=scripts/setup-quiet-boot.sh" \
--include "scripts/seed-policies.sh=scripts/seed-policies.sh" \
--include "scripts/seed-detector.sh=scripts/seed-detector.sh" \
--include "scripts/robot-rescue=scripts/robot-rescue" \
--include "scripts/robot-boot-check=scripts/robot-boot-check" \
--include "updater/systemd/robot-boot-check.service=systemd/robot-boot-check.service" \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
# a bashism would work on a dev box and fail on the board.
- name: Lint the installer
run: |
for script in scripts/install.sh scripts/setup-board.sh scripts/setup-gstreamer.sh scripts/setup-login.sh scripts/migrate-network.sh scripts/provision.sh scripts/provision-board.sh scripts/ci-release-notes.sh scripts/robot-rescue scripts/dev-push.sh scripts/pad-link-test.sh scripts/pad-stack-report.sh scripts/seed-policies.sh; do
for script in scripts/install.sh scripts/setup-board.sh scripts/setup-gstreamer.sh scripts/setup-login.sh scripts/setup-quiet-boot.sh scripts/migrate-network.sh scripts/provision.sh scripts/provision-board.sh scripts/ci-release-notes.sh scripts/robot-rescue scripts/dev-push.sh scripts/pad-link-test.sh scripts/pad-stack-report.sh scripts/seed-policies.sh scripts/seed-detector.sh; do
sh -n "$script"
shellcheck --shell=sh "$script"
# The one-liner is only correct if the file is executable and self-contained.
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ jobs:
--include "robotd/systemd/robotd.service=systemd/robotd.service" \
--include "hooks/postinstall=hooks/postinstall" \
--include "scripts/setup-gstreamer.sh=scripts/setup-gstreamer.sh" \
--include "duck-detect/models/duck_detect.rknn=models/duck_detect.rknn" \
--include "duck-detect/models/duck_detect.onnx=models/duck_detect.onnx" \
--include "scripts/setup-npu.sh=scripts/setup-npu.sh" \
--include "deploy/overlays/rk3568-npu-enable.dts=deploy/overlays/rk3568-npu-enable.dts" \
--include "scripts/setup-rkaiq.sh=scripts/setup-rkaiq.sh" \
--include "scripts/rkaiq-modinfo-shim.c=scripts/rkaiq-modinfo-shim.c" \
--include "scripts/setup-login.sh=scripts/setup-login.sh" \
--include "scripts/setup-quiet-boot.sh=scripts/setup-quiet-boot.sh" \
--include "scripts/seed-policies.sh=scripts/seed-policies.sh" \
--include "scripts/seed-detector.sh=scripts/seed-detector.sh" \
--include "scripts/robot-rescue=scripts/robot-rescue" \
--include "scripts/robot-boot-check=scripts/robot-boot-check" \
--include "updater/systemd/robot-boot-check.service=systemd/robot-boot-check.service" \
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/turn-endpoint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Does the relay credentials endpoint still answer?
#
# `mediad::turn` fetches short-lived Cloudflare credentials so a robot can offer a `relay`
# candidate to a consumer that cannot reach it directly. The endpoint it used before
# 2026-09 had been dead since June — a dangling Route53 delegation — and **nothing
# noticed for three months**, because the only symptom is a warning in a journal nobody
# reads and a candidate type nobody counts. Every other check in this repository passes
# regardless: they pair two peers on one network, which never looks at a relay.
#
# So this is the one check that would have caught it, and it is deliberately *not* on
# `pull_request`. The failure being guarded against is "nobody touched this for months",
# which a PR trigger cannot see; and a third party's outage must never block work that has
# nothing to do with it. A scheduled failure mails whoever owns the repository, which is
# the right blast radius for "an upstream service went away".
name: turn-endpoint

on:
schedule:
# Daily. The endpoint is somebody else's Space, and a week of relay coverage lost
# before anybody hears about it is most of the damage already done.
- cron: "41 5 * * *"
workflow_dispatch:

jobs:
credentials:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# **Read the URL out of the source rather than repeating it here.** A check with its
# own copy of the endpoint tests whatever it was last told, which can drift from what
# the daemon compiles in — and a green check on an endpoint no robot uses is worse
# than no check, because it reads as proof.
- name: Ask the endpoint the daemon uses
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
set -euo pipefail

url=$(sed -n 's/^pub const DEFAULT_TURN_ENDPOINT: &str = "\(.*\)";$/\1/p' \
mediad/src/turn.rs)
[ -n "$url" ] || {
echo "::error::could not read DEFAULT_TURN_ENDPOINT from mediad/src/turn.rs"
exit 1
}
echo "endpoint: $url"

# No token means this cannot check anything. Failing is the point: a check that
# skips itself into permanent silence is the failure mode this workflow exists
# to end.
[ -n "${HF_TOKEN:-}" ] || {
echo "::error::no HF_TOKEN secret. Add a Hugging Face token with no scopes" \
"beyond sign-in; it is used only to mint TURN credentials."
exit 1
}

code=$(curl -sS -o body.json -w '%{http_code}' --max-time 30 \
-H "Authorization: Bearer $HF_TOKEN" "$url?ttl=600")
[ "$code" = "200" ] || {
echo "::error::$url answered HTTP $code, not 200"
head -c 400 body.json || true
exit 1
}

# A 200 carrying no relay is still a robot with no relay candidate to offer —
# which is the outage, not a milder version of it. `stun:` entries do not count:
# `webrtcbin` takes a STUN server through its own property, and this is about the
# thing STUN cannot do.
relays=$(jq '[.iceServers[]? | .urls] | flatten
| map(select(startswith("turn:") or startswith("turns:")))
| length' body.json)
echo "relay servers offered: $relays"
[ "$relays" -gt 0 ] || {
echo "::error::$url answered 200 with no turn:/turns: entries"
head -c 400 body.json || true
exit 1
}
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ secret*

# Playground and scratch state (see updater/examples/playground.rs).
/verify

# Python bytecode from the demo Space, which is source rather than a build here.
__pycache__/

# A venv beside a Space's source, for running one locally. `uv venv` writes its own `.gitignore`
# holding `*` so it self-ignores; this is for the person who reaches for `python -m venv`, which
# does not, and it is the same accident `__pycache__` above was added for.
.venv/
41 changes: 41 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Working in this repository

`CONTRIBUTING.md` is the reference: building, testing, layout, conventions, releasing. This page
is the short list of things that are easy to get backwards, and where the answer lives when they
are not here.

## Docs own mechanisms; one page each

`docs/README.md` assigns every mechanism to one design doc. When a fact belongs to a page listed
there, every other page says one sentence and links. When two pages disagree, the one that does
not own the mechanism is the bug — and when behaviour and a design doc disagree, the doc is the
bug. [`docs/faq.md`](docs/faq.md) is the task-shaped front door for someone building against a
robot rather than changing it.

## A consumer uses WebRTC. `media.stream` is the fallback

The robot publishes H.264 over WebRTC, and that is the default for anything consuming a duck's
camera — it is encrypted end to end, it carries the control channel on the same session, and it
has a return path. It works from a data centre because the robot offers a relay candidate
(`remote-access-design.md` §6).

`media.stream` — the robot dialling an outbound WebSocket and pushing frames to you — is the
fallback for a **program** consuming **frames only** on a **long-running** stream, where relay
metering is the thing that matters. It has no return path and no control channel.

This is worth stating because the repository reads the other way round if you only follow the
code: `media.stream` was built when the relay endpoint was dead and WebRTC genuinely could not
connect from a data centre, so its module doc argues its own case at length. That endpoint is
fixed. Do not conclude from the volume of prose that it is the preferred path.

## Never design around a version difference

One user, one robot. An old component's limits are a question to raise, not something to route
around — bump `API_VERSION` and name the install consequence. A version skew is logged and served,
never refused; only a genuinely missing route or an unknown parameter may refuse.

## Releases are how a fix reaches a robot

`main` being fixed is not a robot being fixed. Robots on the stable channel move when a release is
cut, and a dev build from a branch is superseded by the next `daemon-dev-main` the board's
six-hourly check finds. `docs/design/updater-design.md` owns the mechanism.
1 change: 1 addition & 0 deletions CLAUDE.md
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ hooks/ preinstall · postinstall — what runs inside an update, from t
scripts/ provision-board.sh · dev-push.sh + dev-build.Dockerfile (from your machine) ·
provision.sh → setup-board.sh → setup-gstreamer.sh · setup-rkaiq.sh ·
migrate-network.sh · install.sh (on the board) ·
setup-login.sh · setup-quiet-boot.sh (install.sh and postinstall both run these) ·
robot-boot-check · robot-rescue (recovery, installed to /usr/local/sbin) ·
pad-link-test.sh · pad-stack-report.sh (gamepad radio, on the board) ·
board-test.sh · systemd-test.sh (CI) · cross-sysroot.sh (cross-builds) ·
Expand Down
Loading
Loading