Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions .github/workflows/_build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +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/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; 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
3 changes: 1 addition & 2 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +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
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ runtime = "v2.3.2"
repo = "pollen-robotics/microduck-policies"
version = "v1"

# The duck detector, the same way: trained in pollen-robotics/duck_detector, published on the Hub,
# and seeded onto a board by `scripts/seed-detector.sh` from this pin — a floor, moved past with
# `robotctl duck-detector update`. The model repo shares its name with the *dataset* repo; the robot only
# ever addresses the model (`huggingface.co/<repo>/resolve/…`, `api/models/<repo>`), and the
# dataset lives under `datasets/`, so the shared name cannot be confused on the wire.
[workspace.metadata.detector]
repo = "pollen-robotics/microduck-duck-detector"
version = "duck-v1"

# The prebuilt GStreamer plugins `mediad` needs, built in CI from pinned upstream sources at
# https://github.com/pollen-robotics/microduck-gst-plugins — `mpph264enc` (hardware H.264 through
# Rockchip MPP) and `webrtcsink`/`webrtcsrc`, neither of which exists in any Debian suite.
Expand Down
8 changes: 8 additions & 0 deletions btd/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,10 @@ fn permits(call: &proto::Call) -> bool {
// The transport is the gate here, not the credential.
PolicyFetch(_) | PolicyInstall(_) => true,

// The detector's set, by the same argument: a read that reaches the network, and an
// install whoever tapped it is standing next to.
DetectorCheck | DetectorInstall(_) => true,

// ── the account, which BLE is the right transport for ────────────────
//
// Signing the robot in to a Hugging Face account is what makes it reachable from outside
Expand Down Expand Up @@ -502,6 +506,8 @@ mod tests {
// the download, the shape gate at load, the clamps, the fall reflex.
proto::method::POLICY_INSTALL,
proto::method::POLICY_FETCH,
// Replacing the detector, by the same argument as the policy set.
proto::method::DETECTOR_INSTALL,
// Binding the robot to a Hugging Face account, and unbinding it. Provisioning,
// like the two below it and for the same reason: a robot out of a box has no
// network, so it has no console and no LAN to open one from, and this is the
Expand Down Expand Up @@ -688,6 +694,8 @@ mod tests {
query: "microduck".to_owned(),
}),
proto::Call::PolicyInstall(proto::PolicyInstallParams::default()),
proto::Call::DetectorCheck,
proto::Call::DetectorInstall(proto::PolicyInstallParams::default()),
] {
assert_eq!(
upstream_for(&call),
Expand Down
18 changes: 12 additions & 6 deletions deploy/robotd.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Installed to /etc/robot/robotd.toml. Mostly read once at startup and not watched, so
# mostly a change needs a restart — of the daemon that reads the section, which is not
# always robotd: [media] and [detect] are mediad's and [head_imu] is tofd's. The two
# always robotd: [media] and [duck_detector] are mediad's and [head_imu] is tofd's. The two
# exceptions: padd re-reads [pad_imu_head_control] a second after the file changes, and robotd
# re-reads [policy] when asked. `robotctl configure` knows which is which and offers it
# (docs/design/robotd-design.md §4.2).
Expand Down Expand Up @@ -335,20 +335,26 @@ mode = "walk"
# withdrew keeps sounding.
# hold_ms = 250

[detect]
[duck_detector]
# Finding other Microducks in the head camera. Read by `mediad`, not `robotd` — the frames are on
# `mediad`'s pipeline and perception belongs next to the sensor — but the switch lives here because
# this is the file `robotctl configure` edits and a robot has one place for its settings.
#
# **Off by default.** The model ships in the release (models/duck_detect.rknn for the NPU,
# models/duck_detect.onnx for the CPU), and a robot nobody has asked to look for ducks should not
# pay ~60 ms of work every half second for it.
# **Off by default.** A robot nobody has asked to look for ducks should not pay ~60 ms of work
# every half second for it.
# enabled = true

# Which model, and therefore which processor: a `.rknn` runs on the NPU, an `.onnx` on the CPU.
# Unset tries the release's own, NPU first — and falls through to the CPU on a board whose NPU is
# Unset means the installed set — /opt/robot/detector/current/duck_detect.rknn for the NPU,
# duck_detect.onnx for the CPU — NPU first, falling through to the CPU on a board whose NPU is
# switched off in its device tree, which is how Armbian ships the Radxa Zero 3. `sudo sh
# /opt/robot/daemon/current/scripts/setup-npu.sh` turns it on, and needs a reboot.
#
# That set comes from the Hub (pollen-robotics/microduck-duck-detector, trained in
# pollen-robotics/duck_detector), the way the official policy set does: the release's postinstall
# hook seeds it once, `robotctl duck-detector check` asks whether a newer one exists, and
# `sudo robotctl duck-detector update` installs it and restarts mediad onto it. A retrain is a tag
# there, not a daemon release. This key is for a model of your own.
# model = "/home/microduck/my_detector.rknn"

# Looks per second. **2 is a thermal limit, not a preference**: flat out this reaches 95 °C on a
Expand Down
5 changes: 4 additions & 1 deletion docs/design/policy-channel-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ updater design).

Fetching rather than copying is the point: it is the arrangement `setup-board.sh` already uses
for ONNX Runtime and `setup-gstreamer.sh` for the plugins, which are the other two things a board
needs and a release has no business carrying. The pin lives in `[workspace.metadata.policies]`
needs and a release has no business carrying. The duck detector followed the same road out of the
release — `scripts/seed-detector.sh`, `/opt/robot/detector/current`, `robotctl duck-detector
check/update`, a pin in `[workspace.metadata.detector]` — with a fixed file list in place of the
manifest, since its two files have fixed names (`docs/project/npu-bringup.md`). The pin lives in `[workspace.metadata.policies]`
and as literals in the script, with a test asserting they agree — `setup-gstreamer.sh`'s trap,
because a script that runs from inside a release cannot read the manifest.

Expand Down
26 changes: 26 additions & 0 deletions docs/project/npu-bringup.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ here as a quantised `.rknn`. First model, for reference: `yolo11n` at 320×320,
from three sessions, mAP50 0.976 on a held-out session — and 3.9 MB after INT8 quantisation, which
kept 2 of 2 detections at 95% box overlap against the float model on the desk.

## Where the model comes from

**The Hub, the way the policies do.** `duck_detector` publishes every run to
[`pollen-robotics/microduck-duck-detector`](https://huggingface.co/pollen-robotics/microduck-duck-detector)
— `duck_detect.rknn` for the NPU and `duck_detect.onnx` for the CPU fallback, at the repo root under
fixed names, one tag per run. Nothing in this repository carries the weights: `mediad` reads them
from `/opt/robot/detector/current`, and what fills that is

| | |
|---|---|
| `scripts/seed-detector.sh` | run by the release's postinstall hook; installs the pin in `[workspace.metadata.detector]` on a board that has nothing, and never touches a set it did not install |
| `robotctl duck-detector check` | what is installed against what the repo offers |
| `sudo robotctl duck-detector update [--version <tag>]` | installs a revision and restarts `mediad` onto it |

It is `seed-policies.sh` and `robotctl policy check/update` with a different root and a fixed file
list, served by the same `updaterd` calls (`detector.check`, `detector.install`), and
`docs/design/policy-channel-design.md` §9 has the reasoning that carries over: the pin is a floor,
nothing partial goes live, a retrain is a tag rather than a daemon release.

Two things worth knowing. The model repo **shares its name with the dataset repo**; the robot only
ever addresses the model (`…/resolve/<rev>/…`, `api/models/…`), and the dataset lives under
`datasets/`, so nothing on the robot can land on a frame by accident. And `update`'s "newest" is
decided by **version tags** (`v2` sorts above `v1`; a name like `experimental` never counts), so a
run meant for robots wants a `vN` tag — the first run was tagged `duck-v1`, which is what the pin
names and is fine to install by name, but is not a version `check` can rank.

## What is here

| | |
Expand Down
19 changes: 18 additions & 1 deletion docs/robot/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Three properties worth trusting:
daemon's own loader first, atomically (temp file + rename), and rejected with the reason.

Saving offers what the change actually needs, from the daemon that actually reads it: a restart
for most keys (`[media]` and `[detect]` are `mediad`'s, `[head_imu]` is `tofd`'s), a `robotd`
for most keys (`[media]` and `[duck_detector]` are `mediad`'s, `[head_imu]` is `tofd`'s), a `robotd`
*reload* for `[policy]` — the motors stay powered — and nothing at all for `[pad]` and
`[pad_imu_head_control]`, which `padd` picks up within a second. `sudo`, because the file
is root-owned — without it the editor opens read-only and says so on the first write.
Expand Down Expand Up @@ -222,6 +222,23 @@ name one — `--version v1` is how to go back. The robot returns to its home pos
slot and drives again, and **a slot you loaded yourself is left alone**, because it points
somewhere else entirely.

#### A newer duck detector

The model `mediad` finds other ducks with lives on the Hub the same way
(`pollen-robotics/microduck-duck-detector`) and versions on its own line:

```
robotctl duck-detector check
```

```
sudo robotctl duck-detector update
```

Same shape as the policy pair — `--version <tag>` names one, and `check` changes nothing. `update`
restarts `mediad`, which drops the console's video for a moment; whether the detector then runs at
all is `[duck_detector] enabled` in `robotctl configure`.

#### Trying your own file

No release, no file to edit, no restart:
Expand Down
Binary file removed duck-detect/models/duck_detect.onnx
Binary file not shown.
Binary file removed duck-detect/models/duck_detect.rknn
Binary file not shown.
Loading
Loading