USB-Raumluft-Sensor-Treiber mit MQTT-Anbindung fuer Linux. Liest VOC-Messwerte (Volatile Organic Compounds) von einem USB-Luftqualitaetssensor und publiziert diese an einen MQTT-Broker zur Einbindung in Heimautomatisierungssysteme.
Der Sensor wird unter verschiedenen Marken vertrieben:
- Conrad Raumluft-Qualitaetssensor
- REHAU Raumluft-Sensor
- Weitere Geraete mit Atmel-Chip (USB Vendor
0x03eb, Product0x2013)
Zur Identifikation des Sensors:
lsusb | grep 03eb:2013Erwartete Ausgabe:
Bus 00x Device 00x: ID 03eb:2013 Atmel Corp.
docker run --rm --privileged --device=/dev/bus/usb -v /sys:/sys:ro \
-e MQTT_BROKERNAME=192.168.1.10 \
-e MQTT_PORT=1883 \
-e MQTT_CLIENTID=airsensor \
-e MQTT_TOPIC=home/CO2/voc \
ghcr.io/olcond/airsensorDas Docker-Image ist als Multi-Arch-Build fuer folgende Plattformen verfuegbar:
| Architektur | Beispiel-Hardware |
|---|---|
linux/amd64 |
Standard-PCs, Intel NUCs |
linux/arm/v7 |
Raspberry Pi 2/3 (32-Bit) |
linux/arm64 |
Raspberry Pi 3/4/5 (64-Bit) |
Docker-Image von GitHub Container Registry:
docker pull ghcr.io/olcond/airsensor:latestOder lokal bauen:
git clone https://github.com/volschin/airsensor-mqtt.git
cd airsensor-mqtt
docker build -t airsensor-mqtt .- GCC-Compiler
- libusb 1.0 Entwicklungsbibliothek
- Paho MQTT C Client Entwicklungsbibliothek (TLS-faehig)
- OpenSSL Entwicklungsbibliothek
- pthread
Unter Debian/Ubuntu:
sudo apt-get install gcc libusb-1.0-0-dev libpaho-mqtt-dev libssl-devgcc -o airsensor airsensor.c -lusb-1.0 -lpaho-mqtt3cs -lpthread -lssl -lcryptoDie gesamte MQTT-Konfiguration erfolgt ueber Umgebungsvariablen. Alle Variablen haben Standardwerte und sind optional -- fehlende Variablen werden automatisch mit den Standardwerten belegt.
| Variable | Beschreibung | Standard |
|---|---|---|
MQTT_BROKERNAME |
Hostname oder IP-Adresse des MQTT-Brokers | 127.0.0.1 |
MQTT_PORT |
Port des MQTT-Brokers | 1883 |
MQTT_CLIENTID |
Client-ID fuer die MQTT-Verbindung | airsensor |
MQTT_TOPIC |
MQTT-Topic fuer Sensordaten (JSON) | home/CO2/voc |
MQTT_USERNAME |
Benutzername fuer MQTT-Authentifizierung (optional) | (keiner) |
MQTT_PASSWORD |
Passwort fuer MQTT-Authentifizierung (optional) | (keines) |
HA_DISCOVERY_PREFIX |
Praefix fuer Home Assistant Auto-Discovery | homeassistant |
HA_DEVICE_NAME |
Geraetenamen im Home Assistant | Air Sensor |
POLL_INTERVAL |
Messintervall in Sekunden (10--3600) | 30 |
USB_TIMEOUT |
USB-Timeout in Millisekunden (250--10000) | 1000 |
MAX_RETRIES |
Maximale USB-Fehler vor Reconnect (1--20) | 3 |
MQTT_TLS |
TLS-Verschluesselung aktivieren (1 oder true) |
(deaktiviert) |
./airsensor [-d] [-v] [-o] [-h]
| Option | Beschreibung |
|---|---|
-d |
Debug-Modus: Ausfuehrliche Ausgaben fuer Fehlersuche |
-v |
Nur VOC-Wert ausgeben (Werte ausserhalb 450--2000 ppm werden als 0 ausgegeben) |
-o |
Einzelmessung: einmal lesen, dann beenden |
-h |
Hilfe anzeigen und beenden |
Hinweis: Im Docker-Image ist
-vals Standard-Option im ENTRYPOINT konfiguriert.
Dauerbetrieb mit Debug-Ausgaben:
./airsensor -dEinzelmessung (z.B. fuer Cronjob):
./airsensor -v -oDocker mit Authentifizierung:
docker run --rm --privileged --device=/dev/bus/usb -v /sys:/sys:ro \
-e MQTT_BROKERNAME=mqtt.example.com \
-e MQTT_PORT=1883 \
-e MQTT_CLIENTID=wohnzimmer-sensor \
-e MQTT_TOPIC=wohnung/wohnzimmer/luftqualitaet \
-e MQTT_USERNAME=mqttuser \
-e MQTT_PASSWORD=geheim \
ghcr.io/olcond/airsensorDocker mit TLS-Verschluesselung:
docker run --rm --privileged --device=/dev/bus/usb -v /sys:/sys:ro \
-e MQTT_BROKERNAME=mqtt.example.com \
-e MQTT_PORT=8883 \
-e MQTT_TLS=1 \
-e MQTT_USERNAME=mqttuser \
-e MQTT_PASSWORD=geheim \
ghcr.io/olcond/airsensorservices:
airsensor:
image: ghcr.io/olcond/airsensor:latest
container_name: airsensor
restart: unless-stopped
privileged: true
devices:
- /dev/bus/usb:/dev/bus/usb
volumes:
- /sys:/sys:ro
environment:
MQTT_BROKERNAME: "192.168.1.10"
MQTT_PORT: "1883"
MQTT_CLIENTID: "airsensor"
MQTT_TOPIC: "home/CO2/state"
MQTT_USERNAME: ""
MQTT_PASSWORD: ""
HA_DISCOVERY_PREFIX: "homeassistant"
HA_DEVICE_NAME: "Air Sensor"
POLL_INTERVAL: "30"Hinweis: Der Container benoetigt
privileged: trueund Zugriff auf/sys(read-only), da libusb 1.0 sowohl die USB-Devicenodes als auch sysfs fuer die Geraeteerkennung benoetigt.
Der Sensor liefert VOC-Werte im Bereich von 450 bis 2000 ppm (laut Spezifikation von AppliedSensor). Intern akzeptiert die Software Werte bis 15001 ppm.
| Bereich (ppm) | Luftqualitaet |
|---|---|
| 450--600 | Ausgezeichnet |
| 600--1000 | Gut |
| 1000--1500 | Maessig |
| 1500--2000 | Schlecht |
Der Sensor liefert folgende Messwerte in einem 16-Byte USB-Response (Little-Endian):
| Wert | Bytes | Beschreibung |
|---|---|---|
| VOC | 2--3 | Volatile Organic Compounds in ppm |
| Debug | 4--5 | Interner Debug-Wert |
| PWM | 6--7 | Heizungs-PWM-Wert |
| r_h | 8--9 | Heizungswiderstand (Rohwert ÷ 100 = Ω) |
| r_s | 12--14 | Sensorwiderstand (24-Bit, in Ω) |
Standard-Modus (ohne -v):
2026-02-28 14:30:15, VOC: 523, RESULT: OK
Bei Werten ausserhalb des gueltigen Bereichs:
2026-02-28 14:30:15, VOC: 12345, RESULT: Error value out of range
VOC-only-Modus (-v):
523
Bei Werten ausserhalb des Bereichs wird 0 ausgegeben.
Alle Messwerte werden als JSON-Objekt auf das konfigurierte Topic publiziert:
- Topic: konfigurierbar ueber
MQTT_TOPIC - Payload: JSON, z.B.
{"voc":523,"r_h":382.21,"r_s":8900110,"debug":736,"pwm":10} - QoS: 1 (mindestens einmal zugestellt)
- Retained: Nein
Der Sensor publiziert seinen Online-Status auf {MQTT_TOPIC}/availability:
online— nach erfolgreicher MQTT-Verbindungoffline— bei sauberem Herunterfahren oder Verbindungsverlust (via MQTT Last Will and Testament)
Im Dauerbetrieb wird standardmaessig alle 30 Sekunden ein neuer Messwert gelesen und publiziert. Das Intervall ist ueber POLL_INTERVAL konfigurierbar (10--3600 Sekunden).
Bei USB-Kommunikationsfehlern versucht das Programm automatisch einen erneuten Leseversuch. Nach MAX_RETRIES aufeinanderfolgenden Fehlern wird die USB-Verbindung getrennt und neu aufgebaut.
Der Sensor unterstuetzt MQTT Auto-Discovery fuer Home Assistant. Beim Start werden automatisch Konfigurationsnachrichten auf die Discovery-Topics publiziert, sodass Home Assistant den Sensor ohne manuelle Konfiguration erkennt und einbindet.
Das Discovery-Topic hat das Format:
{HA_DISCOVERY_PREFIX}/sensor/{MQTT_CLIENTID}[_suffix]/config
Beispiel mit Standardwerten:
homeassistant/sensor/airsensor/config
Beim Start werden Discovery-Nachrichten fuer folgende Entitaeten publiziert:
| Entitaet | Suffix | Beschreibung |
|---|---|---|
| VOC | (keiner) | Volatile Organic Compounds (ppm) |
| Heating Resistance | _rh |
Heizungswiderstand (Ω) |
| Sensor Resistance | _rs |
Sensorwiderstand (Ω) |
| Warmup | _warmup |
Aufwaermzeit (min, diagnostisch) |
| Warn Threshold 1 | _warn1 |
Warnschwelle 1 (ppm, diagnostisch) |
| Warn Threshold 2 | _warn2 |
Warnschwelle 2 (ppm, diagnostisch) |
Alle Entitaeten enthalten:
object_idfuer vorhersagbare Entity-IDs (z.B.sensor.airsensor_voc)originBlock mit Integrationsname, Version und Support-URLavailability_topicfuer Online/Offline-Erkennung
Die Mess-Entitaeten (VOC, r_h, r_s) enthalten zusaetzlich:
state_class: measurementfuer Langzeitstatistikensuggested_display_precision(VOC: 0, r_h: 2, r_s: 0)expire_after(3× Messintervall) zum automatischen Markieren als unverfuegbaricon: mdi:resistorfuer die Widerstandssensoren (r_h, r_s)
Die diagnostischen Entitaeten (Warmup, Warn-Schwellen) werden nur publiziert, wenn der Sensor die entsprechenden Abfragen (FLAGGET?, KNOBPRE?) unterstuetzt.
Die VOC-Discovery-Konfiguration sieht beispielsweise so aus:
{
"name": "Air Sensor VOC",
"object_id": "airsensor_voc",
"state_topic": "home/CO2/voc",
"value_template": "{{ value_json.voc }}",
"unit_of_measurement": "ppm",
"device_class": "volatile_organic_compounds_parts",
"state_class": "measurement",
"suggested_display_precision": 0,
"unique_id": "airsensor_voc",
"availability_topic": "home/CO2/voc/availability",
"expire_after": 90,
"device": {
"identifiers": ["airsensor"],
"name": "Air Sensor",
"model": "USB VOC Sensor",
"manufacturer": "Atmel",
"serial_number": "ABC123",
"sw_version": "1.0"
},
"origin": {
"name": "airsensor-mqtt",
"sw_version": "0.10.0",
"support_url": "https://github.com/olcond/airsensor-mqtt"
}
}Hinweis: Die Felder
serial_numberundsw_versionim Device-Block werden nur dann gesetzt, wenn der Sensor auf die*IDN?-Abfrage beim Start antwortet. Bei aelteren Sensoren ohne diese Unterstuetzung entfallen diese Felder.
Der Geraetenamen und das Discovery-Praefix koennen ueber Umgebungsvariablen angepasst werden:
docker run --rm --privileged --device=/dev/bus/usb -v /sys:/sys:ro \
-e MQTT_BROKERNAME=192.168.1.10 \
-e MQTT_TOPIC=home/CO2/voc \
-e HA_DEVICE_NAME="Wohnzimmer Sensor" \
ghcr.io/olcond/airsensorFalls Auto-Discovery deaktiviert ist oder ein anderes Praefix verwendet wird, kann der Sensor manuell in der configuration.yaml eingetragen werden:
mqtt:
sensor:
- name: "Luftqualitaet VOC"
state_topic: "home/CO2/voc"
value_template: "{{ value_json.voc }}"
unit_of_measurement: "ppm"
device_class: volatile_organic_compounds_parts
state_class: measurement
availability_topic: "home/CO2/voc/availability"
icon: "mdi:air-filter"Item-Definition:
Number Luftqualitaet_VOC "Luftqualitaet [%d ppm]" {mqtt="<[broker:home/CO2/voc:state:default]"}
Einen mqtt in-Node konfigurieren mit dem Topic home/CO2/voc und dem entsprechenden Broker.
-
Pruefen, ob der Sensor angeschlossen ist:
lsusb | grep 03eb:2013 -
USB-Berechtigungen pruefen -- das Programm benoetigt Zugriff auf das USB-Geraet:
# Als root ausfuehren oder passende udev-Regel anlegen sudo ./airsensor -d -
udev-Regel fuer unprivilegierten Zugriff erstellen:
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="03eb", ATTR{idProduct}=="2013", MODE="0666"' \ | sudo tee /etc/udev/rules.d/99-airsensor.rules sudo udevadm control --reload-rules sudo udevadm trigger
Failed to connect, return code X
return code 1: Unzulaessige Protokoll-Versionreturn code 2: Client-ID abgelehntreturn code 3: Broker nicht erreichbarreturn code 4: Benutzername/Passwort falschreturn code 5: Nicht autorisiert
Pruefen:
- Ist der MQTT-Broker unter
MQTT_BROKERNAME:MQTT_PORTerreichbar? - Stimmen Benutzername und Passwort?
- Ist die Client-ID eindeutig (kein anderer Client mit derselben ID verbunden)?
Error: Device not found
Das Programm sucht bis zu 10 Mal im Abstand von ca. 11 Sekunden nach dem USB-Geraet. Falls es nicht gefunden wird:
- USB-Kabel/Anschluss pruefen
- Sensor an anderem USB-Port versuchen
- Im Docker-Container:
--device=/dev/bus/usbkorrekt angegeben?
Alle Umgebungsvariablen haben Standardwerte und sind optional. Falls dennoch ein Absturz auftritt, bitte im Debug-Modus (-d) ausfuehren und die Ausgabe pruefen.
Die Anwendung besteht aus airsensor.c (Hauptprogramm) und airsensor.h (gemeinsame Typen, reine Logikfunktionen und Makros). Unit-Tests (193 Assertions) befinden sich in tests/test_airsensor.c und koennen ohne Hardware ausgefuehrt werden (make test).
# Docker-Build testen
docker build -t airsensor-mqtt .
# Pre-commit-Hooks installieren
pip install pre-commit
pre-commit install
# Hooks manuell ausfuehren
pre-commit run --all-files- GitHub Container Registry: Automatischer Build und Push bei Aenderungen an
Dockerfileoderairsensor.cauf demmain-Branch - Monatlicher Rebuild: Am 28. jedes Monats (Sicherheitsupdates der Basis-Images)
- Versionierte Releases: Git-Tags (
vX.Y.Z) erzeugen ein entsprechendes Docker-Image-Tag
MIT License -- siehe LICENSE.
- Rodric Yates -- Originaler airsensor-linux-usb Treiber
- Ap15e (MiOS) -- Anpassungen fuer MiCasaVerde
- Sebastian Sjoholm -- Weitere Modifikationen
- Veit Olschinski -- MQTT-Integration und Docker-Paketierung