129 lines
5.6 KiB
Markdown
129 lines
5.6 KiB
Markdown
# Edge deployment (Raspberry Pi 5, air-gapped / 폐쇄망)
|
|
|
|
Offline-installable bundle + systemd unit for the `move-detect` edge daemon.
|
|
The bundle is fully self-contained (all wheels + pose model + source): the Pi
|
|
performs **zero network fetches** at install or run time. The only runtime
|
|
network use is the cloud POST / config-poll / heartbeat, and even that tolerates
|
|
being offline (events buffer in the local sqlite queue and flush on reconnect).
|
|
|
|
> Scope: this covers the **CPU MoveNet + USB (UVC) webcam** path — the one the
|
|
> daemon wires today (`edge/daemon.py:main`) and the one verified end-to-end.
|
|
> Hailo-8 acceleration (`edge/adapters/hailo_pose.py`) and the CSI camera
|
|
> (`edge/adapters/picamera_source.py`) are separate device-validated paths — see
|
|
> `docs/hardware-bringup.md`.
|
|
|
|
## Files
|
|
|
|
| File | Runs on | Purpose |
|
|
|------|---------|---------|
|
|
| `bundle/build_offline_bundle.sh` | build machine (has internet) | downloads wheels + model, stages source, emits one tarball |
|
|
| `bundle/install_offline.sh` | the Pi (no internet) | venv + `--no-index` install, systemd unit, service account |
|
|
| `systemd/move-detect-edge.service` | the Pi | the daemon unit (auto-restart, device access, hardening) |
|
|
| `systemd/move-detect-edge.env.example` | the Pi | env template → `/etc/move-detect/edge.env` |
|
|
|
|
## 1. Build the bundle (on a machine WITH internet)
|
|
|
|
**Most reliable: build on an aarch64 host** (a Pi 5, or an arm64 CI runner) so the
|
|
downloaded wheels match the target exactly:
|
|
|
|
```bash
|
|
deploy/bundle/build_offline_bundle.sh # native aarch64 wheels
|
|
```
|
|
|
|
From an x86 box you can attempt a cross build (best-effort — onnxruntime/opencv
|
|
wheel tags may not resolve; if it fails, build natively):
|
|
|
|
```bash
|
|
deploy/bundle/build_offline_bundle.sh --cross # target aarch64 / cp311
|
|
```
|
|
|
|
Output: `dist/move-detect-edge-bundle-aarch64-cp311.tar.gz` (+ `MANIFEST.txt`
|
|
listing the bundled wheels). This single file crosses the air gap.
|
|
|
|
## 2. Transfer + install (on the air-gapped Pi)
|
|
|
|
Copy the tarball across the gap (USB, one-way transfer, etc.), then:
|
|
|
|
```bash
|
|
tar xzf move-detect-edge-bundle-aarch64-cp311.tar.gz
|
|
cd move-detect-edge-bundle
|
|
sudo ./install_offline.sh
|
|
```
|
|
|
|
The installer: creates the `movedetect` service account (in `video`/`render`/
|
|
`plugdev`/`hailo` groups), lays down source + model under `/opt/move-detect`,
|
|
builds a venv and installs the bundled wheels with `--no-index`, verifies the
|
|
package imports, seeds `/etc/move-detect/edge.env`, and installs the systemd unit.
|
|
|
|
## 3. Configure + start
|
|
|
|
```bash
|
|
sudo nano /etc/move-detect/edge.env # DEVICE_ID, DEVICE_SECRET, CAFE24_URL, ...
|
|
sudo systemctl enable --now move-detect-edge
|
|
journalctl -u move-detect-edge -f
|
|
```
|
|
|
|
`DEVICE_ID`/`DEVICE_SECRET` must match a `devices` row in the cloud (see
|
|
`cloud/docs/cloud-deploy.md` §6). `MOVENET_ONNX` should point at the bundled
|
|
model (`/opt/move-detect/models/movenet_lightning.onnx`).
|
|
|
|
## 4. Verify the air gap (do this before trusting a build)
|
|
|
|
- [ ] On a host with **networking disabled**, `install_offline.sh` completes —
|
|
the `--no-index` installs prove nothing is fetched. If pip tries to reach
|
|
PyPI, a wheel is missing from the bundle: rebuild (step 1) natively.
|
|
- [ ] Step [4/6] of the installer prints `edge import OK` (onnxruntime + cv2 +
|
|
numpy + requests all import from the venv).
|
|
- [ ] `systemctl status move-detect-edge` is `active (running)`; the log shows
|
|
frames processed and (if the cloud is reachable) heartbeats.
|
|
- [ ] Pull the network cable → events keep queueing (`QUEUE_DB` grows) → restore
|
|
→ the queue flushes. (End-to-end behavior verified in the edge e2e harness.)
|
|
|
|
## Hailo-8 HAT + CSI camera path (optional, faster)
|
|
|
|
The default bundle runs CPU MoveNet + USB webcam. To use the Hailo-8 accelerator
|
|
and/or a CSI camera, add these SYSTEM packages on the Pi (they are apt packages, not
|
|
pip wheels — the venv sees them because it is created with `--system-site-packages`):
|
|
|
|
```bash
|
|
sudo apt install -y hailo-all python3-picamera2 python3-opencv
|
|
```
|
|
|
|
- `hailo-all` — HailoRT runtime + driver + the Python `hailo_platform` bindings.
|
|
- `python3-picamera2` — CSI camera stack (libcamera).
|
|
|
|
Then place the pose model and switch backends in `/etc/move-detect/edge.env`:
|
|
|
|
```ini
|
|
POSE_BACKEND=hailo
|
|
HAILO_HEF=/opt/move-detect/models/yolov8s_pose.hef # copy the compiled HEF here
|
|
CAMERA_BACKEND=csi
|
|
CSI_DEVICE=0
|
|
```
|
|
|
|
Validate on device per `docs/hardware-bringup.md` before trusting output (the Hailo
|
|
decode is unit-locked, but the HailoRT inference + picamera2 color order are hardware-
|
|
validated). USB webcam stays available anytime via `CAMERA_BACKEND=uvc`.
|
|
|
|
**Multiple cameras at once.** Set `CAMERAS=backend:device:camera_id,...` to run several
|
|
cameras in one daemon — each gets its own detection pipeline and `camera_id`, they share
|
|
the Hailo device (round-robin) and the offline queue, and cloud zones are routed to the
|
|
matching camera. Example — CSI + USB together:
|
|
|
|
```ini
|
|
CAMERAS=csi:0:cam-01,uvc:0:cam-02
|
|
```
|
|
|
|
Each `camera_id` must match the `camera_id` on that camera's zones in the cloud.
|
|
|
|
> **Air gap:** `hailo-all` / `python3-picamera2` are the ONE piece this bundle can't
|
|
> carry (Hailo's own driver + apt tree). Pre-install them on the Pi image, or transfer
|
|
> their `.deb`s separately, before running `install_offline.sh`. The pose HEF is
|
|
> likewise supplied out-of-band (compiled from the Hailo Model Zoo).
|
|
|
|
## Not bundled (by design)
|
|
|
|
- **MySQL / the cloud app** — a separate host; see `cloud/docs/cloud-deploy.md`.
|
|
- **HailoRT + `yolov8s_pose.hef` + picamera2** — Hailo's runtime/driver, the compiled
|
|
HEF, and the CSI camera stack are Pi system packages installed out-of-band (above);
|
|
the default CPU MoveNet + USB path needs none of them.
|