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
- Always re-register after connecting - The broker has no memory of your device after a restart
- Use exponential backoff - Prevents overwhelming the broker during recovery
- Add jitter - Prevents "thundering herd" when many devices reconnect simultaneously
- Track intentional vs. unintentional disconnects - Don't reconnect if the user closed the connection
- 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
- Reconnection: Implement automatic reconnection with exponential backoff (see above)
- Heartbeat: The broker sends WebSocket pings; respond to pongs automatically
- Unique IDs: Use truly unique device IDs (MAC address, UUID, serial number)
- Error Handling: Always handle error messages and connection failures
- Graceful Shutdown: Close the WebSocket cleanly when shutting down
- 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 messagestatus- Show connection statusquit- Disconnect