A production-style embedded Linux telemetry daemon written in C++ that publishes structured, versioned sensor data over MQTT. Designed to model real-world device behavior including resilient reconnect logic, runtime configuration, graceful shutdown, and device presence tracking via MQTT Last Will & Testament (LWT).
For a quick end-to-end run, see Quick Start.
This project implements a long-running Linux service that periodically samples one or more sensors and publishes telemetry to an MQTT broker. The daemon is designed to behave like a real embedded/IoT device rather than a one-off publisher or demo script.
Key design goals:
- Non-blocking operation
- Resilience to network and broker failures
- Runtime configurability without recompilation
- Clean startup/shutdown semantics
- Production-style deployment via systemd
- Asynchronous MQTT client (libmosquitto)
- Non-blocking reconnect logic with exponential backoff
- Structured, versioned JSON telemetry payloads
- Runtime configuration via JSON (broker, metrics, QoS, intervals, logging)
- Multiple metric support with independent topics
- Graceful shutdown via SIGINT / SIGTERM
- Device presence tracking using MQTT Last Will & Testament (LWT)
- Retained online status on connect
- Retained offline status on crash or power loss
- Retained offline status on clean shutdown
- Thread-safe logging with runtime-configurable log levels
- systemd service unit with basic hardening
- Docker-hosted MQTT broker for local testing
Note:
--print-configparses and prints the effective configuration (defaults applied) and exits without starting MQTT.
The project is intentionally structured with clear separation of concerns:
- Config: JSON parsing + validation (
AppConfig) - Transport: MQTT connection management + publishing (
MqttClient) - Schema: Telemetry / status payload formats (versioned)
- Sensors: Pluggable sensor interface (
ISensor) with simulated sensors included - Application: Main loop + lifecycle management (signals, systemd-friendly behavior)
This structure allows real hardware sensors to be added later with minimal changes.
- Telemetry:
- 'devices/<client_id>/<topic_suffix>'
- Device status (LWT):
- 'devices/<client_id>/status'
- Health/heartbeat:
- 'devices/<client_id>/health'
Start a local MQTT broker
docker run -d --name mqtt -p 1883:1883 eclipse-mosquitto:2Build the daemon
git clone https://github.com/jhenkler/embedded-linux-telemetry-daemon.git
cd embedded-linux-telemetry-daemon
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -jDependencies: CMake, a C++20 compiler, and
libmosquitto-dev
Run with the example configuration
./build/embedded-linux-telemetry-daemon config/config.jsonThe daemon will:
- Connect to the MQTT broker
- Publish telemetry periodically
- Publish retained online/offline status (LWT)
- Automatically reconnect with backoff on disconnect
Observe telemetry
mosquitto_sub -h localhost -p 1883 -t "devices/#" -vYou should see JSON telemetry and status messages similar to:
devices/pi-sim-01/status {"device":{"client_id":"pi-sim-01"},"schema_version":1,"state":"online","timestamp_s":1771375762}
devices/pi-sim-01/health {"counters":{"publish_fail":14,"publish_ok":18,"reconnects":7},"device":{"client_id":"pi-sim-01"},"schema_version":1,"seq":15,"timestamp_s":1771375777,"uptime_s":15}
devices/pi-sim-01/temp {"device":{"client_id":"pi-sim-01"},"metric":{"name":"temperature","unit":"C","value":85.0},"schema_version":1,"seq":17,"timestamp_s":1771375779}
devices/pi-sim-01/humidity {"device":{"client_id":"pi-sim-01"},"metric":{"name":"humidity","unit":"%","value":382.5},"schema_version":1,"seq":17,"timestamp_s":1771375779}Stop
Ctrl+C
docker stop mosquitto && docker rm mosquittoRun Tests
cmake -S . -B build -DENABLE_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failureUnit tests focus on reconnect/backoff behavior, configuration parsing, and schema correctness.
- C++20 compiler
- CMake ≥ 3.16
- libmosquitto (runtime + development)
- nlohmann/json
On Debian/Ubuntu:
sudo apt install libmosquitto-dev libmosquitto1 nlohmann-json3-devmkdir build
cd build
cmake ..
cmake --build .docker run -d --name mqtt -p 1883:1883 eclipse-mosquitto:2The daemon is configured via a JSON file passed at runtime.
Example config.json
{
"log_level": "info",
"broker": {
"host": "localhost",
"port": 1883,
"keepalive_s": 10
},
"client_id": "pi-sim-01",
"interval_ms": 1000,
"qos": 1,
"retain": false,
"metrics": [
{ "name": "temperature", "unit": "C", "start": 20.0, "step": 0.25, "topic_suffix": "temp"},
{ "name": "humidity", "unit": "%", "start": 45.0, "step": 0.5, "topic_suffix": "humidity"}
]
}./build/embedded-linux-telemetry-daemon config/config.jsonTo observe telemetry:
mosquitto_sub -h localhost -p 1883 -t "devices/#" -v# Show version
./build/embedded-linux-telemetry-daemon --version
# Validate config
./build/embedded-linux-telemetry-daemon --print-config /etc/embedded-linux-telemetry-daemon/config.json
# Normal run
./build/embedded-linux-telemetry-daemon /etc/embedded-linux-telemetry-daemon/config.jsonThe daemon publishes device presence using MQTT retained messages.
devices/<client_id>/status- On successful connection: publishes online (retained)
- On crash / power loss: broker publishes offline via LWT
- On clean shutdown: daemon publishes offline before disconnecting
mosquitto_sub -h localhost -p 1883 -t "devices/+/status" -vKill the daemon ungracefully:
kill -9 <pid>You should see a retained offline message.
A sample systemd unit file is provided.
sudo cp systemd/embedded-linux-telemetry-daemon.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now embedded-linux-telemetry-daemonjournalctl -u embedded-linux-telemetry-daemon -fThe service includes basic hardening options such as NoNewPrivileges, PrivateTmp, and ProtectSystem.
This project was built to demonstrate:
- Embedded Linux service design
- Real-world MQTT usage patterns
- Fault-tolerant networked software
- Clean C++ architecture for long-running systems
- Operational awareness (logging, systemd, lifecycle management)
It intentionally avoids UI, databases, or cloud dependencies to focus on core embedded and systems engineering concerns.
Possible follow-on projects:
- Real hardware sensors (I2C/SPI)
- TLS-secured MQTT
- Metrics aggregation service
- Embedded build integration (Yocto)
These are intentionally left out to keep this project focused and production-shaped.