Du willst Temperatur, Luftfeuchte und andere Werte in deinen Zimmern überwachen und auswerten, aber bist nicht bereit, mehr als unbedingt nötig dafür zu bezahlen? Du kannst auch vieles einfach selbst bauen.
Hardware
Aufgrund von Wartbarkeit verwende ich kleine Breadboards und „D1 Mini„-Module mit ESP8266. Damit wär auch schon fast alles über notwendige Hardware gesagt. Alles übrige ist optional.

Sensoren
Es gibt eine Vielzahl von Sensoren. Anbei will ich auflisten, was ich bis zum Datum dieses Artikels schon ausprobiert habe.
- BME280 / BMP280 / BME680
- dieser misst Luftfeuchte, Temperatur und Luftdruck und kommuniziert mittels I2C. Nicht zu verwechseln mit dem BMP280, der nur Temperatur und Luftdruck misst.
- Der BME680 kann neben den Funktionen des BME280 auf noch Luftqualität messen. Er ist ein hervorragender Sensor für jeden Raum.
- MQ Gassensoren
- Allgemeines: benötigen mehr Strom, als das D1 Mini mittels USB liefern kann. Eine zusätzliche Stromversorgung mit Netzteil für dein Breadboard ist ggf. nötig. Nach einer Laufzeit von etwa einem Jahr mussten bei mir die MQ-Module getauscht werden, da sie keine guten Messergebnisse mehr lieferten. MQ-Module werden aufgeheizt und Messergenisse erst im warmen Zustand genau. Nach dem Start ist eine Wartezeit von min. 60s notwendig, bevor die Ergebnisse auswertbar sind.
- MQ2: reagiert auf Konzentrationen von auf LPG, i-Butan, Propan, Methan, Alkohol, Wasserstoff und Rauch und ist damit gut als Alarm-Sensor für Gaslecks und Rauch geeignet
- MQ3: reagiert auf Konzentrationen von Ethanol, Benzol, Methan, Hexan, LPG und Kohlenmonoxid. Eignet sich gut als Alkoholsensor.
- MQ4: Methan, Wassersoff, Kohlenmonoxid, Ethanol, LPG und Rauch
- MQ5: Methan, Wassersoff, Kohlenmonoxid, Ethanol und LPG (wie MQ4 ohne Rauch)
- MQ6: Methan, Wassersoff, Kohlenmonoxid, Ethanol und LPG (wie MQ5)
- MQ7: Methan, Wassersoff, Kohlenmonoxid, Ethanol und LPG (wie MQ5, jedoch mit empfindlicherem Messbereich)
- MQ8: Methan, Wassersoff, Kohlenmonoxid, Ethanol und LPG (wie MQ5)
- MQ9: Methan, Kohlenmonoxid (empfindlicher als obere Sensoren), LPG
- MQ131: Ozon
- MQ135: CO2 (400-600ppm), Kohlenmonoxid, Ethanol, Ammonium, Toluol, Aceton
- MQ303A: Isobutanol, Wasserstoff, Ethanol
- MQ309A: Wasserstoff, Methan, Kohlenmonoxid, Ethanol
- AS3935 Lightning Detector
- Blitzschlag-Sensor
- Geiger counter RadiationD v1.1 (CAJOE)
- Geigerzähler für Radioaktivität
- Anemometer (Selbstbau)
- Windstärke
- SDS011
- Feinstaub
- verdrahtete Fensterkontakte
- Dallas Temperature Sensor (One Wire)
- geschützter Temperaturfühler (für Wassertemperaturen)
- EC-Sensor
- zum Messen elektrischer Leitfähigkeit von Flüssigkeiten. Anwendungsbereich: Nährstoffe in Wasser
- PH-Sensor
- PH-Wert von Flüssigkeiten messen. Um die Wasserqualität für Pflanzen zu messen.
- TEMT6000
- Helligkeitsmesser
Software
Nun ist es müßig, für all deine Sensoren oder all deine ESPs separate Firmware zu programmieren. Also bin ich mit folgender Strategie daran gegangen:
Alle ESPs müssen die gleiche Firmware verwenden. Sie dürfen sich lediglich im Namen voneinander unterscheiden. Jedes ESP muss also all diese Geräte oben unterstützen und ob es vorhanden ist oder nicht automatisch erkennen.
Alle ESPs werden mit ESPHome konfiguriert. Ich habe ursprünglich die Firmware auch selbst geschrieben. Aber gute offene Arbeit nicht wiederzuverwenden ist dumm. ESPHome spart mir in der Entwicklung enorm Zeit. Außerdem kann ich Firmwareupdates direkt übers WLAN senden.
ESPHome ist Python-basiert. Verwendest du Linux, ist python schon vorinstalliert. Aber auch unter Windows lässt sich Python installieren. Sehr bequem geht das auch über PortablePython, wie ich es verwende.
pip install wheel
pip install esphome
Mehr ist es auch nicht.
Die ESPs kommunizieren ihre Werte mittels MQTT. Um Last zu verringern, lasse ich sie nicht laufend jeden Wert einzeln senden, sondern in regelmäßigen Abständen einen JSON-Datensatz mit allen verfügbaren Daten senden.
ESPHome-Konfiguration für ESP8266
Folgende Konfiguration kannst du verwenden, um relativ allgemein ESP8266 in deinen Räumen zu verteilen und mit jeweils im Kontext sinnvollen Sensoren auszustatten.
Vielleicht benötigst du in einigen Räumen spezialisierte Sensoren, in einigen Anderen aber nur Temperatur und Luftfeuchte. Folgender Konfigurationsvorschlag hilft dir, für alle deine Sensoren nur eine Firmware zu verwenden und damit auch nur einmal zu warten.
#**************************************************************
#* SmartHome ESP8266 Sensorik-Kern
#**************************************************************
#* ESP -- Sensor
#*
#* TX1 --
#* RX0 --
#* D1 -- SCL (AS3935 Lightning Detector) &&
#* [ SCL (BMP280) ] ODER [ SCL (TCA9548A Multiplexer) ]
#* D2 -- SDA (AS3935 Lightning Detector) &&
#* [ SDA (BMP280) ] ODER [ SDA (TCA9548A Multiplexer) ]
#* D3 -- IRQ (AS3935 Lightning Detector)
#* D4 -- VIN (Geiger Counter) &&
#* S (Anemometer) [Anemometer Power: middle; Ground: -]
#* [or other pulse countables]
#* GND -- GND (AS3935 Lightning Detector)
#* GND (Geigerzähler)
#* GND (SDS011)
#* 5V -- 5V (Geigerzähler)
#* 5V (SDS011)
#*
#* RST --
#* A0 -- [Free to use] [3V3-Level]
#* D0 -- [Free to use] [3V3-Level]
#* D5 -- OneWire Data
#* D6 -- UART TX (SDS011 TX)
#* D7 -- UART RX (SDS011 RX)
#* D8 -- [Free to use, with interrupt]
#* 3V3 -- SI (AS3935 Lightning Detector, activate I2C)
#* VCC (AS3935 Lightning Detector)
#* VIN (BME280)
#*
#* Fensterkontakte Anschließen:
#* 1 Pin auf 5V, der andere auf den Dx-Eingang
#*
#**************************************************************/
substitutions:
name: "YOUR DEVICE NAME"
wifi_ssid: "YOUR SSID"
wifi_password: "YOUR WIFI PASSWORD"
mqtt_broker: "YOUR MQTT BROKER"
mqttPath: "YOUR MQTT PATH FOR THE DEVICE"
mqtt_user: "YOUR USERNAME"
mqtt_passwort: "YOUR PASSWORD"
esphome:
name: "${name}"
includes:
- arduino_port_expander.h
esp8266:
board: d1_mini
#board: nodemcuv2
# Enable logging
logger:
#tx_buffer_size: 256
level: NONE
web_server:
port: 80
include_internal: true
ota:
# password: ""
wifi:
networks:
- ssid: "${wifi_ssid}"
password: "${wifi_password}"
reboot_timeout: 2min
fast_connect: true
power_save_mode: light
mqtt:
broker: "${mqtt_broker}"
username: "${mqtt_user}"
password: "${mqtt_password}"
topic_prefix: "${mqttPath}${name}"
discovery: false
#https://esphome.io/components/i2c.html
i2c:
id: i2c_main
scl: D1
sda: D2
scan: true
frequency: 20kHz
#https://esphome.io/components/sensor/sds011.html
uart:
rx_pin: D6
tx_pin: D7
baud_rate: 9600
#Dallas Temperature Sensor (One Wire)
#https://esphome.io/components/sensor/dallas.html
dallas:
- pin: D5
#https://esphome.io/components/sensor/as3935.html
as3935_i2c:
i2c_id: i2c_main
irq_pin: D3
indoor: false #a lot of errors otherwise
noise_level: 2
spike_rejection: 2
lightning_threshold: 1
mask_disturber: false
div_ratio: 0
capacitance: 0
watchdog_threshold: 2
# Arduino Port Extender
# https://esphome.io/cookbook/arduino_port_extender.html
custom_component:
- id: expander1
lambda: |-
auto expander = new ArduinoPortExpander(i2c_main, 0x08, true);
return {expander};
sensor:
- platform: uptime
id: uptime_id
name: "Uptime"
update_interval: 5s
internal: true
#Analog Pins
#https://esphome.io/components/sensor/adc.html
- platform: adc
id: a0
pin: A0
name: "A0"
raw: true
filters:
- throttle_average: 10s
- filter_out: NaN
internal: true
#https://esphome.io/components/sensor/sds011.html
- platform: sds011
pm_2_5:
name: "Particulate Matter <2.5µm Concentration"
id: pm25
filters:
- filter_out: NaN
internal: true
pm_10_0:
name: "Particulate Matter <10.0µm Concentration"
id: pm10
filters:
- filter_out: NaN
internal: true
update_interval: 2min
#Dallas Temperature Sensor (One Wire)
#https://esphome.io/components/sensor/dallas.html
- platform: dallas
id: onewire
index: 0
filters:
- filter_out: NaN
name: "OneWire"
internal: true
#as3935 Lightning Sensor
#https://esphome.io/components/sensor/as3935.html
- platform: as3935
lightning_energy:
id: lightning_energy
name: "Lightning Energy"
filters:
- filter_out: NaN
internal: true
distance:
id: distance_stormfront
name: "Distance Stormfront"
filters:
- filter_out: NaN
internal: true
#BME/P280
#https://esphome.io/components/sensor/bme280.html
- platform: bme280
i2c_id: i2c_main
temperature:
name: "BME-T"
id: bme_t
oversampling: 8x
filters:
- filter_out: NaN
internal: true
pressure:
name: "BME_P"
id: bme_p
oversampling: 8x
filters:
- filter_out: NaN
internal: true
humidity:
name: "BME_H"
id: bme_h
oversampling: 8x
filters:
- filter_out: NaN
internal: true
address: 0x76
update_interval: 10s
- platform: template
name: "Absolute Humidity"
id: absolute_humidity
lambda: |-
const float mw = 18.01534; // molar mass of water g/mol
const float r = 8.31447215; // Universal gas constant J/mol/K
return (6.112 * powf(2.718281828, (17.67 * id(bme_t).state) /
(id(bme_t).state + 243.5)) * id(bme_h).state * mw) /
((273.15 + id(bme_t).state) * r); // in grams/m^3
accuracy_decimals: 2
update_interval: 10s
icon: 'mdi:water'
unit_of_measurement: 'g/m³'
filters:
- filter_out: NaN
internal: true
- platform: template
name: "Dew Point"
id: dew_point
lambda: |-
return (243.5*(log(id(bme_h).state/100)+((17.67*id(bme_t).state)/
(243.5+id(bme_t).state)))/(17.67-log(id(bme_h).state/100)-
((17.67*id(bme_t).state)/(243.5+id(bme_t).state))));
unit_of_measurement: °C
accuracy_decimals: 2
update_interval: 10s
icon: 'mdi:thermometer-alert'
filters:
- filter_out: NaN
internal: true
#https://esphome.io/components/sensor/pulse_counter.html
#https://esphome.io/cookbook/geiger-counter.html
- platform: pulse_counter
pin: D4
name: "D4"
id: d4
count_mode:
rising_edge: DISABLE
falling_edge: INCREMENT
internal: true
- platform: custom
lambda: |-
return {ape_analog_input(expander1, 0), // = A0
ape_analog_input(expander1, 1), // = A1
ape_analog_input(expander1, 2), // = A2
ape_analog_input(expander1, 3), // = A3
ape_analog_input(expander1, 6), // = A6
ape_analog_input(expander1, 7), // = A7
ape_analog_input(expander1, 21), // = D2 pulsecounter
ape_analog_input(expander1, 22) // = D3 pulsecounter
};
sensors:
- id: arduino_a0
name: "Arduino A0"
filters:
# update every 30s
- throttle_average: 20s
- filter_out: NaN
internal: true
- id: arduino_a1
name: "Arduino A1"
filters:
- throttle_average: 20s
- filter_out: NaN
internal: true
- id: arduino_a2
name: "Arduino A2"
filters:
- throttle_average: 20s
- filter_out: NaN
internal: true
- id: arduino_a3
name: "Arduino A3"
filters:
- throttle_average: 20s
- filter_out: NaN
internal: true
- id: arduino_a6
name: "Arduino A6"
filters:
- throttle_average: 20s
- filter_out: NaN
internal: true
- id: arduino_a7
name: "Arduino A7"
filters:
- throttle_average: 20s
- filter_out: NaN
internal: true
- id: arduino_geiger_counter
name: "Geiger Counter"
filters:
- throttle: 20s
- filter_out: NaN
internal: true
- id: arduino_anemometer
name: "Anemometer"
filters:
- throttle: 20s
- filter_out: NaN
internal: true
- platform: template
id: arduino_a0_delayed
name: "Arduino A0 delayed"
lambda: |-
if (id(uptime_id).state > 120) { return id(arduino_a0).state; }
return NAN;
update_interval: 30s
filters:
- filter_out: NaN
internal: true
- platform: template
id: arduino_a1_delayed
name: "Arduino A1 delayed"
lambda: |-
if (id(uptime_id).state > 120) { return id(arduino_a1).state; }
return NAN;
update_interval: 30s
filters:
- filter_out: NaN
internal: true
- platform: template
id: arduino_a2_delayed
name: "Arduino A2 delayed"
lambda: |-
if (id(uptime_id).state > 120) { return id(arduino_a2).state; }
return NAN;
update_interval: 30s
filters:
- filter_out: NaN
internal: true
- platform: template
id: arduino_a3_delayed
name: "Arduino A3 delayed"
lambda: |-
if (id(uptime_id).state > 120) { return id(arduino_a3).state; }
return NAN;
update_interval: 30s
filters:
- filter_out: NaN
internal: true
button:
#https://esphome.io/components/button/restart.html
- platform: restart
name: "Restart"
binary_sensor:
#as3935 Lightning Sensor
#https://esphome.io/components/sensor/as3935.html
- platform: as3935
name: "Storm Alert"
#Digital Pins
- platform: gpio
pin: D0
id: d0
name: "D0"
internal: true
- platform: gpio
pin: D8
id: d8
name: "D8"
internal: true
interval:
- interval: 2min
then:
- mqtt.publish:
topic: "${mqttPath}${name}/json"
payload: !lambda |-
DynamicJsonDocument root(2048);
if( !std::isnan(id(uptime_id).state) ) { root["uptime"] = lrint(id(uptime_id).state); }
if( !std::isnan(id(a0).state) ) { root["a0"] = lrint(id(a0).state); }
if( !std::isnan(id(pm25).state) ) { root["pm25"] = id(pm25).state; }
if( !std::isnan(id(pm10).state) ) { root["pm10"] = id(pm10).state; }
if( !std::isnan(id(onewire).state) ) { root["onewire"] = id(onewire).state; }
if( !std::isnan(id(lightning_energy).state) ) { root["lightning_energy"] = id(lightning_energy).state; }
if( !std::isnan(id(distance_stormfront).state) ) { root["distance_stormfront"] = id(distance_stormfront).state; }
if( !std::isnan(id(bme_t).state) ) { root["bme_t"] = id(bme_t).state; }
if( !std::isnan(id(bme_p).state) ) { root["bme_p"] = id(bme_p).state; }
if( !std::isnan(id(bme_h).state) ) { root["bme_h"] = id(bme_h).state; }
if( !std::isnan(id(absolute_humidity).state) ) { root["absolute_humidity"] = id(absolute_humidity).state; }
if( !std::isnan(id(dew_point).state) ) { root["dew_point"] = id(dew_point).state; }
if( !std::isnan(id(d4).state) ) { root["d4"] = id(d4).state; }
if( !std::isnan(id(arduino_a0).state) ) { root["arduino_a0"] = id(arduino_a0).state; }
if( !std::isnan(id(arduino_a1).state) ) { root["arduino_a1"] = id(arduino_a1).state; }
if( !std::isnan(id(arduino_a2).state) ) { root["arduino_a2"] = id(arduino_a2).state; }
if( !std::isnan(id(arduino_a3).state) ) { root["arduino_a3"] = id(arduino_a3).state; }
if( !std::isnan(id(arduino_a6).state) ) { root["arduino_a6"] = id(arduino_a6).state; }
if( !std::isnan(id(arduino_a7).state) ) { root["arduino_a7"] = id(arduino_a7).state; }
if( !std::isnan(id(arduino_geiger_counter).state) ) { root["arduino_geiger_counter"]= id(arduino_geiger_counter).state; }
if( !std::isnan(id(arduino_anemometer).state) ) { root["arduino_anemometer"] = id(arduino_anemometer).state; }
if( !std::isnan(id(arduino_a0_delayed).state) ) { root["arduino_a0_delayed"] = id(arduino_a0_delayed).state; }
if( !std::isnan(id(arduino_a1_delayed).state) ) { root["arduino_a1_delayed"] = id(arduino_a1_delayed).state; }
if( !std::isnan(id(arduino_a2_delayed).state) ) { root["arduino_a2_delayed"] = id(arduino_a2_delayed).state; }
if( !std::isnan(id(arduino_a3_delayed).state) ) { root["arduino_a3_delayed"] = id(arduino_a3_delayed).state; }
if( !std::isnan(id(d0).state) ) { root["d0"] = id(d0).state; }
if( !std::isnan(id(d8).state) ) { root["d8"] = id(d8).state; }
std::string message;
serializeJson(root, message);
return message;
Pingback: Mini-WLAN-Kamera mit ESPHome und ESP32-Cam - Smarthome DIY - Heimautomatisierung selbst gemacht
Pingback: Ultra-leiser Heizungs-Booster - Smarthome DIY - Heimautomatisierung selbst gemacht