Device Integration Guide

This guide explains how to connect a device to the Sttark Machine Broker. Devices connect via WebSocket to send data and receive commands from control applications.

Overview

┌─────────────────┐                  ┌─────────────────┐
│                 │   1. Connect     │                 │
│     Device      │ ───────────────► │     Broker      │
│                 │   2. Register    │                 │
│                 │ ◄───────────────►│  /devices       │
│                 │   3. Send/Recv   │                 │
└─────────────────┘                  └─────────────────┘

Connection Flow

Step 1: Establish WebSocket Connection

Connect to the broker's /devices endpoint with your API key.

Endpoint: wss://machinebroker.sttark.com/devices

Authentication: Provide API key via query parameter or header:

  • Query: wss://machinebroker.sttark.com/devices?apiKey=YOUR_API_KEY
  • Header: X-API-Key: YOUR_API_KEY
// REQUIRED: Connect with API key
const ws = new WebSocket(
  "wss://machinebroker.sttark.com/devices?apiKey=YOUR_API_KEY"
);

Step 2: Receive Welcome Message

Upon connection, the broker sends a welcome message:

{
  "type": "welcome",
  "message": "Connected to device endpoint. Send register message with deviceId.",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Step 3: Register Device (REQUIRED)

You must register your device with a unique ID before sending data.

Send this message immediately after connecting:

{
  "type": "register",
  "deviceId": "your-unique-device-id",
  "metadata": {
    "name": "Human-readable name",
    "label": "Optional display label",
    "type": "sensor",
    "version": "1.0.0"
  }
}
Field Required Description
type Yes Must be "register"
deviceId Yes Unique identifier (alphanumeric, hyphens, underscores, max 64 chars)
metadata No Optional object with device information
metadata.name No Human-readable device name
metadata.label No Display label for dashboards

Registration Response:

{
  "type": "success",
  "action": "registered",
  "deviceId": "your-unique-device-id",
  "previousConnection": false,
  "timestamp": "2024-01-15T10:30:01.000Z"
}

Important: Only ONE device can be connected with a given ID at a time. If a device connects with an ID that's already in use, the previous connection is automatically closed.

Step 4: Send Data to Subscribers

Once registered, send data to any control apps subscribed to your device:

{
  "type": "message",
  "payload": {
    "temperature": 23.5,
    "humidity": 65,
    "status": "ok"
  }
}
Field Required Description
type Yes Must be "message"
payload Yes Any JSON object containing your data

The broker relays this to all subscribed control apps as:

{
  "type": "message",
  "deviceId": "your-device-id",
  "payload": { "temperature": 23.5, "humidity": 65, "status": "ok" },
  "timestamp": "2024-01-15T10:30:05.000Z"
}

Step 5: Receive Commands from Control Apps

Control apps can send commands to your device. You'll receive:

{
  "type": "message",
  "payload": {
    "command": "set_temperature",
    "value": 25
  },
  "timestamp": "2024-01-15T10:31:00.000Z"
}

Handle these commands according to your device's capabilities.


Complete Message Reference

Messages You Send

Message Type When to Send Required
register Immediately after connecting Yes
message Anytime after registration No

Messages You Receive

Message Type Description
welcome Sent on connection
success Registration confirmed
error Something went wrong
message Command from a control app

Error Messages

{
  "type": "error",
  "error": "Description of what went wrong",
  "timestamp": "2024-01-15T10:30:00.000Z"
}

Common errors:

  • "Missing or invalid deviceId" - Registration without valid ID
  • "Invalid deviceId format" - ID contains invalid characters
  • "Device not registered" - Tried to send message before registering

Example Implementation

See test/mock-device.js for a complete Node.js example. Here's a minimal implementation:

const WebSocket = require("ws");

// Configuration
const BROKER_URL = "wss://machinebroker.sttark.com/devices";
const API_KEY = "your-api-key";
const DEVICE_ID = "my-sensor-001";

// Connect
const ws = new WebSocket(`${BROKER_URL}?apiKey=${API_KEY}`);

ws.on("open", () => {
  console.log("Connected to broker");

  // REQUIRED: Register device
  ws.send(
    JSON.stringify({
      type: "register",
      deviceId: DEVICE_ID,
      metadata: {
        name: "Temperature Sensor",
        type: "sensor",
      },
    })
  );
});

ws.on("message", (data) => {
  const message = JSON.parse(data);

  switch (message.type) {
    case "success":
      console.log("Registered successfully");
      // Start sending data
      startSendingData();
      break;

    case "message":
      // Handle command from control app
      console.log("Received command:", message.payload);
      handleCommand(message.payload);
      break;

    case "error":
      console.error("Error:", message.error);
      break;
  }
});

// Send data periodically
function startSendingData() {
  setInterval(() => {
    ws.send(
      JSON.stringify({
        type: "message",
        payload: {
          temperature: Math.random() * 30 + 10,
          timestamp: new Date().toISOString(),
        },
      })
    );
  }, 5000);
}

// Handle incoming commands
function handleCommand(payload) {
  // Your device-specific command handling
  console.log("Processing command:", payload);
}

Connection Lifecycle

┌──────────────────────────────────────────────────────────────┐
│                    Device Connection Lifecycle                │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  1. CONNECT ──► WebSocket to /devices?apiKey=...            │
│       │                                                      │
│       ▼                                                      │
│  2. RECEIVE ◄── Welcome message                             │
│       │                                                      │
│       ▼                                                      │
│  3. SEND ────► Register with deviceId                       │
│       │                                                      │
│       ▼                                                      │
│  4. RECEIVE ◄── Success confirmation                        │
│       │                                                      │
│       ▼                                                      │
│  5. LOOP ────► Send data messages                           │
│       │    ◄── Receive commands                             │
│       │                                                      │
│       ▼                                                      │
│  6. CLOSE ───► Disconnect when done                         │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Reconnection Strategy

Important: The broker does not persist connection state. When the broker restarts (e.g., during deployments), all connections are dropped and devices must reconnect and re-register.

Recommended Approach: Exponential Backoff with Jitter

const RECONNECT_BASE_DELAY = 1000;  // Start with 1 second
const RECONNECT_MAX_DELAY = 30000;  // Cap at 30 seconds
let reconnectAttempts = 0;
let isIntentionalClose = false;

function getReconnectDelay() {
  const delay = Math.min(
    RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts),
    RECONNECT_MAX_DELAY
  );
  // Add jitter (±20%) to prevent thundering herd
  const jitter = delay * 0.2 * (Math.random() - 0.5);
  return Math.round(delay + jitter);
}

function connect() {
  const ws = new WebSocket(`${BROKER_URL}?apiKey=${API_KEY}`);

  ws.on("open", () => {
    console.log("Connected");
    reconnectAttempts = 0; // Reset on success

    // IMPORTANT: Re-register after every connection
    ws.send(JSON.stringify({
      type: "register",
      deviceId: DEVICE_ID,
      metadata: { name: "My Device" }
    }));
  });

  ws.on("close", (code) => {
    if (!isIntentionalClose) {
      const delay = getReconnectDelay();
      reconnectAttempts++;
      console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})...`);
      setTimeout(connect, delay);
    }
  });

  ws.on("error", (err) => {
    console.error("WebSocket error:", err.message);
    // Let the close handler trigger reconnection
  });
}

// Start connection
connect();

// Graceful shutdown
process.on("SIGINT", () => {
  isIntentionalClose = true;
  ws.close(1000, "Shutting down");
});

Key Points

  1. Always re-register after connecting - The broker has no memory of your device after a restart
  2. Use exponential backoff - Prevents overwhelming the broker during recovery
  3. Add jitter - Prevents "thundering herd" when many devices reconnect simultaneously
  4. Track intentional vs. unintentional disconnects - Don't reconnect if the user closed the connection
  5. Reset attempt counter on success - Ensures quick reconnection for transient failures

Connection Close Codes

Code Meaning Should Reconnect?
1000 Normal closure Only if unintentional
1001 Going away (server shutdown) Yes
1006 Abnormal closure Yes
1011 Server error Yes (with backoff)
1012 Service restart Yes

Best Practices

  1. Reconnection: Implement automatic reconnection with exponential backoff (see above)
  2. Heartbeat: The broker sends WebSocket pings; respond to pongs automatically
  3. Unique IDs: Use truly unique device IDs (MAC address, UUID, serial number)
  4. Error Handling: Always handle error messages and connection failures
  5. Graceful Shutdown: Close the WebSocket cleanly when shutting down
  6. Idempotent Registration: Design your device to handle re-registration gracefully

Testing Your Integration

Use the mock device script to test:

# Set your API key
export API_KEY=your-api-key

# Run mock device
node test/mock-device.js my-device-001 wss://machinebroker.sttark.com "My Test Device"

Interactive commands:

  • send <json> - Send a message
  • status - Show connection status
  • quit - Disconnect