Skip to content

Streams

Streams are the subscribe/publish half of the WebSocket protocol. You subscribe to a named stream with a filter; the server sends event_* messages whenever new data matches.

Snapshots are deduped and retained — when you subscribe, the server sends you the latest known snapshot for each matching resource immediately, then continues with deltas as they arrive.

Stream names

The StreamName enum lists what's available:

Stream Carries Source
device_snapshot Per-device live state (position, temperature, exposure state, …) SkyNode for each connected device
telescope_snapshot Per-telescope aggregate state SkyNode
instrument_snapshot Per-instrument state — combinations of devices that together carry out an observation type Computed from device snapshots
telescope_log Telescope log lines — significant events, errors, operator notes SkyNode
constraint_snapshot Operational constraint definitions and current thresholds SkyNode
constraint_status_snapshot Constraint pass/fail status with latest evaluation SkyNode
manual_control_lease Manual control lease lifecycle events Hub
shutdown_interlock_snapshot Active shutdown interlocks SkyNode

Subscribing

Send a subscribe_stream message identifying the stream and filter:

{
  "kind": "subscribe_stream",
  "subscription_id": "<client-chosen id>",
  "stream": "device_snapshot",
  "filter": {
    "telescope_uid": "00000000-0000-0000-0000-000000000000"
  },
  "options": {
    "include_existing": true
  }
}

The subscription_id is a client-chosen string used to correlate deliveries with the subscription. The server includes it on every event_* message delivered for that subscription.

The filter shape is per-stream. Common filter keys:

  • telescope_uid — restrict to one telescope.
  • observatory_uid — restrict to one observatory.
  • device_type — restrict to a device kind.
  • device_uid — restrict to a specific device.

When options.include_existing is true (default), the server immediately replays the current retained state for each matching resource before streaming new deliveries. Set false if you only want fresh data.

To stop receiving:

{
  "kind": "unsubscribe_stream",
  "subscription_id": "<the same id>"
}

Receiving

Deliveries arrive as event_<stream> messages:

{
  "kind": "event_device_snapshot",
  "subscription_id": "<your-subscription-id>",
  "snapshot": { /* DeviceSnapshot record */ }
}

The shape of snapshot matches the corresponding schema:

  • event_device_snapshotDeviceSnapshot (with polymorphic deviceType discriminator)
  • event_telescope_snapshotTelescopeSnapshot
  • event_instrument_snapshotInstrumentSnapshot
  • event_constraint_snapshotOperationalConstraint
  • event_constraint_status_snapshotConstraintStatusSnapshot
  • event_manual_control_leaseManualControlLease
  • event_telescope_logTelescopeLogEntry

Publishing (SkyNode only)

SkyNode installations publish snapshots back to the hub through the same WebSocket. The publish messages have shape:

{
  "kind": "publish_device_snapshot",
  "snapshot": { /* DeviceSnapshot */ }
}

These are not useful for external integrations — only SkyNode installations are authorized to publish. They're listed here for completeness so the protocol is fully described.

Patterns

Per-telescope dashboard

Subscribe to all of device_snapshot, constraint_status_snapshot, telescope_log, and manual_control_lease filtered to your telescope's UID. That gives you everything a dashboard needs: live device state, constraint go/no-go, log entries, and operator takeover events.

Multi-telescope monitoring

For network-wide monitoring (fleet dashboards), subscribe to telescope_snapshot with no filter — you'll receive an initial snapshot per telescope and updates as they change.

Backfill from a known cursor

The protocol doesn't currently expose a cursor — event_* messages are deltas from the moment of subscription forward (plus the initial retained state if include_existing is true). To backfill historical state, use the REST API's snapshot endpoints, then subscribe for new data.

Lifecycle

  • Keep-alive — exchange ping/pong to keep the connection warm. Inactive connections may be closed by intermediaries.
  • Reconnect — on disconnect, reconnect with the same access token and re-subscribe. The server doesn't persist subscriptions across reconnects.
  • Error — when an error message arrives, log the code and message. A forbidden error means your token doesn't have permission for the requested stream/filter — get a token with appropriate scope rather than retrying.

Reference

The full message envelope and per-stream shapes are at packages/py/skynet-sdk/skynet_sdk/schemas/ws_protocol.py. The router is at apps/public-api/public_api/routers/ws.py and apps/public-api/public_api/routers/ws/.