Skip to content

@bloomsparkagency/rtls - Real-Time Location System Tracking

The RTLS package manages incoming high-frequency location streams (up to 5,000 updates/sec). It translates custom vendor formats into unified spatial coordinates, executes sensor filtering in background Web Workers, and renders asset movements at a constant 60 FPS.


🔌 The RTLSAdapter Interface

All location streams implement a standard, unified interface:

typescript
export interface RTLSAdapter {
  /** Unique ID for the active stream. */
  id: string;
  
  /** Vendor type. */
  vendor: RTLSVendor;
  
  /** Network connection protocol. */
  transport: 'websocket' | 'http-poll' | 'mqtt' | 'grpc-web' | 'sse';
  
  /** Output coordinate system. 'meter' tracks local building grid offset; 'latlon' uses WGS-84 coordinates. */
  coordinateMode: 'meter' | 'latlon';
  
  /** Initiates network handshakes. */
  connect(): Promise<void>;
  
  /** Closes open sockets and clears poll timers. */
  disconnect(): Promise<void>;
  
  /** Subscribe to incoming position updates. */
  subscribe(cb: (update: PositionUpdate) => void): () => void;
  
  /** Queries current connection status and retry offsets. */
  health(): AdapterHealth;
}

export type RTLSVendor =
  | 'sewio' | 'cisco' | 'quuppa' | 'kontaktio' | 'pozyx' | 'infsoft'
  | 'centrak' | 'stanley' | 'zebra' | 'indooratlas'
  | 'mqtt' | 'websocket' | 'rest' | 'sse';

export interface PositionUpdate {
  assetId: string;
  buildingId?: string;
  floorId?: string;
  x?: number; y?: number; z?: number; // meter mode
  longitude?: number; latitude?: number; // latlon mode
  accuracy?: number; // error uncertainty circle in meters
  ts: number; // epoch timestamp in ms
  meta?: Record<string, unknown>;
}

export interface AdapterHealth {
  status: 'connected' | 'reconnecting' | 'disconnected';
  lastSeen: number; // epoch ms of last packet
  reconnectAttempt: number;
  nextRetryMs: number;
}

📡 The 14 Pre-Built Adapters

The SDK includes out-of-the-box support for leading commercial asset localization systems:

Adapter NameTransportCoordinate ModePayload Shape & Reference
SewioAdapterWebSocketMeterPub/Sub Tag/Zone feeds: {"body":{"feed_id":"23","x":"12.2","y":"4.5","ele":"1.5","at":"2026-05-23 15:00:00.000000"},"resource":"/tags"}
CiscoFirehoseAdapterHTTPS StreamLatLonLine-delimited JSON push channel from Cisco DNA/Spaces Firehose, passing custom tenant sensor event blocks.
QuuppaAdapterHTTP / MQTTMeterQPE API formats or MQTT v5 on quuppa/apidata/{project}/{type}/{format}/{tagId} yielding {tagId, smoothX, smoothY, smoothZ, ts}.
KontaktAdapterWebSocket / RESTLatLonKontakt.io Location Engine tracking structures: {trackingId, position:{x,y,z}, levelId}.
PozyxAdapterMQTT / WSMeterPozyx Cloud MQTT feeds: {tagId, position:{x,y,z}} on {tenant}/tags topic.
InfsoftAdapterHTTP PollMeterinfsoft Cloud REST payload grids: {positionId, x, y, level}.
CenTrakAdapterMQTT / RESTSection/RoomZone-based room level beacons: {badgeId, roomId, ts}.
StanleyAeroScoutAdapterHTTP PollMeterStanley AeroScout location engine frames: {tagId, x, y, mapId}.
ZebraMotionWorksAdapterWebSocketMeterZebra MotionWorks RTLS frame structures: {tagId, x, y, z, ts}.
IndoorAtlasAdapterSDK CallbacksLatLonIndoorAtlas Web positioning callback frames: {lat, lng, accuracy, floorLevel}.
GenericMQTTAdapterMQTT / WSConfigurableConnects to arbitrary MQTT brokers (EMQX, HiveMQ) using custom JSON mappings.
GenericWebSocketAdapterWebSocketConfigurableRaw custom JSON mappings over standard WebSockets.
GenericRESTPollAdapterHTTP PollConfigurablePolled JSON endpoints with configurable poll frequencies.
GenericSSEAdapterServer-SentConfigurableHTML5 EventSource streams with custom payload mappers.

🔄 Reconnection Jitter Policy

If a location feed drops due to network interruptions, adapters automatically initiate reconnect loops inside the Web Worker using a standardized exponential backoff algorithm with jitter:

  • Base Timeout: 1 second.
  • Backoff Multiplier: Double the duration per attempt ($\times 2$).
  • Cap Threshold: 32 seconds.
  • Jitter Jiggle: Introduce $\pm 20%$ random jitter to prevent "thundering herd" reconnect requests from overloading the customer's RTLS broker during wide-scale outages.

⚡ High-Throughput Decoupled Rendering

To keep the UI running at 60 FPS while processing thousands of updates per second:

  1. Ref-Based Caching: Position updates bypass React state and Zustand completely. The Web Worker pipes resolved position arrays straight to a ref-based, main-thread cache:
    typescript
    const positions = new Map<string, PositionUpdate>();
  2. RequestAnimationFrame (rAF) Loop: The <RTLSLayer> runs an active rAF loop at the browser refresh rate.
  3. Active Level Filtering: During the rAF cycle, the layer filters the position cache, identifying assets whose floorId matches the currently visible floor.
  4. Imperative styling: The layer makes direct, imperatively styled changes to the map using map.setFeatureState(). Off-floor assets are buffered but skipped during the WebGL rendering pass, reducing canvas recalculations to nearly zero.

🩺 Kalman Filtering (KalmanFilter2D Utility)

UWB, BLE, and Wi-Fi signals often exhibit heavy drift and signal noise. The SDK includes high-performance math filtering utilities running in the worker:

typescript
import { KalmanFilter2D } from '@bloomsparkagency/rtls';

// Instantiate 2D filter with default settings
// Q (Process Noise) = 0.05, R (Measurement Noise) = 0.5
const filter = new KalmanFilter2D({ q: 0.05, r: 0.5 });

// Apply raw coordinates to retrieve smoothed output
const [smoothX, smoothY] = filter.update(rawX, rawY, timestampMs);
  • Dead-Reckoning Timeout: If an asset goes quiet, the Kalman worker projects movement coordinates using previous vectors for up to 3 seconds before fading the marker out.

Released under commercial licensing.