Skip to main content

GLOWbe mobile app v1 architecture and scope

Context

We want to ship a GLOWbe mobile app that lets a customer buy a consumer-tier prop (today: Glowbie) and start using it without owning a GLOWhub or running desktop GLOWbe Studio. The desired experience: download the app on iOS or Android, power on the prop, connect, pick a glow, play. The mobile app's scope is the Connect and Control feature zones only; the Compose and Create zones stay on desktop Studio.

This decision sits inside the existing domain (see CONTEXT-MAP.md):

  • Hub bounded context defines Hub Mode: Local Hub (glowhub-core hosted on the same device as the client) vs Device Hub (a physical GLOWhub).
  • Engine bounded context defines Engine Mode: Online (engine runs inside a hub) vs Offline (engine embedded in Studio, no hub at all).
  • Wireless Link is the abstract term for the radio between hub and props — currently implemented as WiFi with the hub acting as the access point. The CONTEXT explicitly says the implementation may change.

Hard constraints established during the deliberation:

  • Portability. No reliance on home Wi-Fi or any fixed infrastructure. The customer must be able to use a Glowbie at a friend's place, outdoors, at a festival.
  • Cross-platform iOS and Android. Not Android-only, not iOS-only.
  • Real-time, honest low latency. Audio-reactive effects must genuinely react with no perceptible delay. No "cheating" via pre-computed beat tracks, no audio-output delay compensation: the system must work with live mic input from live musicians, where compensation is impossible by definition.
  • Stable wireless link. Music shows and live use cannot tolerate dropouts.
  • Non-intimidating consumer UX. Mobile must not surface dev-tool concepts (node editor, graph, internal nodes). The customer model is "pick a glow, play."

Soft constraints:

  • Maximize code reuse with desktop Studio — same engine, hub, protocol crates; single team.
  • Don't foreclose on future SaaS (cloud sync, accounts, possibly marketplace) — out of v1 scope but in the product direction.
  • The Local Hub code path exists in desktop Studio but is dev-test only — never validated in production. Mobile inherits that gap and must budget for end-to-end validation.

Options

The high-level architectural shape was the first fork:

  1. Status quo — no mobile app. Customers need desktop Studio + GLOWhub to use any GLOWbe product.
  2. Mobile app as a GLOWhub remote. Mobile is a thin Control client; still requires the customer to own and bring a GLOWhub.
  3. Mobile app as Local Hub host — hubless from the customer's point of view. glowhub-core is linked into the mobile app process; the phone is the hub. Engine runs on the phone; wireless link runs between the phone and the props directly. Chosen.
  4. Mobile app as Engine Offline mode + new direct-to-prop channel. No hub at all, engine linked as a library, a new ad-hoc protocol carries Output signals to props. Rejected: forces a new transport that the Hub bounded context has no name for, fractures the domain.
  5. Mobile app as a partner-integration shell (Spotify Ready, Apple Home, Matter). Customer brings no GLOWbe-owned hardware. Rejected as a different product; doesn't address the "buy a Glowbie and play with it" use case.

Within the chosen shape (option 3), every sub-decision below had its own option set. The deliberation walked them sequentially because each one constrained the next — wireless choice fixed the latency floor, which scoped audio capabilities, etc.

Recommendation

Option 3 — mobile as Local Hub host — with the specific sub-decisions below. The full deliberation is in the session transcript; this section captures the alternatives considered and the reason each was rejected, so future readers can see what's already been ruled out.

Considered:

  • Phone as Wi-Fi AP (phone hotspot) — rejected, iOS does not allow programmatic Personal Hotspot control and the iOS hotspot model is built for internet sharing not local LAN.
  • Home router as AP (props join home Wi-Fi, phone joins home Wi-Fi, mDNS finds each other) — rejected, contradicts the portability constraint.
  • Prop as Wi-Fi AP, raw Output signals (one prop acts as AP, phone and other props join it) — viable in isolation, but at 6 props × 5000 LEDs at 60 fps the AP-prop has to run its own LEDs, terminate Wi-Fi for 5 stations + the phone, and forward 35 Mbps — engineering-feasible but at the edge of a tiny embedded SoC. Folded into the chosen path only after the scope cap (sub-decision B) brought aggregate bandwidth into comfortable territory.
  • BLE-only — rejected at scope. BLE 5 maxes around 1.4 Mbps practical app throughput, can't carry a 5000-LED prop, and the iOS BLE connection-interval floor (15 ms minimum) is already above the 60 fps frame period. Multi-prop time-slicing degrades sync. Surviving as the discovery + idle layer, not the performance layer.
  • Wi-Fi Direct / P2P — rejected, Android-only in practice (iOS's AWDL is Apple-to-Apple only).
  • Thread, UWB, ESP-NOW-like proprietary — rejected for v1 (Thread needs a border router; UWB phone APIs are locked down; phones can't speak proprietary 2.4 GHz).
  • Move rendering to the prop, send Render-level commands instead of per-LED Output signals — interesting future direction that would collapse wireless bandwidth by ~100×, but pulls firmware into the Engine bounded context with cascading consequences. Deferred.

Chosen: hybrid BLE + Wi-Fi.

  • BLE as the Wireless Link variant for discovery, idle, control-plane signaling, and pairing. Always-on, low-power. Phone scans, sees nearby props in the GLOWbe app's "Connect" tab instantly.
  • Wi-Fi prop-as-AP as the Wireless Link variant for active performance / data-plane. Prop spins up SoftAP on demand (instructed via BLE), phone joins via NEHotspotConfiguration (iOS) / WifiNetworkSpecifier (Android). Output signals flow over UDP. Cellular keeps the phone's general internet alive throughout (same model as DJI, GoPro, Insta360, Lighttoys FT Remote Pro).
  • Concurrent BLE+Wi-Fi is a first-class capability on Glowbie's nRF5340 + nRF7002 SoC pairing.
  • The Hub CONTEXT's existing wording — "the implementation may change in the future (e.g. UWB); Wireless Link is the abstract term that survives the protocol choice" — already accommodates this without a structural change.

Sub-decision B: Mobile scope cap

Considered:

  • No cap; mobile must drive up to 6 props of 5000 LEDs each in real-time. Rejected: physically at the edge of consumer wireless from a phone even with every right architectural choice.
  • Cap at 6 props, but no LED-count cap. Same problem — one 5000-LED prop is already ~7 Mbps before any others.
  • Cap at consumer-tier (≤6 props, Glowbie-class LED counts; Glowbie today is 40 LEDs; upper comfortable bound around 500 LEDs). Chosen. Beyond this, the customer needs a GLOWhub.

Rationale: matches the segmentation pattern of every successful consumer/pro product line (Philips Hue's hubless Bluetooth mode vs. Hue Bridge, Sonos consumer vs. pro, DJI Mini vs. Inspire, GoPro vs. broadcast). The "GLOWhub required for the bigger experience" upgrade is a feature, not a defeat — it's the natural upsell.

Sub-decision C: BLE pairing and ownership

Considered:

  • Just Works pairing (anyone with the app in range can connect). Rejected: party-griefing risk; "why is my Glowbie green? oh, my friend connected to it from across the room."
  • Display-confirmed passkey (prop shows a code on its LEDs, user enters in app). Rejected: high friction; awkward on a Glowbie's LED form factor.
  • Cloud-anchored ownership (prop registers in glowregistry, account-based access control). Deferred to the future SaaS phase.
  • Button-gated Pair Mode + BLE bonding (TOFU)chosen. Customer holds the prop, performs a physical gesture, app sees prop in "ready to pair" state, taps to bond. BLE bonding keys persist in prop flash across power cycles and app reinstalls.

Glowbie button gesture constraints (verified against glowchip/nordic/glowchip/glowchip-glowbie/src/power.c):

  • LTC2955 power chip cuts power at ~5 s hold — firmware cannot intercept.
  • R17 and R18 are unpopulated on the current Glowbie PCB — firmware cannot detect button release, so no hold-duration gestures are possible at all.
  • Available menu: single press, double press, triple press, N-press. Each LTC2955 INT pulse resets its internal long-press timer, so multi-press doesn't accidentally trigger shutdown.

Pair Mode entry: double-press. Factory reset / unbond: 5 rapid presses. Both are firmware-only changes (interrupt-driven INT handling + small gesture state machine); no PCB modifications. No in-app "Unbond" — overengineered when the firmware gesture covers the common cases (lost phone, second-hand prop).

Two new terms added to glowhub-core/CONTEXT.md:

  • Bonded Prop — prop with completed BLE pairing, persisted in the hub's bond list.
  • Pair Mode — time-limited prop-side state entered via physical gesture during which the prop accepts new bondings.

Sub-decision D: Audio sources for reactive effects

Considered:

  • Spotify integration via Spotify SDK or Web API. Rejected for v1: neither iOS nor Android allows third-party apps to capture Spotify's audio output (DRM); the Spotify SDK only exposes control + metadata, not samples; Spotify deprecated the Audio Analysis API in late 2024 (no precomputed beat timings); the developer ToS commits us to constraints we don't want to take on for a v1 feature.
  • Pre-import beat analysis on local files + delay-compensated playback (perfect sync via prediction). Rejected — explicitly rejected by the user as "cheating." The platform must work with live mic input from live musicians, where pre-analysis is impossible by definition. The system must be actually real-time, not appear real-time. See [[feedback-realtime-honest-latency]] memory.
  • Spotify metadata mode (Spotify Web API tells us track + position, we schedule from precomputed beat timing). Same rejection as above; also depends on Spotify API stability that has eroded recently.

Chosen for v1: App Playback + Ambient Mic, both reactive.

  • App Playback: a built-in audio player in the GLOWbe mobile app. User picks a local audio file (M4A / MP3 / FLAC) from their library or imports via share-sheet. Audio plays through the phone speaker / Bluetooth output. The engine reads the same buffer that's heading to the DAC (zero capture latency for this source).
  • Ambient Mic: phone microphone via iOS AVAudioEngine low-latency path (setPreferredIOBufferDuration) / Android AAudio LowLatency performance mode. Works with any audio in the room: Spotify on a speaker, live instruments, ambient sound.
  • Both feed the same reactive analysis pipeline. No pre-analysis. No delay compensation.

Audio Source is a new term added to glowbe-studio/CONTEXT-control.md. On mobile it is scoped per-glow (one source feeds all of that glow's Audio Input Nodes). On desktop Control (future scope) it will be scoped per Audio Input Node. This is one instance of a general principle: mobile is the simplified projection of the same Control bounded context; see [[project-mobile-is-simplified-control]] memory.

Sub-decision E: Latency philosophy and budget

Considered:

  • Aggressive latency optimisation across every stage of the pipeline (audio I/O, analysis algorithm, engine tick, wireless, LED PHY) — chosen.
  • Latency compensation via audio output delay (only works for App Playback, not Mic) — rejected; same reason as sub-decision D.
  • Predictive scheduling from precomputed beat data — rejected; same reason.

Honest end-to-end budget at Glowbie scope (40 LEDs, hybrid wireless, tuned audio I/O):

  • Envelope-driven effects: ~20–40 ms. Real-time, fully imperceptible.
  • Frequency-band-driven effects: ~35–50 ms. Near real-time, at the perceptual edge.
  • Beat-snap effects: ~60–90 ms. Limited by the algorithmic floor of onset detection (beats are recognised after the transient). Deprioritised in the v1 glow catalog; not forbidden but their lag is inherent.

LED PHY observation (verified against firmware): Glowbie uses Würth WL-ICLED via Zephyr's worldsemi,ws2812-spi driver — SPI as a waveform generator for WS2812B's one-wire protocol, doubled to two on-wire pixels per logical LED for 16-bit-per-channel premium color. At 40 LEDs this is ~3 ms PHY per frame — fine. At 200 LEDs it would be ~12 ms — meaningful. Beyond 300 LEDs at 60 fps the doubling scheme hits a wall. Future consumer-tier props with higher LED counts should evaluate alternatives (8-bit-per-channel + WS2812B, multi-strip parallel via Zephyr multi-strip driver, or switching to true APA102/SK9822 chemistry). Not blocking for v1.

Sub-decision F: Compositions vs. Glows in the mobile catalog

Considered:

  • Glows only, Compositions deferred to v2. Rejected on a technical correction: the engine always runs through a Composer (verified in glowengine/src/engine.rs:130-144Composer::from_single_glow wraps a bare Glow into a synthetic single-clip composition). There is no "Glow-only mode" at runtime to defer. Adding Composition support to mobile adds no engine surface that isn't already there.
  • Full Glows + Compositions including authoring in mobile. Rejected: the Compose feature zone (timeline authoring) stays on desktop; mobile is Control only.
  • Compositions as the universal runtime shape; mobile consumes, does not author. Chosen.

Implications:

  • Mobile catalog displays Compositions. Single-Glow compositions are the simple case (one track, one clip, no audio, no automations) and look like "pick a vibe, play" to the customer. Multi-prop multi-track shows are the rich case, with full media-player playback semantics.
  • Playback controls scale with the composition's shape: timeline-backed compositions get a scrubber; degenerate single-Glow ones get only play/pause.
  • A composition with baked-in audio forces App Playback of that audio as the source; a composition without baked-in audio leaves Audio Source user-selectable.
  • The glowengine/CONTEXT.md wording was updated during the session to be honest about the Composer-always model.

Sub-decision G: Desktop → mobile content transit

Considered:

  • Local file transfer via OS share-sheet (manual: user exports composition from desktop, transfers via AirDrop / email / cloud-drive-of-choice, mobile imports). Chosen for v1.
  • Local network share when both online (mDNS-discovered pull). Rejected: requires both devices on the same network, contradicts portability.
  • Cloud sync via GLOWbe account. Deferred to the SaaS phase. See [[project-saas-future-direction]] memory.
  • OS-provided cloud-drive sync (iCloud / Drive / Dropbox). Effectively a flavour of (a); not separately useful.
  • Bundled compositions only, no transit. Rejected: incoherent with the use case of authoring a multi-prop show on desktop and playing it on mobile.

The v1 mobile catalog has two sections: Bundled (content GLOWbe ships, currently single-Glow wrappers from glowsamples) and My Library (anything imported via share-sheet). Catalog entries should carry a source field structured so a future cloud origin can be added without restructuring local sources.

Sub-decision H: Control tab parameter exposure

Considered:

  • Public Parameter Schema layer: glow authors explicitly mark which Input Nodes are user-visible with name/range/default metadata. Rejected as overengineered.
  • Auto-derive: show all Input Nodes as widgets, one per node, type-determined. Chosen, on the convention that authors only add Input Nodes for user-facing parameters in the first place. See [[project-input-nodes-are-user-facing]] memory.

Implication: glowproto/proto/config.proto's currently-empty ConfigInputControl {} / ConfigInputColor {} etc. need additive extensions for display name, min/max, and default before the mobile Control widgets can render sensibly. Small proto change, no breaking impact, prerequisite to mobile Control implementation.

Sub-decision I: Tech stack

Considered:

  • Qt for desktop + mobile. Was the conclusion of a prior 2025-06-13 ADR, which was deleted during this session as outdated. Its key con — "Tauri mobile is in beta" — has been resolved by Tauri 2.0 stable (October 2024), and the inertia of a year of Studio investment on Tauri has flipped the migration cost. Rejected.
  • Tauri for desktop + mobile. Chosen. Single codebase, web frontend + Rust backend, share glowengine / glowhub-core / glowproto crates directly. Use Tauri plugins for platform APIs (BLE, NEHotspotConfiguration / WifiNetworkSpecifier, audio I/O low-latency paths).
  • Split: keep desktop on Tauri, build mobile on Qt or Flutter. Rejected — two UI stacks is the wrong cost for a small team; the engine/hub/proto crates already give us the shared core regardless of UI tech.

This decision explicitly supersedes the deleted 2025-06-13 Qt ADR.

Decision

The v1 GLOWbe mobile app is built on the following architecture:

  1. Engine Mode = Online, Hub Mode = Local Hub with glowhub-core linked into the mobile app's process.
  2. Scope cap: ≤6 props, Glowbie-class consumer LED counts (~40 today, comfortable upper bound ~500); larger setups require a GLOWhub.
  3. Wireless Link, mobile variant: BLE for discovery + idle + control-plane; Wi-Fi prop-as-AP for active performance. Concurrent on Glowbie hardware. Cellular keeps the phone's general internet alive during shows.
  4. Pairing: BLE bonding entered via Pair Mode on the prop (double-press); factory reset via 5 rapid presses. Firmware-only on Glowbie; no PCB changes.
  5. Audio sources: App Playback (built-in player, local files) + Ambient Mic. Both reactive. No Spotify integration in v1. No pre-analysis. No delay compensation.
  6. Latency philosophy: genuinely real-time, not predictively masked. Effect categories aware of algorithmic floors; v1 catalog favours envelope and frequency-band effects over beat-snap effects.
  7. Content model: Compositions are the universal runtime shape. Mobile catalog is Compositions; authoring stays on desktop. Mobile is consume-only.
  8. Desktop → mobile transit: local file transfer via OS share-sheet for v1. Cloud sync is a future SaaS feature, deliberately deferred.
  9. Control tab UI: auto-derive widgets from the active composition's Input Nodes; no separate public-parameter layer.
  10. Tech stack: Tauri for both desktop Studio and the mobile app, sharing engine/hub/proto Rust crates. Supersedes the deleted 2025-06-13 Qt-direction ADR.

Glossary updates committed during the session: glowhub-core/CONTEXT.md (Hub Mode wording; Bonded Prop, Pair Mode), glowbe-studio/CONTEXT-control.md (Audio Source), glowengine/CONTEXT.md (Composer-always model).

Pre-conditions for v1 implementation work, to be addressed before or during the v1 build:

  • End-to-end validation of the Local Hub code path against real props on real wireless. The existing desktop Local Hub mode has never been production-validated; mobile inherits this gap.
  • Apple "Hotspot Configuration" app entitlement requested and granted.
  • ConfigInputControl / ConfigInputColor / etc. extended in glowproto/proto/config.proto with display name, range, and default fields.
  • Firmware: interrupt-driven INT handling + gesture state machine for double-press (Pair Mode) and 5-press (factory reset); BLE bonding implementation; SoftAP bring-up on demand.
  • Mobile app shell: Tauri iOS and Android targets; share-sheet import handler for composition files; in-app audio player; low-latency audio capture wiring; mDNS or known-port prop discovery on the prop-AP LAN.