@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:
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 Name | Transport | Coordinate Mode | Payload Shape & Reference |
|---|---|---|---|
SewioAdapter | WebSocket | Meter | Pub/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"} |
CiscoFirehoseAdapter | HTTPS Stream | LatLon | Line-delimited JSON push channel from Cisco DNA/Spaces Firehose, passing custom tenant sensor event blocks. |
QuuppaAdapter | HTTP / MQTT | Meter | QPE API formats or MQTT v5 on quuppa/apidata/{project}/{type}/{format}/{tagId} yielding {tagId, smoothX, smoothY, smoothZ, ts}. |
KontaktAdapter | WebSocket / REST | LatLon | Kontakt.io Location Engine tracking structures: {trackingId, position:{x,y,z}, levelId}. |
PozyxAdapter | MQTT / WS | Meter | Pozyx Cloud MQTT feeds: {tagId, position:{x,y,z}} on {tenant}/tags topic. |
InfsoftAdapter | HTTP Poll | Meter | infsoft Cloud REST payload grids: {positionId, x, y, level}. |
CenTrakAdapter | MQTT / REST | Section/Room | Zone-based room level beacons: {badgeId, roomId, ts}. |
StanleyAeroScoutAdapter | HTTP Poll | Meter | Stanley AeroScout location engine frames: {tagId, x, y, mapId}. |
ZebraMotionWorksAdapter | WebSocket | Meter | Zebra MotionWorks RTLS frame structures: {tagId, x, y, z, ts}. |
IndoorAtlasAdapter | SDK Callbacks | LatLon | IndoorAtlas Web positioning callback frames: {lat, lng, accuracy, floorLevel}. |
GenericMQTTAdapter | MQTT / WS | Configurable | Connects to arbitrary MQTT brokers (EMQX, HiveMQ) using custom JSON mappings. |
GenericWebSocketAdapter | WebSocket | Configurable | Raw custom JSON mappings over standard WebSockets. |
GenericRESTPollAdapter | HTTP Poll | Configurable | Polled JSON endpoints with configurable poll frequencies. |
GenericSSEAdapter | Server-Sent | Configurable | HTML5 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:
- 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>(); - RequestAnimationFrame (rAF) Loop: The
<RTLSLayer>runs an active rAF loop at the browser refresh rate. - Active Level Filtering: During the rAF cycle, the layer filters the position cache, identifying assets whose
floorIdmatches the currently visible floor. - 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:
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.
