iot-data-generator (mer)
July 2, 2026 · View on GitHub
A developer-friendly IoT test data generator CLI written in Rust.
Generate realistic IoT sensor payloads and send them to your system over MQTT, HTTP, or TCP — with zero infrastructure required to get started.
This is not a load-testing tool. It is focused on high-quality, realistic IoT test data.
Demo
From setup to sending data over MQTT:

Features
- Random payload generation — temperature, humidity, voltage, current, power, energy, status
- Custom Handlebars templates — full control over payload structure
- Three protocols — MQTT (plain & TLS), HTTP, TCP
- Authentication — username/password, Bearer token, API key header
- Environment variable expansion — keep secrets out of config files with
${VAR_NAME} - Validation & preview — check config and see sample payloads before sending
- Run control — total message count, interval, or hard time limit
- Cross-platform — Linux, macOS, Windows (prebuilt binaries available)
- Docker ready — single image, compose with a local Mosquitto broker included
Installation
Option 1 — crates.io (recommended)
Install the latest release straight from crates.io with Cargo (installs the mer binary):
cargo install mer-iot
Requires Rust (stable). Upgrade later with cargo install mer-iot --force.
Option 2 — Prebuilt binary
Download the latest binary for your platform from GitHub Releases. Binaries are built automatically when a release is published:
# Linux x86_64
curl -L https://github.com/iotmertech/iot-data-generator/releases/latest/download/mer-latest-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv mer /usr/local/bin/
# Linux ARM64
curl -L https://github.com/iotmertech/iot-data-generator/releases/latest/download/mer-latest-aarch64-unknown-linux-gnu.tar.gz | tar xz
sudo mv mer /usr/local/bin/
# macOS Apple Silicon
curl -L https://github.com/iotmertech/iot-data-generator/releases/latest/download/mer-latest-aarch64-apple-darwin.tar.gz | tar xz
sudo mv mer /usr/local/bin/
# macOS Intel
curl -L https://github.com/iotmertech/iot-data-generator/releases/latest/download/mer-latest-x86_64-apple-darwin.tar.gz | tar xz
sudo mv mer /usr/local/bin/
# Windows (PowerShell)
Invoke-WebRequest -Uri "https://github.com/iotmertech/iot-data-generator/releases/latest/download/mer-latest-x86_64-pc-windows-msvc.zip" -OutFile mer.zip
Expand-Archive mer.zip -DestinationPath .
# Add mer.exe to your PATH
Option 3 — Build from source
Requires Rust (stable):
git clone https://github.com/iotmertech/iot-data-generator.git
cd iot-data-generator
cargo build --release
# Binary: ./target/release/mer
sudo cp target/release/mer /usr/local/bin/ # optional: add to PATH
Option 4 — Docker
# Pull and run (no local Rust needed)
docker run --rm ghcr.io/iotmertech/mer:latest --help
# Run an example against a local broker
docker run --rm \
-v $(pwd)/examples:/app/examples:ro \
--network host \
ghcr.io/iotmertech/mer:latest \
mqtt run -f /app/examples/mqtt-basic.yaml
Option 5 — Docker Compose (bring your own broker)
Runs mer in a container and connects to your existing broker:
git clone https://github.com/iotmertech/iot-data-generator.git
cd iot-data-generator
cp .env.example .env # set MQTT_BROKER and MQTT_TOPIC
docker compose up
For brokers that require auth, edit the command in docker-compose.yml to use docker/mer.docker-auth.yaml and fill in MQTT_USERNAME / MQTT_PASSWORD in your .env.
No broker is bundled —
merconnects to whatever broker you point it at.
Quickstart
1. Generate a starter config
mer init --protocol mqtt -f mer.yaml
On Windows, prefer -f over shell redirection (> mer.yaml). PowerShell writes UTF-16 by default, which older mer versions could not read.
2. Preview payloads
mer preview payload -f mer.yaml
mer preview payload -f mer.yaml -n 5 # show 5 samples
3. Validate the config
mer validate config -f mer.yaml
4. Send data
mer mqtt run -f mer.yaml
mer http run -f mer.yaml
mer tcp run -f mer.yaml
Commands
| Command | Description |
|---|---|
| `mer init --protocol <mqtt | http |
mer validate config -f <file> | Validate a config file (exits 0 on success) |
mer preview payload -f <file> [-n N] | Preview N generated payloads (default 3) |
mer mqtt run -f <file> | Send MQTT messages |
mer http run -f <file> | Send HTTP requests |
mer tcp run -f <file> | Send TCP messages |
Config Reference
MQTT
protocol: mqtt
target:
broker: "mqtt://mqtt.iotmer.cloud:1883" # or mqtts:// for TLS (port 8883)
topic: "devices/{device_id}/telemetry"
client_id: "mer-{device_id}" # optional, auto-generated if omitted
qos: 1 # 0, 1, or 2
retain: false
device:
count: 5
id_prefix: device # device IDs: device-0000, device-0001, …
payload:
mode: random # or template
run:
total_messages: 100
interval_ms: 1000 # ms between messages
duration_secs: 60 # optional hard time limit
HTTP
protocol: http
target:
url: "http://ingest.iotmer.cloud:8080/api/v1/devices/{device_id}/data"
method: POST # GET, POST, PUT, PATCH
timeout_secs: 10
headers:
Content-Type: "application/json"
X-Custom-Header: "value"
device:
count: 3
id_prefix: sensor
payload:
mode: random
run:
total_messages: 50
interval_ms: 500
TCP
protocol: tcp
target:
host: localhost
port: 9000
timeout_secs: 5
line_delimiter: true # append \n to each message
device:
count: 2
id_prefix: node
payload:
mode: random
run:
total_messages: 20
interval_ms: 1000
Simulated Timestamps
By default each payload's timestamp is the current wall-clock time. Add an
optional time section to generate a simulated clock instead — useful for
backfilling historical data or producing deterministic, evenly-spaced series.
time:
mode: fixed # real | fixed | random (default: real)
start: "2026-01-01T00:00:00Z" # also accepts "2026-01-01 00:00:00" (UTC)
step_secs: 300 # fixed mode: +5 min per message
min_secs: 60 # random mode: minimum +1 min
max_secs: 1800 # random mode: maximum +30 min
field: ts # JSON key for the timestamp (default "ts")
real— current time (default, unchanged behavior).fixed—start + seq * step_secs. Deterministic and evenly spaced (e.g. start at2026-01-01 00:00:00, then00:05:00,00:10:00, …).random— starts atstart, then advances by a random amount betweenmin_secsandmax_secsfor each message.
The clock is global and advances once per message (in the same order as the
seq counter), shared across all devices.
In template mode the simulated timestamp is available as {{ts}} (RFC3339).
{{now_utc}} always returns real wall-clock time regardless of the time
setting.
Payload Modes
random (default)
Generates a realistic IoT JSON payload automatically:
{
"device_id": "device-0001",
"device_index": 1,
"seq": 42,
"ts": "2024-01-01T12:00:00Z",
"temperature": 23.45,
"humidity": 61.2,
"voltage": 230.5,
"current": 3.2,
"power": 738.0,
"energy_total": 1523.7,
"status": "online"
}
Field ranges:
| Field | Range |
|---|---|
temperature | 15.0 – 45.0 °C |
humidity | 20.0 – 95.0 % |
voltage | 220.0 – 240.0 V |
current | 0.1 – 15.0 A |
power | 10.0 – 3600.0 W |
energy_total | 0.0 – 10000.0 kWh |
status | online (4/6), offline (1/6), error (1/6) |
template
payload:
mode: template
template_inline: |
{
"device_id": "{{device_id}}",
"ts": "{{now_utc}}",
"temperature": {{random_float 18.0 35.0}},
"humidity": {{random_float 30.0 90.0}},
"active": {{random_bool}},
"seq": {{seq}},
"level": {{random_int 0 5}}
}
Or load from an external file:
payload:
mode: template
template_file: ./my-template.json
Template helpers
| Helper | Example | Output |
|---|---|---|
{{now_utc}} | — | 2024-01-01T12:00:00Z |
{{ts}} | — | simulated time (see time config) |
{{random_int min max}} | {{random_int 0 100}} | 42 |
{{random_float min max}} | {{random_float 10.0 50.0}} | 27.35 |
{{random_bool}} | — | true or false |
{{device_id}} | — | sensor-0003 |
{{device.index}} | — | 3 |
{{seq}} | — | 17 |
{{seq_pulse min max peak_at total_steps}} | {{seq_pulse 8.0 90.0 1344 2688}} | triangle: min at edges, max at peak_at |
{{seq_inv_pulse min max peak_at total_steps}} | {{seq_inv_pulse 0.80 0.99 1344 2688}} | inverted triangle: max at edges, min at peak_at |
{{seq_after offset scale}} | {{seq_after 1344 0.5}} | delayed ramp: max(0, seq - offset) * scale |
{{seq_pulse_rand min max peak_min peak_max total_steps}} | {{seq_pulse_rand 8.0 90.0 1000 1700 2688}} | triangle with a random peak in [peak_min, peak_max] (stable per device) |
{{seq_inv_pulse_rand min max peak_min peak_max total_steps}} | {{seq_inv_pulse_rand 0.80 0.99 1000 1700 2688}} | inverted triangle sharing the device's random peak |
{{seq_cum_energy step_min step_max peak_at total_steps [readings_per_day]}} | {{seq_cum_energy 2.0 18.0 1344 2688 48}} | monotonic cumulative energy (kWh / kVArh); daily increments vary |
The seq_* helpers read the current message seq (and device index)
automatically from the context — you don't pass them as arguments. They're handy
for shaping values over a run (e.g. a mid-cycle triangle load, a cos φ dip,
cumulative energy registers, or a compensation bank that starts accumulating after
a threshold).
Use the _rand variants when each device should peak at a different, random
time: the peak is drawn once from [peak_min, peak_max] and stays fixed for
that device's whole run. All _rand helpers for a given device share the same
peak, so active/reactive load and the cos φ dip line up in time. See
energy-triangle.yaml.
Authentication
Username / Password (MQTT, HTTP)
auth:
type: username_password
username: myuser
password: "${MQTT_PASSWORD}"
For MQTT, you can also set credentials directly under target:
target:
broker: "mqtts://mqtt.iotmer.cloud:8883"
topic: "test/{device_id}"
auth:
username: myuser
password: "${MQTT_PASSWORD}"
Bearer Token (HTTP)
auth:
type: bearer
token: "${API_TOKEN}"
API Key Header (HTTP)
auth:
type: api_key
header: X-API-Key
value: "${API_KEY}"
Environment Variables
Use ${VAR_NAME} anywhere in your config to inject secrets at runtime:
auth:
type: bearer
token: "${MY_API_TOKEN}"
export MY_API_TOKEN="secret-token"
mer http run -f mer.yaml
If a variable is not set, mer exits with a clear error:
Error: Environment variable 'MY_API_TOKEN' is not set
Run Summary
After each run, mer prints a summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Run Summary — MQTT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total sent : 100
Successful : 98
Failed : 2
Success rate : 98.0%
Errors : 2
Bytes sent : 24.5 KB
Elapsed : 100.12s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Examples
The [examples/](examples/) directory contains ready-to-use configs:
| File | Description |
|---|---|
[mqtt-basic.yaml](examples/mqtt-basic.yaml) | 5 devices, 20 messages, random payload, MQTT localhost |
[demo-mqtt.yaml](examples/demo-mqtt.yaml) | Demo: mqtt.iotmer.cloud, public broker, README GIF |
[mqtt-auth.yaml](examples/mqtt-auth.yaml) | MQTT with username/password auth |
[mqtt-tls.yaml](examples/mqtt-tls.yaml) | MQTT over TLS (mqtts://) with auth |
[http-basic.yaml](examples/http-basic.yaml) | 3 sensors, HTTP POST with custom header |
[http-bearer.yaml](examples/http-bearer.yaml) | HTTP POST with Bearer token auth |
[http-api-key.yaml](examples/http-api-key.yaml) | HTTP POST with API key header auth |
[tcp-basic.yaml](examples/tcp-basic.yaml) | 2 nodes, TCP with line delimiter |
[custom-template.yaml](examples/custom-template.yaml) | MQTT with inline Handlebars template |
[smart-home.yaml](examples/smart-home.yaml) | Smart home sensors (temp, plug, door, CO₂, lux) |
[industrial.yaml](examples/industrial.yaml) | Factory floor machines (vibration, pressure, flow, RPM) |
[fleet-tracking.yaml](examples/fleet-tracking.yaml) | Vehicle GPS tracking over HTTP |
[energy-meter.yaml](examples/energy-meter.yaml) | Three-phase smart energy meters over MQTT |
[weather-station.yaml](examples/weather-station.yaml) | Weather stations over TCP (wind, rain, UV, pressure) |
[duration-limited.yaml](examples/duration-limited.yaml) | Time-capped run with duration_secs |
[time-fixed.yaml](examples/time-fixed.yaml) | Simulated timestamps, fixed +5 min step |
[time-random.yaml](examples/time-random.yaml) | Simulated timestamps, random 1–30 min step |
[time-template.yaml](examples/time-template.yaml) | Simulated {{ts}} inside a custom template |
[energy-triangle.yaml](examples/energy-triangle.yaml) | Mid-month triangle load, cos φ dip & compensation ramp |
Development
# Build
cargo build
# Run tests
cargo test
# Lint
cargo clippy -- -D warnings
# Format
cargo fmt
# All checks (same as CI)
make check
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contribution guide.
Security
Please read our Security Policy before reporting vulnerabilities.
Key points:
- Never commit plaintext secrets — use
${ENV_VAR}in config files - Use
mqtts://for production MQTT connections - Use
https://for production HTTP endpoints
License
MIT © 2026 iotmertech