gabe /presentato-mf
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
acme-camera: boundaries, device-cloud, fleet management with bulk config, AI usage
caf8326 · Gabriel Morell · 2026-10-13 19:37
Files changed
modified
content/presentations/acme-camera.md
+144
−1
@@ -18,7 +18,7 @@ reveal_transition = "slide"
| 18 | 18 | |
| 19 | 19 | 1. Architecture: hardware and software |
| 20 | 20 | 2. Performance metrics and targets |
| 21 | −3. Instrumentation and fleet data | |
| 21 | +3. Instrumentation and fleet management | |
| 22 | 22 | 4. Risks, plan and questions |
| 23 | 23 | |
| 24 | 24 | Note: |
@@ -222,6 +222,22 @@ Each unit runs as its own user with ProtectSystem=strict and DeviceAllow for onl
| 222 | 222 | The broker checks the connecting uid (SO_PEERCRED) against per-topic ACLs, so portalctl can request config but never write it |
| 223 | 223 | A crashed daemon is restarted by systemd and counted in telemetry |
| 224 | 224 | |
| 225 | +-- | |
| 226 | + | |
| 227 | +## Boundaries | |
| 228 | + | |
| 229 | +| Boundary | Transport | | |
| 230 | +|---|---| | |
| 231 | +| process ↔ process | MQTT on a Unix socket | | |
| 232 | +| SOC ↔ MCU | UART + wake GPIO | | |
| 233 | +| device ↔ cloud | MQTT 5 over mutual TLS | | |
| 234 | +| clips → cloud | HTTPS to object storage | | |
| 235 | + | |
| 236 | +Note: | |
| 237 | +The MCU raises a GPIO to wake the SOC, then they talk COBS-framed messages over UART from one shared schema, so both sides agree on MotionDetectionMode and friends | |
| 238 | +mcuctl is the only process that touches the UART, everything else goes through the broker | |
| 239 | +Video never goes over MQTT, cameractl gets a pre-signed URL and uploads clips directly | |
| 240 | + | |
| 225 | 241 | --- |
| 226 | 242 | |
| 227 | 243 | # Boot Plan |
@@ -263,6 +279,49 @@ Factory reset wipes /data and revokes the device cert server side
| 263 | 279 | |
| 264 | 280 | --- |
| 265 | 281 | |
| 282 | +# Device ↔ Cloud | |
| 283 | + | |
| 284 | +One connection, held only while awake | |
| 285 | + | |
| 286 | +Note: | |
| 287 | +The device is asleep most of the time, so the cloud is designed around a device that is usually unreachable | |
| 288 | + | |
| 289 | +-- | |
| 290 | + | |
| 291 | +## Connection | |
| 292 | + | |
| 293 | +| Concern | Approach | | |
| 294 | +|---|---| | |
| 295 | +| Identity | per-device cert from the claim | | |
| 296 | +| Transport | MQTT 5 over mutual TLS | | |
| 297 | +| Reconnects | TLS session resumption | | |
| 298 | +| Commands | queued until the next wake | | |
| 299 | +| Config | device shadow, desired vs reported | | |
| 300 | +| Offline | buffer locally, send on reconnect | | |
| 301 | + | |
| 302 | +Note: | |
| 303 | +Session resumption skips most of the TLS handshake on every wake, which is time and energy on the event path | |
| 304 | +A persistent MQTT session holds commands for a sleeping device, it picks them up on its next wake or heartbeat | |
| 305 | +Live view needs the device reachable while asleep, which means Wi-Fi power-save on the SOC module or the keep-alive radio from the trade-offs, both cost standby current | |
| 306 | + | |
| 307 | +-- | |
| 308 | + | |
| 309 | +## Cloud Topics | |
| 310 | + | |
| 311 | +| Topic | Direction | Carries | | |
| 312 | +|---|---|---| | |
| 313 | +| `d/<id>/event` | up | motion, detections | | |
| 314 | +| `d/<id>/telemetry` | up | batched metrics | | |
| 315 | +| `d/<id>/shadow` | both | desired / reported config | | |
| 316 | +| `d/<id>/cmd` | down | live view, reboot, logs | | |
| 317 | +| `d/<id>/ota` | down | update offers | | |
| 318 | + | |
| 319 | +Note: | |
| 320 | +The device can only publish and subscribe under its own id, enforced by the cert | |
| 321 | +The shadow is the bridge to configctl: the cloud writes desired, configctl applies it and reports back what it actually runs | |
| 322 | + | |
| 323 | +--- | |
| 324 | + | |
| 266 | 325 | # Components |
| 267 | 326 | |
| 268 | 327 | | Component | Role | |
@@ -440,6 +499,62 @@ A crash spike in a new firmware cohort pauses the rollout automatically
| 440 | 499 | |
| 441 | 500 | --- |
| 442 | 501 | |
| 502 | +# Fleet Management | |
| 503 | + | |
| 504 | +Identity, config and updates for every device | |
| 505 | + | |
| 506 | +Note: | |
| 507 | +Two audiences use the same tools: Acme operating the whole fleet, and owners managing their own cameras | |
| 508 | + | |
| 509 | +-- | |
| 510 | + | |
| 511 | +## Lifecycle | |
| 512 | + | |
| 513 | +| Stage | What happens | | |
| 514 | +|---|---| | |
| 515 | +| Factory | keys in the secure element, cert registered | | |
| 516 | +| Claim | owner's app links the device to an account | | |
| 517 | +| Operate | shadow config, telemetry, alerts | | |
| 518 | +| Update | staged OTA by cohort | | |
| 519 | +| Retire | reset wipes `/data`, cert revoked | | |
| 520 | + | |
| 521 | +Note: | |
| 522 | +The claim token is the QR code on the device, it's useless without the device cert behind it | |
| 523 | +A resold camera goes through Retire then Claim, so the new owner never sees the old owner's data | |
| 524 | + | |
| 525 | +-- | |
| 526 | + | |
| 527 | +## Bulk Configuration | |
| 528 | + | |
| 529 | +- Owners group cameras by site or area | |
| 530 | +- Profiles: motion, light, power, processing | |
| 531 | +- Apply a profile to a group, override per camera | |
| 532 | +- Each camera reports when it has applied | |
| 533 | + | |
| 534 | +Note: | |
| 535 | +A profile write sets the desired shadow on every camera in the group, each one picks it up on its next wake or heartbeat | |
| 536 | +configctl validates it like any other change, so a profile can't push a camera into a state it doesn't support | |
| 537 | +The app shows pending / applied / rejected per camera, because sleeping devices apply changes over minutes, not instantly | |
| 538 | +Same mechanism Acme uses at fleet scale, e.g. changing a default for a firmware cohort | |
| 539 | + | |
| 540 | +-- | |
| 541 | + | |
| 542 | +## OTA Rollout | |
| 543 | + | |
| 544 | +| Stage | Devices | Gate | | |
| 545 | +|---|---|---| | |
| 546 | +| Internal | ~50 dogfood | a clean week | | |
| 547 | +| Canary | 1% | crashes, drain vs control | | |
| 548 | +| Staged | 10% → 50% | same, per cohort | | |
| 549 | +| Full | 100% | | | |
| 550 | + | |
| 551 | +Note: | |
| 552 | +Any cohort regressing on crash rate or battery drain pauses the rollout automatically | |
| 553 | +Devices only download above a battery threshold or while charging, and delta updates keep the bytes down | |
| 554 | +A/B slots mean a bad update rolls back on its own, the rollout gates are about not needing that | |
| 555 | + | |
| 556 | +--- | |
| 557 | + | |
| 443 | 558 | # Risks |
| 444 | 559 | |
| 445 | 560 | | Risk | Mitigation | |
@@ -921,3 +1036,31 @@ struct Detection {
| 921 | 1036 | |
| 922 | 1037 | Note: |
| 923 | 1038 | A detection above the threshold upgrades a motion event into a person / vehicle / package alert |
| 1039 | + | |
| 1040 | +--- | |
| 1041 | + | |
| 1042 | +# How I Used AI | |
| 1043 | + | |
| 1044 | +| Where | How | | |
| 1045 | +|---|---| | |
| 1046 | +| Slides | drafted text, tables and notes | | |
| 1047 | +| Types | sketched the Rust types per component | | |
| 1048 | +| Review | checked the deck against the brief | | |
| 1049 | +| Tooling | built and deployed the deck site | | |
| 1050 | + | |
| 1051 | +Note: | |
| 1052 | +Draft, edit to match what you actually did | |
| 1053 | +Tooling included the Diogramos embed frame mode and theme sync, the Forgejo CI and the Argo deploy | |
| 1054 | + | |
| 1055 | +-- | |
| 1056 | + | |
| 1057 | +## Practices | |
| 1058 | + | |
| 1059 | +- AI drafts, I decide the architecture | |
| 1060 | +- Numbers checked by hand: power math, part specs | |
| 1061 | +- Small steps, reviewed and committed as I go | |
| 1062 | +- Every slide rendered and checked | |
| 1063 | + | |
| 1064 | +Note: | |
| 1065 | +Draft, edit to match how you actually work | |
| 1066 | + |
Parents: 3b61cfd