Skip to content

Event bus for writes: broadcast device state after mutations, remove claim_processing #244

@ng

Description

@ng

Summary

When multiple clients (phones, M5 dials, web) are connected, a temperature change from one client takes up to 30s to appear on others. The WebSocket server should broadcast state changes immediately after mutations succeed.

Additionally, the claim_processing / activeClient / processingState code is dead — no client sends claim_processing. It should be removed, simplifying the WebSocket to a pure read-only pub/sub channel.

Architecture Change

graph LR
    subgraph "Clients (read-only WS + write via HTTP)"
        PhoneA["Phone A"]
        PhoneB["Phone B"]
        DialL["M5 Dial Left"]
        DialR["M5 Dial Right"]
        Web["Web App"]
    end

    subgraph "sleepypod-core"
        API["tRPC API :3000\n(all writes)"]
        WS["WebSocket :3001\n(read-only pub/sub)"]
        EB["broadcastFrame\n(event bus)"]
        DAC["DAC Socket\n(hardware)"]
        MON["dacMonitor\n(2s poll)"]
    end

    PhoneA -- "POST /device/temperature" --> API
    PhoneB -- "POST /device/power" --> API
    DialL -- "POST /device/temperature" --> API
    API -- "after success" --> EB
    MON -- "status:updated" --> EB
    EB -- "deviceStatus frame" --> WS
    WS -- "subscribe" --> PhoneA & PhoneB & DialL & DialR & Web
    MON -- "poll 2s" --> DAC
    API -- "SequentialQueue" --> DAC
Loading

Changes Required

1. Remove claim_processing from WS protocol

  • src/streaming/piezoStream.ts: Remove activeClient, heartbeatTimer, resetHeartbeatTimer(), releaseClient(), claim/release/heartbeat message handlers
  • src/streaming/processingState.ts: Delete file
  • src/server/routers/biometrics.ts: Remove getProcessingStatus procedure

2. Add broadcastMutationStatus() to device router

  • src/server/routers/device.ts: After setTemperature/setPower/setAlarm/clearAlarm/snoozeAlarm succeed, overlay the mutation onto dacMonitor.getLastStatus() and call broadcastFrame() with a deviceStatus frame
  • Fire-and-forget, no extra hardware call needed
  • dacMonitor 2s poll still broadcasts authoritative status as consistency backstop

3. Client cleanup (optional)

  • src/hooks/useSensorStream.ts: Remove any claim_processing send logic if present

Why This Works

  • broadcastFrame() already sends to ALL subscribed WS clients
  • deviceStatus frame type already consumed by web (useDeviceStatus) and iOS (DeviceManager)
  • SequentialQueue prevents concurrent DAC write conflicts
  • Temperature debounce (200ms) collapses rapid drags — only final value broadcasts

Follow-up

  • M5 Dial WebSocket subscription (sleepypod/m5-rotary-dial) — replace 30s HTTP polling with instant WS frames

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions