@nolag/iot

IoT device telemetry and command dispatch with acknowledgment tracking.

Overview

Connect IoT devices and controllers over real-time channels. Devices push telemetry (sensor readings, status updates, or any structured measurement) while controllers dispatch commands with Promise-based acknowledgment tracking and configurable timeouts. Two roles participate in a device group: devices report data and acknowledge commands, and controllers monitor telemetry streams and issue commands. A single client can act as both. Groups organise devices by fleet, physical location, or function.

Key Features

  • Real-time telemetry broadcast from devices to all group subscribers
  • Promise-based command dispatch with configurable acknowledgment timeout
  • Ephemeral telemetry for high-frequency data without storage overhead
  • Ephemeral commands with fire-and-forget dispatch and ack tracking
  • Ephemeral acknowledgment channel for command response tracking
  • Device online/offline presence via lobby
  • Multiple groups per connection; join by zone, building floor, vehicle type, etc.

How It Works

NoLagIoT wraps the @nolag/js-sdk client and manages a lobby that reflects device presence. Calling joinGroup(name) returns a DeviceGroup that subscribes to three topics: telemetry for ephemeral sensor readings, commands for ephemeral command payloads, and _cmd_ack for ephemeral acknowledgment messages. All topics are ephemeral (no server-side retention) to handle high-frequency telemetry without storage overhead. When a controller calls sendCommand() the SDK stores a pending Promise keyed on the command ID; when the target device calls ackCommand() the matching Promise resolves or rejects based on the reported status.

TopicPurposeReplay
telemetrySensor readings and device status updatesEphemeral
commandsCommands dispatched to devicesEphemeral
_cmd_ackCommand acknowledgment messagesEphemeral

Installation

npm install @nolag/iot @nolag/js-sdk

Quick Start

import { NoLagIoT } from '@nolag/iot'

const client = new NoLagIoT('your-access-token')
await client.connect()

// Join a device group (organised by fleet, location, or function)
const group = await client.joinGroup('sensors-floor-3')

// ── Device side ───────────────────────────────────────────────────────────────

// Send a telemetry reading
group.sendTelemetry('temperature', 22.4, {
  unit: 'celsius',
  sensorId: 'tmp_01',
})

group.sendTelemetry('humidity', 61, { unit: 'percent' })

// Listen for commands sent by controllers
group.on('command', async ({ command }) => {
  console.log('Received command:', command.name, command.params)

  // Execute the command...
  const result = await handleCommand(command)

  // Acknowledge success
  await group.ackCommand(command.commandId, 'success', result)
})

// ── Controller side ───────────────────────────────────────────────────────────

// Watch all telemetry from the group
group.on('telemetry', ({ deviceId, sensorId, value, opts, timestamp }) => {
  console.log(`[${deviceId}] ${sensorId}: ${value}`)
})

// Retrieve the last known reading for a specific sensor
const reading = await group.getTelemetry('device-007', 'temperature')
console.log('Last temperature:', reading)

// Send a command (returns a Promise that resolves when the device acks)
try {
  const cmd = await group.sendCommand('device-007', 'reboot', {
    delay: 5000,
  })
  // Promise resolves once the device calls ackCommand
  console.log('Command acked by device:', cmd.status)
} catch (err) {
  // Throws if the device does not ack within the configured timeout
  console.error('Command timed out or rejected:', err)
}

// Monitor ack events
group.on('commandAck', ({ commandId, deviceId, status, result }) => {
  console.log(`Command ${commandId} acked by ${deviceId} with status ${status}`)
})

// Presence
client.on('deviceOnline', ({ deviceId }) => console.log('Online:', deviceId))
client.on('deviceOffline', ({ deviceId }) => console.log('Offline:', deviceId))

API Reference

NoLagIoT

MethodReturnsDescription
connect()Promise<void>Establish the WebSocket connection and join the lobby.
disconnect()voidClose the connection and leave the lobby.
joinGroup(name)Promise<DeviceGroup>Subscribe to a device group. Returns the group instance.
leaveGroup(name)Promise<void>Unsubscribe from a device group and release its resources.
getOnlineDevices()Device[]Return all devices currently present in the lobby.

NoLagIoT Events

EventPayloadDescription
connectednoneWebSocket connection established.
disconnectedreason: stringConnection closed.
reconnectednoneConnection restored; room membership and presence are restored automatically.
errorerror: ErrorA transport or protocol error occurred.
deviceOnlinedevice: DeviceA device joined the lobby.
deviceOfflinedevice: DeviceA device left the lobby.

DeviceGroup

MethodRoleReturnsDescription
sendTelemetry(sensorId, value, opts?)DevicevoidPublish a sensor reading to the group. opts carries metadata such as unit or tags.
getTelemetry(deviceId?, sensorId?)BothPromise<TelemetryReading[]>Fetch the most recent telemetry. Omit arguments to get all devices and sensors.
sendCommand(targetDeviceId, command, params?)ControllerPromise<DeviceCommand>Dispatch a named command to a device. Resolves when the device acknowledges, or rejects on timeout.
ackCommand(commandId, status, result?)DevicevoidSend an acknowledgment for a received command. status is 'success' or 'error'.

DeviceGroup Events

EventPayloadDescription
telemetry{ deviceId, sensorId, value, opts?, timestamp }A telemetry reading was received from a device in the group.
command{ command: DeviceCommand }A command was dispatched to this device (device role only).
commandAck{ commandId, deviceId, status, result? }A device acknowledged a previously dispatched command.
deviceJoineddevice: DeviceA device joined this group.
deviceLeftdevice: DeviceA device left this group.