Skip to content

Device Simulator

The Multiflexmeter project includes a device simulator for testing the dashboard without requiring physical hardware. The simulator generates generic test data for development purposes.

The device simulator can operate in two modes:

  • LOCAL Mode (default): Sends data directly to the Astro /api/uplink endpoint
  • TTN Mode: Sends data via The Things Network HTTP Integration API

The simulator generates data for multiple devices with:

  • Generic sensor data payloads
  • LoRaWAN signal quality (RSSI, SNR)
  • GPS coordinates from real Dutch poldermill locations

Start the simulator with default settings:

Terminal window
npm run simulator

This will:

  1. Simulate 3 devices (configurable)
  2. Send measurements every 10 seconds
  3. Send data to http://localhost:4321/api/uplink
  4. Generate generic test sensor data

Customize behavior with environment variables:

Terminal window
# Custom number of devices
NUM_DEVICES=5 npm run simulator
# Custom interval (milliseconds)
INTERVAL=5000 npm run simulator
# Custom endpoint
LOCAL_ENDPOINT=http://192.168.1.100:4321/api/uplink npm run simulator
# Combine multiple options
NUM_DEVICES=2 INTERVAL=15000 npm run simulator

Sends data directly to your local Astro development server.

Usage:

Terminal window
npm run simulator

Or with explicit mode:

Terminal window
MODE=LOCAL npm run simulator

Configuration:

Terminal window
# .env or command line
MODE=LOCAL
LOCAL_ENDPOINT=http://localhost:4321/api/uplink
NUM_DEVICES=3
INTERVAL=10000

Data Flow:

┌────────────────┐
│ Simulator │
│ (Node.js) │
└────────┬───────┘
│ HTTP POST
│ (simplified format)
v
┌────────────────┐
│ /api/uplink │
│ (Astro) │
└────────┬───────┘
v
┌────────────────┐
│ Data Store │
│ (SQLite/Redis) │
└────────┬───────┘
│ SSE
v
┌────────────────┐
│ Dashboard │
│ (Browser) │
└────────────────┘

Sends data via The Things Network HTTP Integration API, simulating real TTN webhook delivery.

Usage:

Terminal window
npm run simulator:ttn

Or with explicit configuration:

Terminal window
MODE=TTN TTN_APP_ID=your-app-id TTN_API_KEY=your-api-key npm run simulator

Configuration:

Terminal window
# .env or command line
MODE=TTN
TTN_APP_ID=multiflexmeter-test
TTN_API_KEY=NNSXS.YOUR.API.KEY
TTN_REGION=eu1
NUM_DEVICES=3
INTERVAL=10000

Data Flow:

┌────────────────┐
│ Simulator │
│ (Node.js) │
└────────┬───────┘
│ HTTP POST
│ (TTN format)
v
┌────────────────┐
│ TTN │
│ HTTP API │
└────────┬───────┘
│ Webhook
v
┌────────────────┐
│ /api/uplink │
│ (Astro) │
└────────────────┘
VariableDefaultDescription
MODELOCALOperating mode: LOCAL or TTN
NUM_DEVICES3Number of devices to simulate
INTERVAL10000Milliseconds between measurements
LOCAL_ENDPOINThttp://localhost:4321/api/uplinkEndpoint for LOCAL mode
TTN_APP_ID-TTN application ID (required for TTN mode)
TTN_API_KEY-TTN API key (required for TTN mode)
TTN_REGIONeu1TTN region (eu1, nam1, au1, etc.)

The simulator uses real Dutch poldermill locations:

DeviceNameCityCoordinatesAltitude
1MallemolenGouda52.0116°N, 4.7104°E5m
2De RoosDelft52.0116°N, 4.3571°E3m
3Molen de ValkLeiden52.1595°N, 4.4869°E2m
4De ZwaanRotterdam51.9225°N, 4.4792°E4m
5WindlustAmsterdam52.3676°N, 4.9041°E1m

Devices cycle through these locations if NUM_DEVICES exceeds 5.

The simulator generates generic test data for development purposes. This mimics the passthrough behavior of the actual firmware.

Key Points:

  • The simulator sends test payload data via FPort 1
  • Data format is arbitrary and for testing only
  • Real devices send raw sensor module data (format depends on sensor at 0x36)
  • Application-level decoding is required for both simulated and real data

Binary Format (test data example):

┌────────────────────────────────────────┐
│ Generic test payload (variable bytes) │
│ Used for development/testing only │
└────────────────────────────────────────┘

Future versions will implement structured data with standardized formats:

  • Defined tx_pkt_t structure
  • Consistent data format across all devices
  • Firmware-level validation

Sent once on simulator startup, matching firmware behavior.

Binary Format:

┌────────┬─────────┬─────────┬─────────┬─────────┐
│ Byte 0 │ Byte 1 │ Byte 2 │ Byte 3 │ Byte 4 │
├────────┼─────────┼─────────┼─────────┼─────────┤
│ 0x10 │ FW_MSB │ FW_LSB │ HW_MSB │ HW_LSB │
└────────┴─────────┴─────────┴─────────┴─────────┘

Example: 10 03 07 03 07

  • Command: 0x10
  • Firmware: v3.7
  • Hardware: v3.7

The simulator generates randomized test data to simulate device activity:

// Generate test payload (format varies by implementation)
const testPayload = generateTestData();

Characteristics:

  • Random payload generation for testing
  • Simulates device uplink behavior
  • Tests dashboard data processing pipeline
  • Not intended to match specific sensor formats

Realistic LoRaWAN signal metrics:

rssi = -80 + (Math.random() * 20); // -80 to -60 dBm
snr = 5 + (Math.random() * 5); // 5 to 10 dB

Typical Values:

  • RSSI: -70 dBm (good signal)
  • SNR: 7.5 dB (moderate quality)

Simplified format sent directly to /api/uplink:

{
"devEui": "0000000000000001",
"devAddr": "00000001",
"fPort": 1,
"payload": "base64_encoded_test_data",
"receivedAt": "2025-11-02T10:30:00Z",
"rssi": -72.3,
"snr": 7.8,
"latitude": 52.0116,
"longitude": 4.7104,
"altitude": 5
}

Complete TTN webhook format:

{
"end_device_ids": {
"device_id": "mfm-00000001",
"application_ids": {
"application_id": "multiflexmeter-test"
},
"dev_eui": "0000000000000001",
"dev_addr": "00000001"
},
"uplink_message": {
"f_port": 1,
"f_cnt": 42,
"frm_payload": "base64_encoded_test_data",
"rx_metadata": [{
"gateway_ids": {
"gateway_id": "simulator-gateway"
},
"rssi": -72.3,
"snr": 7.8,
"timestamp": 1730548200000
}],
"settings": {
"data_rate": {
"lora": {
"bandwidth": 125000,
"spreading_factor": 7
}
},
"frequency": "868100000"
},
"received_at": "2025-11-02T10:30:00Z"
},
"locations": {
"user": {
"latitude": 52.0116,
"longitude": 4.7104,
"altitude": 5,
"source": "SOURCE_REGISTRY"
}
}
}

Simulate multiple devices simultaneously:

Terminal window
# Simulate 5 devices
NUM_DEVICES=5 npm run simulator

Each device:

  • Has a unique Device EUI (0000000000000001, 0000000000000002, etc.)
  • Has a unique location from the poldermill list
  • Operates independently with randomized states
  • Sends measurements with slight time offsets (0-2 second stagger)

Devices Overview:

http://localhost:4321/devices

Shows all simulated devices with status indicators.

Filter by Device:

http://localhost:4321/dashboard?device=0000000000000001

Shows data for a specific device only.

All Devices:

http://localhost:4321/dashboard

Shows combined data from all devices.

  1. Start Astro Dev Server:

    Terminal window
    npm run dev
  2. Open Dashboard in browser:

    http://localhost:4321/devices
  3. Start Simulator in new terminal:

    Terminal window
    npm run simulator
  4. Watch Live Updates as data appears in dashboard

  5. Stop Simulator with Ctrl+C to see statistics:

    === Simulator Stopped ===
    Mallemolen (0000000000000001): 127 frames sent
    De Roos (0000000000000002): 125 frames sent
    Molen de Valk (0000000000000003): 128 frames sent

Start both Astro and simulator together:

Terminal window
npm run dev:dashboard

This runs npm run dev and npm run simulator concurrently.

High-Frequency Updates:

Terminal window
INTERVAL=3000 npm run simulator # Every 3 seconds

Single Device Testing:

Terminal window
NUM_DEVICES=1 npm run simulator

Many Devices (Stress Test):

Terminal window
NUM_DEVICES=10 INTERVAL=5000 npm run simulator

Custom Endpoint (testing deployed site):

Terminal window
LOCAL_ENDPOINT=https://your-site.vercel.app/api/uplink npm run simulator

Problem: Error: ECONNREFUSED

Solution:

  1. Ensure Astro dev server is running (npm run dev)
  2. Check endpoint URL matches server address
  3. Verify firewall allows local connections

Problem: Simulator runs but dashboard shows no data

Solution:

  1. Check browser console for errors
  2. Verify SSE connection at /api/stream
  3. Check Astro console for uplink processing messages
  4. Ensure database is initialized (SQLite file created)

Problem: [TTN ERROR] HTTP 401 or HTTP 404

Solution:

  1. Verify TTN_APP_ID matches your TTN application
  2. Check TTN_API_KEY is valid and has correct permissions
  3. Ensure webhook is configured in TTN console
  4. Verify TTN_REGION matches your application region

Problem: Simulator data rejected in production

Expected Behavior: This is intentional for security.

Solution:

  • Use simulator only in development (npm run dev)
  • Use real devices or TTN webhook in production

Edit simulator/device-simulator.mjs to customize:

Add New Location:

const DEVICE_LOCATIONS = [
// ... existing locations ...
{
name: 'My Poldermill',
city: 'Utrecht',
latitude: 52.0907,
longitude: 5.1214,
altitude: 8
},
];

Change Firmware Version:

const deviceInfo = {
// ... existing fields ...
hwVersion: { major: 3, minor: 7 },
fwVersion: { major: 3, minor: 7 },
};

Customize Test Data:

function generateTestData() {
// Create your own test payload format
return Buffer.from([0x01, 0x02, 0x03, ...]);
}

Import and use programmatically in your own scripts:

import { DeviceSimulator } from './simulator/device-simulator.mjs';
const simulator = new DeviceSimulator({
name: 'Test Device',
devEui: '0000000000000099',
location: { latitude: 52.0, longitude: 4.7, altitude: 5 },
hwVersion: { major: 3, minor: 7 },
fwVersion: { major: 3, minor: 7 },
});
await simulator.start();