Dev #26

Open
overseer wants to merge 65 commits from dev into main
Owner

Update main

Update main
overseer added 49 commits 2026-06-05 11:03:44 -04:00
feat: add ESP8266 support + Akaso camera compatibility config
CI/CD / lint-and-typecheck (push) Failing after 0s
CI/CD / test (push) Has been skipped
CI/CD / build (push) Has been skipped
Build (Dev) / build (push) Failing after 8s
CI/CD / deploy (push) Has been skipped
324402f268
- Unified firmware for ESP32 (dual-STA) and ESP8266 (time-shared STA)
- ESP8266: alternates between GoPro AP and travel router per poll cycle
- PlatformIO dual-target: esp32dev + esp8266dev (d1_mini)
- camera_ip config field for Akaso/non-GoPro cameras
- LittleFS support for ESP8266 (replaces SPIFFS)
- Camera compatibility table (GoPro H3/H4, Akaso)
- LED polarity handled per-platform (ESP8266 active-low)

ESP8266 time-sharing adds ~4s latency per 30s cycle — invisible at poll rate.
feat: dual-board architecture — ESP8266 camera bridge + ESP32 MQTT bridge
Build (Dev) / build (push) Failing after 1s
CI/CD / lint-and-typecheck (push) Failing after 0s
CI/CD / test (push) Has been skipped
CI/CD / build (push) Has been skipped
CI/CD / deploy (push) Has been skipped
b3d4226b1c
Complete rewrite of firmware into two dedicated boards per camera node:

ESP8266 (Camera Bridge):
- Connects ONLY to GoPro AP — polls status, sends over UART
- Zero network switching, zero MQTT
- HTTP GET /bacpac/SH for status, start/stop
- JSON-per-line UART protocol to ESP32

ESP32 (MQTT Bridge):
- Connects ONLY to travel router — MQTT to Pi hub
- Reads status from ESP8266 over UART2 (RX16/TX17)
- Auto-registration, heartbeat, command forwarding
- Zero camera communication

UART Protocol: JSON-per-line at 115200 8N1
  ESP8266→ESP32: status/ack/pong/error
  ESP32→ESP8266: cmd (start_recording/stop_recording/ping)

Hardware updates:
- BOM now includes both boards (~4/node)
- 3D case has stacked dual-board compartment
- UART wire channel between board recesses
- Shared 3.3V power rail for both boards
feat: redesigned case — tripod-clip box for dual ESPs, USB power bank
Build (Dev) / build (push) Failing after 1s
CI/CD / lint-and-typecheck (push) Failing after 0s
CI/CD / test (push) Has been skipped
CI/CD / build (push) Has been skipped
CI/CD / deploy (push) Has been skipped
893574ee79
Replaced GoPro-sleeve case design with simpler stand-mounted box:
- Case clips to tripod leg/stand pole (20-35mm diameter)
- No camera sleeve needed — case sits on the stand
- Powered by standard USB power bank (off-the-shelf)
- Holds ESP8266 + ESP32 stacked with UART wiring
- Cable ports for USB in/out, LED window, ventilation

Simplified BOM: ~1/node (down from 4), no buck converters needed
feat: add interactive 3D case viewer (Three.js)
Build (Dev) / build (push) Failing after 0s
CI/CD / lint-and-typecheck (push) Failing after 0s
CI/CD / test (push) Has been skipped
CI/CD / build (push) Has been skipped
CI/CD / deploy (push) Has been skipped
f4bf37d6a3
Rotatable 3D render of the tripod-mounted dual-ESP case:
- Case body with rounded corners and lid
- Stacked ESP32 + ESP8266 boards inside
- LED indicator, USB port, ventilation slots
- Tripod pole with C-clamp mount
- USB cables, screws, chip details
- Drag to rotate, scroll to zoom
- Open in any browser
feat: add v3 hardware case and update hub network
CI/CD / lint-and-typecheck (pull_request) Failing after 14m12s
CI/CD / test (pull_request) Has been cancelled
CI/CD / build (pull_request) Has been cancelled
CI/CD / deploy (pull_request) Has been cancelled
c5cbeabd92
docs: align hardware and MQTT architecture notes
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 10m3s
CI/CD / build (pull_request) Failing after 4m58s
CI/CD / deploy (pull_request) Has been skipped
0e2e94a4cf
fix: make tripod case dovetail connector fit
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 8s
CI/CD / deploy (pull_request) Has been skipped
4c4368a79f
Merge pull request 'feat: add v3 hardware case and update hub network' (#6) from agent/hermes/remoterig-hardware-v3-network into dev
Build (Dev) / build (push) Failing after 9s
CI/CD / lint-and-typecheck (push) Successful in 9m29s
CI/CD / test (push) Successful in 9m27s
CI/CD / build (push) Failing after 4m50s
CI/CD / deploy (push) Has been skipped
1a8f67a392
Reviewed-on: #6
CUB-230: hub-side deduplication for offline buffering replay
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 6s
CI/CD / test (push) Successful in 6s
CI/CD / build (push) Failing after 8s
CI/CD / deploy (push) Has been skipped
fe193701ae
- Add dedup check in handleStatus using (camera_id, recorded_at) uniqueness
- Skip insert if duplicate detected - logs replayed entries
- Go mod: updated version to 1.19
feat: add camera node case v4 status panel CAD
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 4m49s
CI/CD / deploy (pull_request) Has been skipped
8c8d4e45e5
fix: seat camera node case preview lid
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 6s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
4487f0e0a4
fix: redo camera node case as upright enclosure
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 9m27s
CI/CD / build (pull_request) Failing after 4m51s
CI/CD / deploy (pull_request) Has been skipped
bbc6b1ea05
fix: harden camera API endpoints (CUB-234)
CI/CD / lint-and-typecheck (pull_request) Failing after 12m11s
CI/CD / test (pull_request) Has been cancelled
CI/CD / build (pull_request) Has been cancelled
CI/CD / deploy (pull_request) Has been cancelled
1f253283f8
- Add request validation: Content-Type check, body size limit (64KB)
- Add field length validation (camera_id: 64, friendly_name: 128, mode: 32, resolution: 32)
- Add FPS range validation (0-240)
- Add battery_pct range validation (0-100)
- Replace ad-hoc map[string]string errors with structured APIError {error, code, details}
- Fix isUniqueConstraintErr to catch both camera_id and mac_address constraint violations
- Fix MacAddress model field from string to *string for NULL handling
- Fix splitSQL to strip -- line comments before splitting (was causing migration failures with modernc.org/sqlite)
- Add 30 integration tests covering all endpoints
- All tests pass: ok github.com/cubecraft/remoterig/internal/api
fix: hub-side dedup for ESP32 offline status replay (CUB-239)
CI/CD / lint-and-typecheck (pull_request) Failing after 11m33s
CI/CD / test (pull_request) Has been cancelled
CI/CD / build (pull_request) Has been cancelled
CI/CD / deploy (pull_request) Has been cancelled
74c8697e57
- Add migration 002: UNIQUE index on status_logs(camera_id, recorded_at)
- Upgrade migration system to version-tracked (schema_version table)
- Prevents race-condition double-inserts that application-level COUNT(*) check cannot guard against
- Complements existing application-level dedup from CUB-230
fix: make camera case cutouts visible
CI/CD / lint-and-typecheck (pull_request) Successful in 9m27s
CI/CD / test (pull_request) Successful in 9m27s
CI/CD / build (pull_request) Failing after 4m49s
CI/CD / deploy (pull_request) Has been skipped
af68bfaa3a
fix: add camera node USB power ports
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
9a50d0c801
fix: make camera node strap loops vertical
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 4m50s
CI/CD / deploy (pull_request) Has been skipped
daeea9f2c9
CUB-229: Design camera auto-discovery and registration flow
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 8s
CI/CD / deploy (pull_request) Has been skipped
95c225e51b
fix: make rear strap brackets side-feed
CI/CD / lint-and-typecheck (pull_request) Successful in 8s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
d89f9dc20b
fix: add camera node IPEX antenna hole
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 8s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
f118b890f0
CUB-176: central hub frontend — camera grid, start/stop controls, history viewer
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 8s
CI/CD / deploy (pull_request) Has been skipped
dd5ffe9fba
- CameraCard: color-coded status (green/yellow/red), per-camera start/stop, battery bar, recording indicator
- HistoryViewer: modal dialog with 24h status log browsing per camera
- App: responsive grid (1-4 cols), Start/Stop All global buttons, SSE connection badge, live stats strip
- API service: aligned with backend endpoints (list, detail, start, stop)
- Types: added StatusLog, CameraDetail, CameraInfo, StartStopResponse
- All 23 tests pass, lint clean, TypeScript + Vite build clean
Merge pull request 'Add camera node case v4 status panel CAD' (#11) from agent/hermes/camera-node-case-v4-status-panel into dev
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 9m28s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
c913039362
Reviewed-on: #11
Reviewed-by: Joshua <joshua@cnjmail.com>
CUB-178: camera monitoring dashboard wireframes and design specs
CI/CD / lint-and-typecheck (pull_request) Failing after 13m42s
CI/CD / test (pull_request) Has been cancelled
CI/CD / build (pull_request) Has been cancelled
CI/CD / deploy (pull_request) Has been cancelled
56fe3d228a
Merge branch 'dev' into agent/dex/CUB-229-camera-auto-discovery
CI/CD / lint-and-typecheck (pull_request) Successful in 10s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
f669ec182a
Merge pull request 'CUB-229: Design camera auto-discovery and registration flow' (#14) from agent/dex/CUB-229-camera-auto-discovery into dev
CI/CD / lint-and-typecheck (push) Successful in 7s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
Build (Dev) / build (push) Failing after 5m14s
3e277349ed
Reviewed-on: #14
Reviewed-by: Joshua <joshua@cnjmail.com>
Merge branch 'dev' into agent/dex/CUB-234-harden-camera-endpoints
CI/CD / lint-and-typecheck (pull_request) Successful in 7s
CI/CD / test (pull_request) Successful in 8s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
81f168e8a4
Merge pull request 'fix: harden camera API endpoints (CUB-234)' (#12) from agent/dex/CUB-234-harden-camera-endpoints into dev
Build (Dev) / build (push) Failing after 11s
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 10s
CI/CD / deploy (push) Has been skipped
cc1b05a4e7
Reviewed-on: #12
Merge branch 'dev' into agent/dex/CUB-239-hub-dedup-replay
CI/CD / lint-and-typecheck (pull_request) Successful in 8s
CI/CD / test (pull_request) Successful in 9s
CI/CD / build (pull_request) Failing after 10s
CI/CD / deploy (pull_request) Has been skipped
c2670a9f33
Merge pull request 'fix: hub-side dedup for ESP32 offline status replay (CUB-239)' (#13) from agent/dex/CUB-239-hub-dedup-replay into dev
Build (Dev) / build (push) Failing after 11s
CI/CD / lint-and-typecheck (push) Successful in 9s
CI/CD / test (push) Successful in 9s
CI/CD / build (push) Failing after 11s
CI/CD / deploy (push) Has been skipped
7fcae17239
Reviewed-on: #13
Reviewed-by: Joshua <joshua@cnjmail.com>
Merge branch 'dev' into agent/rex/CUB-176-central-hub-frontend
CI/CD / lint-and-typecheck (pull_request) Successful in 8s
CI/CD / test (pull_request) Successful in 9m29s
CI/CD / build (pull_request) Failing after 4m49s
CI/CD / deploy (pull_request) Has been skipped
a31dc62a24
Merge pull request 'CUB-176: Central hub frontend — camera grid, start/stop controls, history viewer' (#15) from agent/rex/CUB-176-central-hub-frontend into dev
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Successful in 8s
CI/CD / build (push) Failing after 10s
CI/CD / deploy (push) Has been skipped
Build (Dev) / build (push) Failing after 5m1s
8165822e45
Reviewed-on: #15
Reviewed-by: Joshua <joshua@cnjmail.com>
Merge branch 'dev' into agent/sketch/CUB-178-camera-dashboard-mockups
CI/CD / lint-and-typecheck (pull_request) Successful in 8s
CI/CD / test (pull_request) Successful in 7s
CI/CD / build (pull_request) Failing after 9s
CI/CD / deploy (pull_request) Has been skipped
9accd34b50
Merge pull request 'CUB-178: camera monitoring dashboard wireframes and design specs' (#16) from agent/sketch/CUB-178-camera-dashboard-mockups into dev
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
a478f7d478
Reviewed-on: #16
Reviewed-by: Joshua <joshua@cnjmail.com>
The MCU changed from ESP32 Dev Board to a Seeed Studio XIAO ESP32-C6,
but the firmware still targeted esp32dev. Retarget it and fix the
build so it compiles and flashes for the C6.

platformio.ini:
- env esp32-mqtt -> seeed_xiao_esp32c6 on the pioarduino platform fork
  (mainline espressif32 lags the Arduino-core 3.x the C6 needs)
- add ARDUINO_USB_MODE=1 / ARDUINO_USB_CDC_ON_BOOT=1 for Serial over
  the C6 native USB
- fix build_src_filter ordering in BOTH envs: -<*.cpp> ran last and
  re-excluded the target, leaving setup()/loop() undefined at link

esp32-mqtt-bridge.cpp:
- UART Serial2 RX16/TX17 -> Serial1 RX=D7/TX=D6 (XIAO C6 mapping)
- status LED GPIO2 -> D1 (green channel of the RGB STAT LED)
- fix pre-existing ArduinoJson v7 / PubSubClient build errors
  (.c_str() on a const char*, String topic where const char* required)

Verified: builds clean and boots on hardware (native-USB serial banner
confirmed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
firmware: add OLED status panel to camera node (C6)
Build (Dev) / build (push) Failing after 12s
CI/CD / lint-and-typecheck (push) Successful in 9m31s
CI/CD / test (push) Successful in 9m27s
CI/CD / build (push) Failing after 4m49s
CI/CD / deploy (push) Has been skipped
996ef87dfd
Bring up the 1.3" SH1106 128x64 I2C OLED on the XIAO ESP32-C6
(D4/SDA, D5/SCL @ 0x3C) per the Notion wiring diagram.

- add U8g2 dependency to the seeed_xiao_esp32c6 env
- I2C bus scan at boot (logs responders to serial)
- boot splash + live status screen: camera id, IDLE/REC + session
  timer, battery (raw until calibrated) + video-remaining, hub link
  state (MQTT/wifi/offline), and camera reachability
- refresh runs at the top of loop() so the panel stays live even
  when WiFi/MQTT are down

Verified on hardware: I2C scan finds 0x3C, U8g2 begin ok, panel
shows clean readable text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
firmware: battery calibration + full RGB STAT LED (C6)
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Successful in 8s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
cefb7ef52c
Battery calibration:
- two-point linear cal (bat_raw_min->0%, bat_raw_max->100%) of the
  GoPro offset-57 raw byte, persisted in SPIFFS config
- publish battery_pct in MQTT status only when calibrated (omit
  otherwise, per MQTT_CONTRACT); OLED shows % when calibrated, raw
  until then
- set_battery_cal MQTT command: explicit {raw_min,raw_max} or
  capture-current {point:"full"|"empty"} for field calibration

RGB STAT LED:
- drive D0/D1/D2 (R/G/B) with health colors instead of the single
  green channel: red=offline, magenta=wifi-but-no-hub,
  yellow=hub-but-no-camera, green=healthy; blue during boot
- RGB_COMMON_ANODE polarity flag; this module is common-anode

Verified on hardware: boots, OLED ok, RGB shows correct colors
(blue->red on the bench).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
firmware: no-reflash config updates for ESP-01S + UART-OTA groundwork
Build (Dev) / build (push) Failing after 9s
CI/CD / lint-and-typecheck (push) Successful in 9m28s
CI/CD / test (push) Successful in 9m27s
CI/CD / build (push) Failing after 4m53s
CI/CD / deploy (push) Has been skipped
403e1d9edd
Updating the buried ESP-01S currently means a USB-UART adapter and a
GPIO0 jumper. Add a path to change its settings without reflashing, and
lay the groundwork for full firmware updates over the existing UART.

set_config (no reflash for settings):
- ESP-01S: add saveConfig() + a set_config command — updates GoPro
  SSID/password/IP and poll interval, persists to LittleFS, acks, and
  re-associates Wi-Fi if creds changed
- XIAO: forward an MQTT set_camera_config down to the ESP-01S over UART
  (hub -> MQTT -> XIAO -> UART -> ESP-01S/LittleFS)

UART-OTA groundwork ("XIAO as flasher"):
- reserve XIAO GPIOs ESP01_RST_PIN=D8, ESP01_PGM_PIN=D10 for driving the
  ESP-01S serial bootloader (not driven yet)
- docs/design/esp01s-uart-ota.md: full design (why Wi-Fi OTA doesn't fit
  the 1MB ESP-01S on the GoPro AP, bootloader entry, ROM flash protocol,
  HTTP-pull delivery, scope)
- hardware/README.md: fix stale ESP32-C3 -> XIAO ESP32-C6 wiring, add the
  two control lines (Notion wiring diagram updated to match)

Both firmwares build clean and are flashed; set_config round-trip needs
the broker to exercise end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
firmware: fix C6 filesystem provisioning (LittleFS) + ESP-01S env
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 7s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
f6a25fc324
The C6 never loaded its /config.json, so it fell back to defaults
(SSID RemoteRig, empty password) and couldn't join Wi-Fi. Two bugs:

- Data file was named esp32-config.json but the firmware reads
  /config.json → renamed to config.json.
- Firmware used SPIFFS while pioarduino's uploadfs builds a LittleFS
  image; the SPIFFS mount then reformatted it empty. Switch the C6 to
  LittleFS (matches the toolchain default and the ESP-01S).

Also:
- log loaded ssid/broker/camera_id on config load (not the password)
- platformio.ini: land the ESP-01S env retarget (board d1_mini ->
  esp01_1m, dout, upload_speed 115200) that was missed in 403e1d9
- committed config.json keeps wifi_password blank; the real value is
  flashed to the device, not stored in git

Verified: C6 loads config and associates (got a DHCP lease). MQTT to
the broker is a separate network issue (hub IP / subnet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
net: re-address hub network 10.60.1.0/24 -> 192.168.8.0/24
CI/CD / lint-and-typecheck (push) Successful in 9s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 1m21s
CI/CD / deploy (push) Has been skipped
Build (Dev) / build (push) Failing after 5m1s
b0062f1373
The project was designed around a 10.60.1.0/24 travel-router network,
but the actual RemoteRig router uses 192.168.8.0/24 (the C6 associates
and gets 192.168.8.x; hub confirmed at 192.168.8.56). Replace the
network prefix everywhere (last octet preserved; GoPro 10.5.5.1 left
alone).

- scripts/setup-pi.sh: static IP 192.168.8.56/24, gateway 192.168.8.1,
  deploy/health command examples updated
- esp32-mqtt-bridge.cpp: default mqtt_broker -> 192.168.8.56
- firmware/data/config.json: broker -> 192.168.8.56 (wifi_password kept
  blank in git; real value flashed to the device only)
- docs (CONTEXT, MQTT_CONTRACT, READMEs, wireframes): gateway/hub/DHCP
  and example IPs re-addressed for consistency

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
firmware: store real wifi_password in config.json
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Failing after 0s
CI/CD / build (push) Has been skipped
CI/CD / deploy (push) Has been skipped
f261fa0f55
Per maintainer decision: this is a private repo and the closed
travel-router Wi-Fi password is low-sensitivity, so keep the real
value in the tracked config for reproducible uploadfs provisioning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: pull-based deploy to the Pi via rolling dev release
Build (Dev) / build (push) Failing after 16s
CI/CD / lint-and-typecheck (push) Successful in 9m28s
CI/CD / test (push) Successful in 9m27s
CI/CD / build (push) Failing after 4m49s
CI/CD / deploy (push) Has been skipped
c2a05f9b7c
The Pi is on a closed travel-router LAN, so push-based deploy from a
runner can't reach it. Switch to pull: the runner builds + publishes,
the Pi fetches.

- build-dev.yaml: after the arm64 build, publish the binary + sha256 +
  version.txt to a rolling "dev" Gitea release (replaces the
  upload-artifact + repository_dispatch -> deploy-dev hop)
- remove deploy-dev.yaml (push/scp-based deploy no longer used)
- scripts/pi-update.sh: poll the dev release, verify sha256, install via
  deploy.sh (backup/restart/rollback); only updates when version changes
- scripts/remoterig-update.{service,timer}: run the updater every 5 min
- setup-pi.sh: install deploy.sh + pi-update.sh + update.env template +
  the updater timer; summary now reflects the pull flow
- README: document the pull-based CI/CD; fix stale GOARM=6 (Zero 2 W is
  arm64 on 64-bit OS / arm GOARM=7 on 32-bit)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: fix jq-install precedence in build-dev (could run apt install when jq present)
Build (Dev) / build (push) Failing after 9s
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Failing after 0s
CI/CD / build (push) Has been skipped
CI/CD / deploy (push) Has been skipped
d8ea71a295
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: build frontend into the go:embed path so the hub binary compiles
Build (Dev) / build (push) Failing after 17s
CI/CD / lint-and-typecheck (push) Successful in 7s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 10s
CI/CD / deploy (push) Has been skipped
8387a4208f
cmd/server/main.go has //go:embed all:src/dist (relative to cmd/server/),
but Vite built to repo-root dist/, so cmd/server/src/dist never existed and
every `go build` failed with "pattern all:src/dist: no matching files found".
The hub binary has never built in CI as a result.

- vite.config.ts: outDir -> cmd/server/src/dist (emptyOutDir)
- commit cmd/server/src/dist/index.html placeholder so the embed always has
  a file (real build overwrites it)
- .gitignore: scope dist ignore to /dist; ignore cmd/server/src/dist/* but
  keep the index.html placeholder (the prior !src/dist/index.html rule
  pointed at the wrong path)
- ci.yaml: upload artifact from the new output path

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: publish dev release via Node (runner has no curl/jq/sudo)
Build (Dev) / build (push) Failing after 9s
CI/CD / lint-and-typecheck (push) Successful in 7s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
a1456fe741
The build-dev publish step failed with exit 127 — the act runner image is
minimal (no curl, jq, or sudo; runs as root). Node is always present
(setup-node), so do the release publish in Node using built-in fetch/crypto
and FormData/Blob for the asset upload. No external tools needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: run workflows on the go-react runner image
Build (Dev) / build (push) Failing after 10s
CI/CD / lint-and-typecheck (push) Successful in 1m23s
CI/CD / test (push) Successful in 8s
CI/CD / build (push) Failing after 1m23s
CI/CD / deploy (push) Has been skipped
50e672e753
The workflows used runs-on: ubuntu-latest, which mapped to
docker.gitea.com/runner-images:ubuntu-latest — an image whose Node from
setup-node won't execute (exit 127) and which lacks curl/jq. The runner
already advertises a purpose-built "go-react" CI image; point the
workflows at it instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: use go-react image toolchains instead of setup-go/setup-node
Build (Dev) / build (push) Failing after 4s
CI/CD / lint-and-typecheck (push) Successful in 8s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 9s
CI/CD / deploy (push) Has been skipped
b1edabd3da
On the go-react runner image, npm/node/go already work (npm ci succeeds),
but the setup-go/setup-node actions install tool-cache binaries that can't
execute on this runner (node/22.22.3/x64: "cannot execute"), failing the
jobs in their post steps. Drop those actions and use the image's built-in
toolchains; also reduces flaky github.com action clones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: install Go via setup-go (go-react image has Node but not Go)
Build (Dev) / build (push) Successful in 11s
CI/CD / lint-and-typecheck (push) Successful in 7s
CI/CD / test (push) Successful in 7s
CI/CD / build (push) Failing after 8s
CI/CD / deploy (push) Has been skipped
35136cb9ad
build-dev failed with "go: command not found" — the go-react image ships
Node but no Go. Restore setup-go (its static Go binary runs on this runner,
unlike setup-node's dynamic Node), and keep Node from the image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: trim ci.yaml to frontend quality gates
CI / test (push) Successful in 6s
Build (Dev) / build (push) Successful in 11s
CI / lint-and-typecheck (push) Successful in 7s
CI / build (push) Failing after 0s
53ed73ff6c
lint/typecheck/test pass; the build job failed only on
actions/upload-artifact@v4, which Gitea Actions doesn't support. Drop the
artifact upload and the placeholder production deploy job (the real build +
deploy is build-dev.yaml's pull-based release). Keep lint, typecheck, test,
and a build compile-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: consolidate frontend gates into one job
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 11s
CI / quality (pull_request) Successful in 11s
4ba11cc945
The 3-job ci.yaml re-cloned actions/checkout from github.com per job, and
those clones intermittently fail with connection resets (build job died
there even though lint/typecheck/test passed). Collapse to a single job:
one checkout, then lint -> typecheck -> test -> build. Fewer github.com
clones, faster, less flaky.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 11:03:53 -04:00
Merge branch 'main' into dev
Build (Dev) / build (push) Successful in 10s
CI / quality (push) Successful in 10s
CI / quality (pull_request) Failing after 0s
4823b746ca
overseer added 2 commits 2026-06-05 11:48:17 -04:00
The service unit hard-defaulted to User=pi, but not every Pi has a 'pi'
user (e.g. this hub uses 'overseer') — systemd then fails with 217/USER.
Default SERVICE_USER to ${SUDO_USER:-pi} so the service + /opt/remoterig
ownership match the actual operator. Override with --service-user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: rename rolling release tag dev -> dev-latest
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 12s
CI / quality (pull_request) Failing after 0s
c6d812cca2
A release tag named "dev" collides with the dev branch, making refs
ambiguous ("refname 'dev' is ambiguous") and breaking git push/checkout.
Publish the rolling build to tag "dev-latest" instead; pi-update.sh pulls
from there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 11:57:17 -04:00
deploy: atomic binary replace (fix "Text file busy")
Build (Dev) / build (push) Successful in 10s
CI / quality (push) Successful in 10s
CI / quality (pull_request) Successful in 10s
9fc80a27c9
cp over /opt/remoterig/remoterig fails with "Text file busy" once the
service is running. Copy to a .new file and rename over the target
(works on a live binary), in both the deploy and rollback paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 12:14:03 -04:00
registration: self-assigned camera IDs (Option B) + tolerate clockless status
Build (Dev) / build (push) Failing after 16s
CI / quality (push) Failing after 0s
CI / quality (pull_request) Successful in 11s
7929d1d969
Auto-registration never completed: the firmware announced on the wrong
topic, the hub never replied, and an unregistered node couldn't receive a
reply anyway. Switch to self-assigned IDs:

firmware (esp32-mqtt-bridge.cpp):
- camera_id defaults to the device id (clientID, e.g. rig-86d978)
- always subscribe to <id>/command; announce on the contract topic
  remoterig/cameras/<id>/announce (was the unmatched announce-<id> form)
- drop the bogus numeric timestamp from status (node has no clock)

hub (subscriber.go):
- handleAnnounce registers new cameras under the node's self-assigned id
  (no cam-NNN, no registered reply)
- handleStatus tolerates an empty/invalid timestamp and stamps server-side
  (previously rejected the status outright)

docs/MQTT_CONTRACT.md updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 12:44:40 -04:00
ci: retry transient network errors when publishing the release
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 10s
CI / quality (pull_request) Successful in 10s
18db26c265
The publish step died with "fetch failed: ECONNRESET" mid-run, leaving a
half-created release (no version.txt asset → the Pi got 404s). Wrap the
Gitea API calls in a small retry (rfetch) so a flaky connection doesn't
leave the rolling release incomplete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 14:06:13 -04:00
docs: add root CONTEXT.md project working-context / decision log
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 11s
CI / quality (pull_request) Successful in 11s
5239346eaa
Living context + decision log for humans and LLMs: architecture, network,
repo workflow, hardware pin map, firmware behavior, pull-based CI/CD,
key decisions/gotchas, current status, and handy commands. Cross-links the
deeper docs/ references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 14:24:23 -04:00
hub: fix camera listing, heartbeat parse, and legacy-id migration
Build (Dev) / build (push) Successful in 10s
CI / quality (push) Successful in 10s
CI / quality (pull_request) Successful in 11s
e00c8dce85
Three bugs surfaced once the camera reported in:

- ListCameras LEFT JOIN returns NULL status columns for a camera with no
  status rows yet, which failed scanning into non-nullable int/time fields
  (recording_state, online, recorded_at) and emptied the whole list.
  COALESCE them (recorded_at falls back to the camera's created_at).
- handleHeartbeat rejected every heartbeat ("cannot unmarshal number into
  string") because the node sends a numeric millis() timestamp. The handler
  doesn't use it, so drop the Timestamp field and let it be ignored.
- handleAnnounce kept a stale cam-NNN row registered by MAC under the old
  (pre-self-id) scheme, so self-id status inserts hit a FOREIGN KEY error.
  When a MAC is known under a different id than the node's self-id, migrate:
  drop the old row and re-register under the self-id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 14:34:10 -04:00
hub: scan recorded_at via sql.NullTime in ListCameras
Build (Dev) / build (push) Successful in 1m13s
CI / quality (push) Successful in 12s
CI / quality (pull_request) Failing after 0s
8e6cd11d9c
modernc/sqlite returns a COALESCE() expression as a raw string (no column
type affinity), which can't scan into *time.Time. Drop the COALESCE on the
timestamp and scan the plain DATETIME column (which modernc returns as
time.Time) through sql.NullTime, so a camera with no status row yet lists
with a zero time instead of erroring out the whole list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 14:37:59 -04:00
docs: CONTEXT.md — mark camera pipeline end-to-end verified
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 11s
CI / quality (pull_request) Successful in 11s
f03dbb056d
Camera rig-86d978 registers + lists in the API/dashboard with status
ingested. Add decisions for modernc/sqlite datetime scanning and legacy
camera-id migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 15:21:17 -04:00
firmware(esp-01s): real GoPro Hero 3 status read (was the shutter bug)
Build (Dev) / build (push) Successful in 10s
CI / quality (push) Successful in 10s
CI / quality (pull_request) Successful in 10s
ee947485d1
Validated against a Hero 3 Silver:
- fetchStatus() now GETs the status endpoint /camera/se (was /bacpac/SH?p=%01,
  which *started recording* every poll), at the correct host 10.5.5.9.
- Read the response from the stream, not getString(): the blob is binary and
  starts with 0x00, which truncated the Arduino String to empty.
- Offsets: recording = byte 29 (confirmed by not-recording vs recording diff),
  battery_raw = byte 19 (drains with charge; calibrate on the hub),
  video_remaining = bytes 25-26 (provisional).
- Default config set to this camera (goprosilver-1 / 10.5.5.9); per-camera
  values can still be overridden at runtime via set_config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 19:45:32 -04:00
hub: default to kiosk mode (empty api_key) for the closed LAN
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 11s
CI / quality (pull_request) Successful in 10s
832dd7cbf2
The SPA doesn't send X-API-Key, so a non-empty api_key made the dashboard
401 and show no cameras. Default api_key to "" (no auth) for the closed
travel-router network, consistent with anonymous MQTT. Document the kiosk
decision, the GoPro Hero 3 protocol, and the gotcha that the pull updater
deploys only the binary (config.yaml must be changed on the Pi).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 20:14:16 -04:00
fix(dashboard): keep SSE alive + seed camera list via REST
Build (Dev) / build (push) Successful in 19s
CI / quality (push) Successful in 18s
CI / quality (pull_request) Successful in 16s
cb549a8803
The dashboard showed "No Cameras Connected" despite the API returning the
camera:
- middleware.Timeout + http.Server.WriteTimeout (10s) cancelled the
  long-lived /api/v1/events/stream every 10s, before any 30s status event
  could arrive — so the SSE-fed store never populated. Drop the global
  request timeout and set WriteTimeout=0 (closed-LAN kiosk).
- The SPA never seeded from GET /api/v1/cameras (SSE only pushes on change).
  Fetch the list once on mount and setCameras().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 20:22:09 -04:00
hub: emit battery_pct/video_remaining as null, not omitted
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 11s
CI / quality (pull_request) Successful in 11s
b1ed8cdb20
The SPA types these as number|null and null-checks them, but omitempty
dropped the field entirely when uncalibrated → undefined in JS → "NaN%".
Always serialize the field (null when unknown) so the card shows "N/A".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 20:28:28 -04:00
hub: actually send start/stop commands over MQTT
Build (Dev) / build (push) Successful in 11s
CI / quality (push) Successful in 11s
CI / quality (pull_request) Successful in 11s
d538dd3b70
The /cameras/{id}/start and /stop handlers only wrote a recording_events
row — they never published the command, so the camera never recorded.
Add Subscriber.PublishCommand (publishes {"command":...} to
remoterig/cameras/<id>/command, which the XIAO forwards to the ESP-01S),
thread a CommandPublisher into the recording handlers, and wire mqttSub in
via apiRouter. Tests pass nil (publish skipped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
overseer added 1 commit 2026-06-05 21:57:09 -04:00
docs: update CONTEXT.md — control-path wiring, dashboard, decisions 18-21
Build (Dev) / build (push) Successful in 13s
CI / quality (push) Successful in 12s
CI / quality (pull_request) Successful in 11s
7c07338707
- §11: dashboard now renders live (SSE/seed/kiosk); GoPro monitoring works;
  flag that camera CONTROL is blocked by the faulty XIAO->ESP command wire
  (status RX works, command TX doesn't). Dedupe the token/default-branch lines.
- §9: add decisions for the MQTT control path, SSE longevity + REST seed,
  nullable status JSON (NaN% fix), and UART being two independent wires.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Some checks are pending
Build (Dev) / build (push) Successful in 13s
CI / quality (push) Successful in 12s
CI / quality (pull_request) Successful in 11s
This pull request doesn't have enough required approvals yet. 0 of 1 approvals granted from users or teams on the allowlist.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin dev:dev
git checkout dev
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CubeCraft-Creations/remote-rig#26