Heute baue ich eine CO2-Ampel, die zusätzlich Temperatur, Feuchtigkeit und Feinstaub messen kann. Diese schickt die Werte an einen Home Assistant. So kann ich dort mit einer Automatisierung auf schlechte Luftqualität reagieren, bzw. aufmerksam gemacht werden. Nach eineinhalb Jahren HomeOffice wird es mal Zeit. Im entvölkerten Office steht schon länger eine mit LoRa angebundene CO2 Ampel, die die Notwendigkeit des Lüftens meldet. Hierzu nutze ich den ESPHome Baukasten.
Als Basis hab ich mir den IKEA Vindriktning Sensor auserkoren. Er ist spottbillig für einen PM2.5 Feinstaubsensor, und soll laut Datenblatt (lokale Kopie) eine MTTF (mean time to failure) von >5 Jahren haben. Ob er das Versprechen einhält, wird sich noch zeigen müssen. Die Sensoren, die ich für das Luftdaten-Projekt im Einsatz hatte, haben fast das 2,5-fache gekostet und jeweils nur ~1 Jahr durchgehalten. Und die hatten weder Gehäuse noch ne schicke LED-Anzeige. Allerdings wurden diese auch (ganzjährig) draußen eingesetzt. Ein weiterer Vorteil ist, dass relativ viel Platz im Gehäuse ist, um den ESP32 und die restliche Sensorik unterzubekommen. Man kann den ESP Huckepack auf der RX-Datenleitung lauschen lassen, während die IKEA MCU weiterhin die Anfragen an den Sensor stellt und die LEDs entsprechend leuchten lässt. Will man nur den PM-Sensor aus dem Vindriktning looten und ohne die IKEA MCU verwenden, so geht dies ebenso.
Vindriktnings innere Werte
Das Gehäuse lässt sich durch Entfernen der 4 Schrauben auf der Rückseite öffnen. Anschließend die beiden Kabel des Feinstaubsensors mit der MCU trennen. Nun kann die Front entfernt werden.
Schauen wir uns das PCB mal etwas näher an. Wir sehen, dass wir gar nicht so viel sehen. Ein paar Widerstände, ein Kondensator, ein Linearregler und nen 8-beinigen Microchip. Dieser hat angenehmerweise all seine Beinchen an Diagnose-Pads herangeführt. Und 5V Spannung liegt dort ebenfalls an.
Abgesehen von +5V und GND, ist vor allem das mit RESET beschriftete Pad interessant, auf dem ich die serielle Kommunikation der MCU mit dem Sensor mitlauschen will. An dem LED_R_1 Pad liegt die Spannung der Photodiode an. Diese werde ich dafür verwenden, die Helligkeit der CO2 LED an die originale anzupassen.
PoC der Feinstaub- / CO2-Ampel auf dem Breadboard
Zunächst kompiliere ich mit dem Home Assistant Pluging eine quasi leere ESPHome Firmware und flashe diese auf den Wroom32. Die komplette .yaml Konfiguration findes sich im weiteren Verlauf dieses Artikels, daher verzichte ich an dieser Stelle auf die Snippets, die mich zum Endergebnis führen. Vor allem stecke ich jetzt einmal alles „trocken“ an die später vorgesehen Pins und teste es. Es ist mir nicht nur einmal passiert, dass ich beim finalen Aufbau noch einmal Pins umgelegt habe, die entweder das zukünftige OTA-Flashen oder gar das Booten des ESPs verhindern.
Sensorik
Der Sensirion SDC30 Sensor (CO2, Luftfeuchtigkeit, Temperatur) wird per I²C angesteuert. Folglich benötigt er +3,3V, GND von dem Development Board (später von dem MCP1825S 3,3V Festspannungsregler). In einem Internetforum hieß es, der SDC würde sich im Betrieb etwas erwärmen, was die Temperaturwerte verfälschen soll. Das scheint (zumindest in diesem Setup) aber kaum messbar zu sein. Nichtsdestotrotz habe ich an den I²C Bus zusätzlich noch einen BMP280 Temperatur / Luftdrucksensor mit angeschlossen. Wirklich notwendig ist der retrospektiv aber nicht – es sei denn man möchte unbedingt noch den Luftdruck messen. Weil ich aber sowieso ne Handvoll von dem BMPs hier rumfliegen und sonst keine andere Verwendung für sie hatte, hab ich ihn sogar final mit verbaut. Warum? Weil ich es kann!
Die TX Leitung des PM1006 (am RX UART der IKEA MCU) wird an einem beliebigen GPIO des ESPs angeschlossen. Es reicht, in diesem Betriebsmodus, lediglich den RX Pin in der ESPHome UART Konfiguration zu definieren. Für autonomen Betrieb des Sensors ohne IKEA MCU wird auch ein TX Port benötigt. Siehe Dokumentation des PM1006-Moduls von ESPHome.
Infrarot Empfänger und Sender
Auf dem Bild kann man noch 2 Geräte erkennen, die ich angeschlossen habe. Hierbei handelt es sich um einen Infrarot Empfänger und eine IR-Blaster Diode. Diese schließe ich zusätzlich an, weil dieser Sensor im Schlafzimmer seinen Dienst verrichten soll. Da ich gerne mit Musik oder zu einer Doku einschlafe, soll der Home Assistant später das Media-Geraffel abschalten, sobald Sleep as Android, über seinen Webhook den Eintritt in eine Tiefschlafphase meldet. Der IR Empfänger wird dabei lediglich initial zum Anlernen der IR-Codes genutzt.
Natürlich könnte man sich den Empfänger im finalen Ausbau der CO2-Ampel sparen, aber es ist so viel Platz im Gehäuse und vielleicht wird er später nochmal nützlich.
Status LED
Als letztes wird eine Status LED hinzugefügt. Der Sensor soll die CO2 Werte ja auch optisch analog zum Vindriktning anzeigen können. Hierfür hab ich mir ein Tütchen 10mm RGB-LEDs mit gemeinsamer Kathode (gemeinsamer Minuspol) geklickt. Ich habe hier pro Farbe einen 270Ω Widerstand verbaut. Vermutlich ginge auch ein einzelner Widerstand an der Kathode, aber ich denke, das könnte beim Farbmischen (Orange z. B.) zu inkonsistenter Helligkeit führen. Und Widerstände sind in meinem Privathackerspace auch hinreichend vorhanden.
Schaltplan
Als alles lief, habe ich einen Schaltplan in KiCad erstellt, als Dokumentation für mich selbst. Nach diesem Schaltplan werde ich später alles final verlöten. Das linke Bild ist der Schlafzimmer-Sensor mit dem zusätzlichen IR-Kram. Diesen habe ich für das Wohnzimmer weggelassen, da hier bereits alles smart ist.
Mir ist bewusst, dass der Infrarot-Blaster durch keinen Vorwiderstand geschützt ist. Ursprünglich war hier sogar einer vorgesehen, aber damit leuchtete sie zu schwach, sodass die Befehle bei den Geräten nicht ankamen. Andere Leute im Internet verstärken den Ausgang des ESPs sogar noch mit einem Transistor. Am Ende des Tages reden wir hier von sehr kurzen gepulsten Signalen.
Bisschen belastend ist aber, dass ich die Spannung der Photodiode direkt an einen ADC-Pin des ESP32 heranführe, da hier Spannungen von 0-5V ankommen. Der ESP32 verträgt eigentlich gut nur Spannungen bis 3.3V. Höhere Messwerte als 3,9V können auch gar nicht gemessen werden, selbst mit einer attenuation von 11dB. Bislang ist keiner der beiden ESPs gestorben, also scheint das irgendwie schon noch innerhalb der Toleranz zu liegen. Er muss auch nur Spannungen von <0,3V für die dunkle Darstellung von Spannungen darüber unterscheiden können. Das klappt so.
Beachten sollte man, dass die Photodiode deutlich empfindlicher im imfraroten Bereich als im sichtbaren ist. Auf Tageslicht reagiert sie ganz gut, auf künstliches weniger. Hier habe ich eine sehr helle LED-Taschenlampe zum Testen benutzt und direkt in die LED geleuchtet.
ESPHome Software für die CO2-Ampel kompilieren
Das Schöne an ESPHome ist, dass man kaum programmieren (können) muss, um eine Firmware zu erstellen. Man beschreibt einfach gesagt die Schnittstelle(n) des ESPs mit einer YAML Konfigurationsdatei. Den Rest macht ESPHome selbst. Am Ende herausgefallen kommt eine Binärdatei, die initial einmal per USB auf das Gerät geflasht wird. Fügt man den ‚ota‘ Block mit ein, kann man zukünftig direkt aus dem Webinterface von ESPHome heraus das Flashen „durch die Luft“ anstoßen.
Nachfolgend einige kommentierte Auszüge aus meiner Konfiguration. Die vollständigen Dateien finden sich bei mir im git: Schlafzimmer, Wohnzimmer
esphome:
name: sleepingroom
platform: ESP32
board: nodemcu-32s
wifi:
ssid: "Voltage-legacy"
password: !secret voltage_legacy_psk
use_address: sleepingroom.home
power_save_mode: high
fast_connect: on
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Schlafzimmer Fallback Hotspot"
password: !secret fallback_psk
captive_portal:
# Enable logging
logger:
level: DEBUG
# Enable Home Assistant API
api:
password: !secret api
# Enable over-the-air updates
ota:
password: !secret ota
Code-Sprache: YAML (yaml)
Zuerst definiere ich den Namen des Sensors und auf welcher Plattform und Board er laufen soll.
Anschließend zwinge ich ESPHome, den im lokalen DNS eingetragenen FQDN „sleepingroom.home“ statt Broadcast-Discover zu verwenden.
Damit der Fallback-AP Dinge tun kann, muss das captive Portal definiert sein.
API und OTA und Fallback-AP Updates sollten selbstverständlich geschützt sein, sonst kann der Sensor sehr einfach übernommen werden.
# Initialize I²C
i2c:
- id: bus_a
sda: 13
scl: 16
scan: true
# Initialize LEDC GPIOs
output:
- platform: ledc
pin: GPIO17
id: led_red
- platform: ledc
pin: GPIO18
id: led_green
max_power: 50%
- platform: ledc
pin: GPIO19
id: led_blue
# Initialize UART for Vindriktning sensor
uart:
rx_pin: GPIO26
baud_rate: 9600
Code-Sprache: PHP (php)
Hier definiere ich die Pins für I²C, die 3 PWM-Ausgänge für die 3 Farben der LED und den RX-Pin für den UART.
Den grünen Kanal für die LED schränke ich auf 50% Leistung ein, da sie sonst viel zu hell wäre und Farbverfälschungen die Folge wären.
# Define RGB mode for LED
light:
- platform: rgb
id: co2_light
name: "CO2 Status LED"
red: led_red
green: led_green
blue: led_blue
internal: true
Code-Sprache: PHP (php)
Für die Ansteuerung der LED verwende ich die RGB Plattform, die mir die manuelle Farbmischung abnimmt. Ich definiere sie als interne Komponente, da Farbe und Helligkeit auf Basis der Sensorwerte eingestellt werden. Täte ich das nicht, könnte man Farbe und Helligkeit wie bei jeder anderen smarten Lampe über ein Farbrad einstellen und sogar Effekte verwenden.
sensor:
# WiFi signal strength
- platform: wifi_signal
name: "WiFi Signalstärke"
update_interval: 60s
# SCD30 CO2 + temperature + humidity sensor
# * air quality category 0: LED is purple, sensor needs calibration (<380ppm)
# • air quality category 1: LED is green (380-800ppm)
# • air quality category 2: LED is yellow (900-1200ppm)
# • air quality category 3: LED is orange/amber (1200 bis 1600ppm)
# • air quality category 4: LED is red (1600-2000ppm)
- platform: scd30
i2c_id: bus_a
co2:
name: "Schlafzimmer CO2"
id: co2_value
accuracy_decimals: 1
on_value_range:
- below: 380
then:
- text_sensor.template.publish:
id: co2_warn
state: "Kalibrierung nötig"
- light.turn_on:
id: co2_light
red: 75%
green: 0%
blue: 100%
- above: 380
below: 800
[ ... ]
temperature:
name: "Schlafzimmer Temperatur"
accuracy_decimals: 2
humidity:
name: "Schlafzimmer Luftfeuchtigkeit"
accuracy_decimals: 1
# SCD30 temp sensor might be a bit off - this is'nt
temperature_offset: 0 °C
ambient_pressure_compensation: 1
automatic_self_calibration: True
address: 0x61
update_interval: 60s
Code-Sprache: PHP (php)
Nun definiere ich die Sensoren. Der ‚wifi_signal‘ Sensor meldet die aktuelle Empfangsstärke zum AP.
Die Angabe vom I²C Bus ist optional, die Angabe der Adresse nur bei mehreren Sensoren gleichen Typs verpflichtend. Der Ordnung halber, habe ich aber alles festgenagelt.
Sinnvoll ist es, ‚ambient_pressure_compensation‘ und ‚automatic_self_calibration“ zu aktivieren. Ersteres macht nur bei einem statisch platzierten Sensor Sinn, bei einem mobilen will man stattdessen ‚altitude_compensation‘ verwenden. Die automatische Kalibrierung beobachtet über einen 2 Wochen Zeitraum, CO2 Minimalmesswerte begleitet durch Temperatur- und Feuchtigkeitsschwankungen. So wird der Messwert auf den aktuellen durchschnittlichen CO2-Wert von ~400ppm kalibriert.
Mit ‚on_value_range‘ reagiert der ESP auf definierte Wertebereiche, um den Text-Sensor und die Farbe der LED zu setzen. Diese habe ich hier abgekürzt.
# Vindriktning particulate matter sensor
- platform: pm1006
pm_2_5:
name: "Feinstaub PM 2.5µm"
on_value_range:
- above: 0
below: 35
then:
- text_sensor.template.publish:
id: pm25_warn
state: "grün"
- above: 35
below: 85
then:
- text_sensor.template.publish:
id: pm25_warn
state: "orange"
- above: 85
then:
- text_sensor.template.publish:
id: pm25_warn
state: "rot"
Code-Sprache: CSS (css)
Da die IKEA MCU die verbauten LEDs bereits leuchten lässt, muss ich hier nichts weiter konfigurieren. Ich setze aber – wie schon beim CO2-Sensor – einen Text-Sensor basierend auf den Grenzwerten, bei denen die LED-Farbe wechselt.
# Analog photo diode voltage
- platform: adc
pin: 32
name: "Spannung Photodiode"
attenuation: 11db
update_interval: 1s
internal: true
on_value_range:
- below: 0.25
then:
light.turn_on:
id: co2_light
brightness: 30%
- above: 0.5
then:
light.turn_on:
id: co2_light
brightness: 60%
Code-Sprache: CSS (css)
Zu guter Letzt wird noch die Spannung der Photodiode gemessen, um die Helligkeit der CO2 LED einstellen zu können. Unter 0,25V soll die LED dunkel leuchten, über 0,5V hell. Die Helligkeit habe ich so eingestellt, dass sie sich mit den originalen LEDs in etwa deckt. Die Lücke zwischen den Wertebereichen ist absichtlich, um konsistentes Verhalten im Übergangsbereich zu gewährleisten.
CO2-Ampel in Home Assistant einbinden
Wenn der Sensor mit der neuen Firmware startet, sollte er sich nun direkt am Home Assistant melden. Folglich sollte jetzt bereits eine Benachrichtigung von der ESPHome Integration aufgeploppt sein. Folgt man dieser, kann man die Einbindung mit einem Klick starten, nach der API-Passworteingabe tauchen die Sensoren kurze Zeit später unter Geräte und Entitäten auf. Fügt man die Sensoren zu einer Lovelace Karte hinzu, könnte es folgendermaßen aussehen:
CO2 Ampel verlöten und zusammenbauen
Jetzt, da alles funktioniert und die Firmware installiert ist, kann das Verlöten der Einzelteile erfolgen. Meine erste Idee war, ein PCB mit Spannungsversorgung (3,3V Festspannungsregler) zu löten. Alle Pins vom ESP32 sollten zum Verteilerboard gehen und dort verteilt werden. Zusätzlich wollte ich Vorwiderstände für die LEDs hier ebenfalls unterbringen. Beim Außensensor hatte ich gute Erfahrungen damit gemacht, einfach die Käbelchen an die Lötpads des Wroom32 Moduls anzulöten – das waren aber nur 3 Stück. Ich gebs gerne zu, das war bei so vielen Käbelchen echt ne blöde Idee. Erstens ist es sehr fummelig, und zweitens die Verbindung nicht sehr stark. Das heißt ich musste beim Reinstopfen in das Gehäuse etliche abgelöste Pins wieder reparieren. Einer hat dann auch direkt ein Lötpad vom ESP abgerissen. Ich würde sowas nicht empfehlen, aber seht selbst.
2. Versuch
Weil das so blöd gelaufen ist, habe ich mir für den 2. Sensor vorher noch ein Breakout-Board besorgt, auf dem ich den ESP setzen und die Käbelchen gescheit anlöten kann. Die PSU habe ich diesmal einzeln an der Gehäuse Front befestigt und die LED-Vorwiderstände direkt an die LED auf ein PCB gelötet. So kann ich auf das Verteilerboard verzichten und alle verbliebenden Verbindungen an das Breakout-Board anlöten, was die Sache viel übersichtlicher und stabiler macht. Außerdem muss der Abgang zum ESP so nur noch 90° nach oben abgeknickt werden und es rutscht alles problemlos in das Gehäuse. Eine Plastiknase musste ich dabei abknipsen, da sie gegen das ESP-Board stieß.
So sieht das doch ganz gut aus. Seitdem tun beide Sensoren klaglos ihren Dienst und ich hab schon die ersten Erkenntnisse, unter welchen Bedingungen sich die Raumluft hier in der Wohnung wie schnell verschlechtert und ein Lüften angeraten ist. Hierzu habe ich eine einfache Automatisierung geschrieben, die auslöst, wenn Feinstaub- oder CO2-Warnwert länger als 10m lang „rot“ ist (und ich zu Hause bin). Der Home Assistant schickt dann eine persistente Push-Benachrichtigung auf mein Handy, die erst wieder entfernt wird, wenn die Luftqualität wieder „grün“ ist.