Skip to content

feat(go3s): Insta360 Go3S adapter with BLE trigger + WiFi aggregation - #11

Merged
styu12 merged 29 commits into
mainfrom
feat/insta360-go3s-adapter
Apr 14, 2026
Merged

feat(go3s): Insta360 Go3S adapter with BLE trigger + WiFi aggregation#11
styu12 merged 29 commits into
mainfrom
feat/insta360-go3s-adapter

Conversation

@styu12

@styu12 styu12 commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an Insta360 Go3S camera adapter to syncfield-python. Wireless start/stop via BLE, background WiFi/OSC file download after recording, viewer UI for status + retry.

  • Backend: Go3SStream adapter (StreamBase subclass) that drives BLE start/stop and enqueues a per-episode aggregation job. Background queue runs on a dedicated daemon thread + asyncio loop; atomic per-camera + per-episode semantics with selective retry. SDK-restart crash recovery via aggregation.json scan.
  • Multihost-aware: leader/follower roles auto-downgrade to on_demand policy so lab WiFi (mDNS) isn't broken mid-session.
  • Viewer: StandaloneRecorderPanel ("Live preview unavailable"), persistent AggregationStatusBar, per-episode status badges in both table and card views, discovery modal recognizes go 3 / go3 BLE names, 3 new WS commands (aggregate_episode, retry_aggregation, cancel_aggregation).
  • Cross-platform: macOS / Linux WiFi switching; Windows stub raises NotImplementedError.
  • New extra: syncfield[camera] (bleak + aiohttp).

Reference: BLE protocol ported verbatim from opengraph-studio/recorder, validated against Go3S firmware v8.0.4.11.

Design docs

  • Spec: docs/superpowers/specs/2026-04-14-insta360-go3s-adapter-design.md
  • Plan: docs/superpowers/plans/2026-04-14-insta360-go3s-adapter.md

Test plan

  • Unit tests: 45 adapter tests (BLE protocol vectors, camera lifecycle, OSC atomic download, WiFi switcher, aggregation queue + retry + shutdown drain + crash recovery, Go3SStream lifecycle, policy downgrade, viewer snapshot, viewer commands). All PASS.
  • Integration tests: full record→enqueue→aggregate E2E, aggregation-during-recording non-interference, atomic failure + retry. 3 PASS.
  • Frontend: yarn build clean (TS + Vite + Tailwind v4).
  • Hardware smoke test: record a 10s clip against a real Go3S (examples/insta360_go3s/record.py); verify episode dir contents (mp4 + aggregation.json with state: completed).
  • Multihost smoke test: leader + follower session with Go3S; confirm policy auto-downgrades to on_demand and lab WiFi stays up during recording.
  • macOS WiFi-switch permission: first aggregation run should prompt Location permission.

Known follow-ups (non-blocking)

  • manifest.json Go3S-specific fields (ble_address, sd_path, aggregation_policy)
  • Env-var config for WiFi / download timeouts
  • formatBytes duplication in frontend → extract to lib/format.ts
  • Aggregation badge should hide on non-Go3S episodes (currently shows "Ready" by default)

🤖 Generated with Claude Code

styu12 and others added 29 commits April 14, 2026 02:14
- Add live_preview: bool = True field to StreamCapabilities for routing
  Go3S streams to standalone recorder panel (defaults to True for backward
  compatibility)
- Include live_preview in to_dict() serialization
- Extend FinalizationReport.status Literal with pending_aggregation value
  to express background WiFi aggregation jobs
- Create comprehensive test files test_types_capabilities.py and
  test_types_finalization.py with full coverage
- Update existing test_types.py tests to expect live_preview in dict

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Implements Go3SBLECamera with Future-per-seq dispatch, SYNC handshake,
CMD_CHECK_AUTH auth, start/stop capture, and CaptureResult. Adds
pytest-asyncio and asyncio_mode=auto to pyproject.toml.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Implements the Go3SStream lifecycle class (Task 10) that subclasses
StreamBase and bridges the sync orchestrator API to the async BLE
helper, building an AggregationJob on stop_recording and enqueuing it
per the configured aggregation_policy.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…host roles

When a Go3SStream with aggregation_policy="eager" is added to a session
whose role is LeaderRole or FollowerRole, the policy is automatically
downgraded to "on_demand" so the host's WiFi adapter stays connected to
lab WiFi (needed for mDNS multihost coordination) instead of switching to
the camera AP during recording.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add AggregationSnapshot dataclass to state.py and wire it into
snapshot_to_dict so the /ws/control and /api/status payloads include
an aggregation section (active_job, queue_length, recent_jobs). A
best-effort listener on the global Go3S aggregation queue keeps the
state live; a no-op if the camera extra is not installed.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add handle_control_command() dispatcher in viewer/server.py for the three
new WS commands (aggregate_episode, retry_aggregation, cancel_aggregation),
and add the corresponding methods to SessionOrchestrator that delegate to
the Go3S aggregation queue.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Renders a standalone-recorder card body (no live preview) for video
streams where capabilities.live_preview is false, showing recording /
aggregating / ready / failed states with a Retry button on failure.

- New component: standalone-recorder-panel.tsx
- stream-card.tsx: dispatcher branch for live_preview=false video streams
- types.ts: StreamCapabilities, AggregationSnapshotWS, AggregationActiveJob,
  extended ControlAction with aggregation commands
- state.py / poller.py: surface live_preview from StreamCapabilities
- server.py: emit capabilities in WS stream dict; route aggregation
  action commands (retry_aggregation, cancel_aggregation, aggregate_episode)
  through handle_control_command in _handle_command

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Implements T16: mounts a persistent AggregationStatusBar below the stop-result
banner in RecordView (running = animated progress bar + bytes, failed = Retry
button). Adds AggregationBadge to EpisodeTable and EpisodeCard in the review
mode, sourced from the live WS aggregation snapshot (active_job + recent_jobs).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add `add_go3s_stream` command to `handle_control_command` with a
`_next_default_id` helper that auto-increments the stream id. Route
the command through `_handle_command` (WS action dispatcher). Extend
the discovery modal with `isGo3SDevice` name-pattern detection and
`deviceTypeLabel` to render "Insta360 Go3S" instead of the raw adapter
string for devices whose BLE name contains "go 3" or "go3".

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Adds a 'camera' optional-dependencies entry for the Insta360 Go3S
adapter (bleak + aiohttp) and rolls the new package into the 'all'
extra. Install with: uv add 'syncfield[camera]'.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Empty string bypassed the prior None guard and produced an
AggregationJob with sd_path='' that failed opaquely in the worker.
Now raises a clear error at stop_recording() with diagnostic guidance.
The aggregation queue has had crash-recovery support since T08, but no
production callsite invoked it. Now scan SYNCFIELD_GO3S_RECOVERY_ROOT
(default: cwd) at singleton init and re-enqueue any leftover PENDING/
RUNNING aggregation jobs from prior runs.
…ng_loop()

Both call sites are inside running coroutines, so get_running_loop() is
the correct API. Eliminates DeprecationWarning on Python 3.10+ and
prevents the RuntimeError that 3.14 will raise.
Module-level mutable list bled state across instances and was mutated
from the aggregation worker thread without synchronization.
Was sending only 3 of 5 fields; the TS StreamCapabilities interface
declares all 5. Use to_dict() so future consumers don't see undefined
for supports_precise_timestamps / is_removable.
…apter

# Conflicts:
#	src/syncfield/viewer/frontend/src/components/stream-card.tsx
#	src/syncfield/viewer/frontend/tsconfig.tsbuildinfo
@styu12
styu12 merged commit dea5675 into main Apr 14, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant