Thermometer kosten beim Amazon 10€ für 2 Stück. Aber die nicht nicht so nice.
Außerdem: was bringt mir ein Display für ein Außenthermometer? Ich möcht ja wissen ob es kalt genug ist um drinnen zu bleiben, wäre blöd wenn ich dafür raus muss.
Wenn du Elektroniker bist und alle Teile dafür zuhause rumliegen hast, nennt sich das dann “neue Projektidee”. Sowas Treibt das einfache Projekt dann in Richtung “kleine Wetterstation”.
Die Hardware
ESP8266 Microcontroller
Der kleine Bruder des ESP32 Dev Board. Ich habe noch viele von denen rum liegen, Zeit sie endlich für etwas sinnvolles zu verwenden.
Wi-FI können sie ja, das ist die Hautpvoraussetzung. Und I2C, um mit dem Sensor zu kommunizieren können sie natürlich auch. Bestens geeignet für dieses kleine Projekt.
Handelsübliche Namen für dieses Board sind:
- NodeMCU 8266
- NodeMCU 0.9
Gibt es mit Verschiedenen USB-Serial Convertern, wobei die alle gleich funktionieren:
- CP2102 (Mein Board) (Kleineres PCB)
- CH340 (das auf dem Bild)
BME280 Sensor Board
Dieses kleine, aber feine Board ist nicht größer als ein kleiner Fingernagel.
Über I2C liefert es auf Kommando die aktuelle Temperatur, Luftdruck und weil es die BME anstatt der BMP Variante is, auch noch Luftfeuchtigkeit.
Wiring Diagram
Dadurch, dass wir den BME280 über I2C ansteuern, ist die Verkabelung recht easy. Spannungsversorgung dran. I2C Pins verbinden. Fertig.
ESP8266 | BME280 |
D1 | SCL |
D2 | SDA |
GND | GND |
3V3 | VIN |
Wunderschön gezeichnet. Ich weiß, Danke!
Die Software
Die Anforderungen an die Software sind eher minimalistisch:
- Verbindet sich bei Start mit dem vordefiniertem WLAN Netz. Verbindet sich auch zum MQTT Broker.
- Fragt in einem defnierbarem Intervall die Sensorwerte ab und schickt sie über MQTT.
- Zusätzlich schickt der Microcontroller noch IP und MAC Adresse über MQTT, zu Debug Zwecken.
- Status LED
- Während Connection leuchtet Builtin LED. Wenn Connection steht, dann geht die LED aus. Dienst zur Fehlerbehebung wenn die LED ständig leuchtet, ist was falsch. (LED ist LOW Active im Code @ NodeMCU!)
- LED ist an während gesendet wird. Blinkt also pro Message kurz auf.
Libraries und Config
Für die MQTT Verbindung verwende ich die Library PubSubClient von knolleary. Auch im Arduino Library Manager zum Download.
Um den BME Sensor auszulesen, verwende ich die BME280 Library von Adafruit. Gibt es auch im Arduino Library Manager.
Der erste Block wird verwendet um die WiFi Credentials zu befüllen.
const int DELAY
gibt an wie oft der Sensor ausgelesen werden soll. Hier alle 2500 Millisekunden, also 2.5 Sekunden.
String mqtt_subtopic
wird verwendet damit ich mehrere Sensoren auseinander halten kann. Dieser String wird auch verwendet um den Client einen Namen zu geben. Weil 2 gleiche Client Namen funktionieren nicht in einem MQTT Netz!
Falls der BME Sensor nicht ganz genaue Werte liefert, lässt sich mit const float TEMP_SENS_OFFSET
ein offset geben. Meiner ist zb. immer 2 grad zu warm. Mit einem Infrarot Thermometer getestet.
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
// WIFI CREDENTIALS
const char* SSID = "YOUR_SSID";
const char* PSK = "YOUR_PASSWORD";
const char* MQTT_BROKER = "MQTT_BROKER_IP";
const int PORT = 1883;
// LOOP DELAY
const int DELAY = 2500;
// MQTT STUFF
String mqtt_subtopic = "wohnzimmer";
WiFiClient espClient;
PubSubClient client(espClient);
String espClientNameString = "ESPClient_" + mqtt_subtopic;
char espClientName[50];
// BME STUFF
Adafruit_BME280 bme;
const float TEMP_SENS_OFFSET = -2;
Setup
Zuerst wird die LED konfiguriert und eingeschaltet. LOW ist in dem Fall “ein”.
Serial Port natürlich zum Debuggen. Baud rate von 115200 sollte es schon sein, zu schnell gibts hier nicht.
Dann wird die WiFi Verbindung mit den oben definierten Credentials hergestellt. Solange es nicht verbunden ist, werden am Serial Port Punkte ausgegeben. Wenn die Verbindung steht, wird die IP Adresse angezeigt.
Dann wird MQTT eingerichtet, aber noch nicht verbunden. Hier wird auch der MQTT Name zusammengestückelt.
Zuletzt wird die Verbindung mit dem BME280 überprüft. Dieser hat die I2C Adresse 0x76. Wenn an dieser Adresse ein Sensor gefunden wird, dann ist das Setup abgeschlossen.
void setup() {
// INIT LED. TURN IT ON
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// INIT SERIAL
Serial.begin(115200);
Serial.println("\nWelcome to Weater-ESP");
// SETUP WIFI
Serial.println(String("Connecting to ") + SSID);
WiFi.begin(SSID, PSK);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// SETUP MQTT
client.setServer(MQTT_BROKER, PORT);
espClientNameString.toCharArray(espClientName, sizeof(espClientName));
// INIT BME SENSOR
static bool connected = false;
if (!connected) {
while(!bme.begin(0x76)){
Serial.println("Could not find BME280. Check Wiring!");
delay(500);
}
}
Serial.println("BME280 connected");
}
Funktion Reconnect
So, warum haben wir oben zwar den MQTT Server eingerichtet, aber nicht gleich verbunden? Weil wir das in der Loop machen. Hat den einfachen Vorteil, dass wenn die MQTT Verbindung zwischendurch mal flöten geht, der Sensor nicht neu gestartet werden muss. Das macht der alleine, wenn er entdeckt, dass keine Verbindung zum Broker da ist. Deswegen wurde der Teil vom Code in eine eigene Funktion ausgelagert.
void reconnect() {
while (!client.connected()) {
Serial.println("Reconnecting...");
if (!client.connect(espClientName)) {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}
Loop
Der Loop ist recht einfach. Zuerst wird gecheckt ob eine MQTT Verbindung steht, wenn nicht, wird die Reconnect Funktion aufgerufen.
Danach wird die LED eingeschaltet, um zu signalisieren der Sensor ist beschäftigt. (Remember: Blinkende LED)
Dann kommen im Endeffekt eine Anreihung von Blöcken, die alle das selbe machen
- Hol dir den Sensor Wert (oder andere Infos).
- Pack ihn in eine leserliche Form in ein char Array.
- Bau dir das topic als char Array zusammen.
- Schick den Wert über das Topic über MQTT raus.
Wenn fertig und alles geschickt, wird die LED ausgeschaltet.
Dann wird gewartet, bis der Loop wieder von vorne los gehen kann.
void loop() {
// CONNECTION CHECK
if (!client.connected()) {
digitalWrite(LED_BUILTIN, LOW);
reconnect();
}
digitalWrite(LED_BUILTIN, LOW);
client.loop();
// SEND REAL STUFF
char msg[50];
snprintf (msg, 50, "Alive since %ld milliseconds", millis());
Serial.println(msg);
String topicMsg_string = "homesens/" + mqtt_subtopic + "/message";
char topicMsg [50];
topicMsg_string.toCharArray(topicMsg, sizeof(topicMsg));
client.publish(topicMsg, msg);
// SEND IP ADDRESS
char bufIp [20];
IPAddress ipaddr = WiFi.localIP();
sprintf(bufIp, "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]);
String topicIp_string = "homesens/" + mqtt_subtopic + "/ip";
char topicIp [50];
topicIp_string.toCharArray(topicIp, sizeof(topicIp));
client.publish(topicIp, bufIp);
// SEND MAC ADDRESS
String mac = String(WiFi.macAddress());
char bufMac [20];
mac.toCharArray(bufMac, 20);
String topicMac_string = "homesens/" + mqtt_subtopic + "/mac";
char topicMac [50];
topicMac_string.toCharArray(topicMac, sizeof(topicMac));
client.publish(topicMac, bufMac);
// SEND TEMPERATURE
float temp = bme.readTemperature() + TEMP_SENS_OFFSET;
char buftemp[20];
sprintf (buftemp, "%f", temp);
String topicTemperature_string = "homesens/" + mqtt_subtopic + "/temperature";
char topicTemperature [50];
topicTemperature_string.toCharArray(topicTemperature, sizeof(topicTemperature));
client.publish(topicTemperature, buftemp);
// SEND PRESSURE
float pressure = bme.readPressure()/100;
char bufpressure[20];
sprintf (bufpressure, "%f", pressure);
String topicPressure_string = "homesens/" + mqtt_subtopic + "/pressure";
char topicPressure [50];
topicPressure_string.toCharArray(topicPressure, sizeof(topicPressure));
client.publish(topicPressure, buftemp);
// SEND HUMIDITY
float humidity = bme.readHumidity();
char bufhumidity[20];
sprintf (bufhumidity, "%f", humidity);
String topicHumidity_string = "homesens/" + mqtt_subtopic + "/humidity";
char topicHumidity [50];
topicHumidity_string.toCharArray(topicHumidity, sizeof(topicHumidity));
client.publish(topicHumidity, buftemp);
// FINISH
digitalWrite(LED_BUILTIN, HIGH);
// WAIT FOR GIVEN TIME PERIOD
delay(DELAY);
}
Der ganze Code
Klassischer Arduino-Style Code. Nichts besonderes, schnell und einfach aufgesetzt. Kann natürlich noch verfeinert werden, aber funktioniert ja so auch. Eine super Idee fürs nächste Level wäre Deep Sleep und Akkubetrieb. Aber nicht heute.
Feel free to copy. Gibts auch auf meinem Github Repo: https://github.com/thazaubara/weather-ESP
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
// WIFI CREDENTIALS
const char* SSID = "YOUR_SSID";
const char* PSK = "YOUR_PASSWORD";
const char* MQTT_BROKER = "MQTT_BROKER_IP";
const int PORT = 1883;
// LOOP DELAY
const int DELAY = 2500;
// MQTT STUFF
String mqtt_subtopic = "wohnzimmer";
WiFiClient espClient;
PubSubClient client(espClient);
String espClientNameString = "ESPClient_" + mqtt_subtopic;
char espClientName[50];
// BME STUFF
Adafruit_BME280 bme;
const float TEMP_SENS_OFFSET = -2;
void setup() {
// INIT LED. TURN IT ON
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// INIT SERIAL
Serial.begin(115200);
Serial.println("\nWelcome to Weater-ESP");
// SETUP WIFI
Serial.println(String("Connecting to ") + SSID);
WiFi.begin(SSID, PSK);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// SETUP MQTT
client.setServer(MQTT_BROKER, PORT);
espClientNameString.toCharArray(espClientName, sizeof(espClientName));
// INIT BME SENSOR
static bool connected = false;
if (!connected) {
while(!bme.begin(0x76)){
Serial.println("Could not find BME280. Check Wiring!");
delay(500);
}
}
Serial.println("BME280 connected");
}
void reconnect() {
while (!client.connected()) {
Serial.println("Reconnecting...");
if (!client.connect(espClientName)) {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}
void loop() {
// CONNECTION CHECK
if (!client.connected()) {
digitalWrite(LED_BUILTIN, LOW);
reconnect();
}
digitalWrite(LED_BUILTIN, LOW);
client.loop();
// SEND REAL STUFF
char msg[50];
snprintf (msg, 50, "Alive since %ld milliseconds", millis());
Serial.println(msg);
String topicMsg_string = "homesens/" + mqtt_subtopic + "/message";
char topicMsg [50];
topicMsg_string.toCharArray(topicMsg, sizeof(topicMsg));
client.publish(topicMsg, msg);
// SEND IP ADDRESS
char bufIp [20];
IPAddress ipaddr = WiFi.localIP();
sprintf(bufIp, "%d.%d.%d.%d", ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]);
String topicIp_string = "homesens/" + mqtt_subtopic + "/ip";
char topicIp [50];
topicIp_string.toCharArray(topicIp, sizeof(topicIp));
client.publish(topicIp, bufIp);
// SEND MAC ADDRESS
String mac = String(WiFi.macAddress());
char bufMac [20];
mac.toCharArray(bufMac, 20);
String topicMac_string = "homesens/" + mqtt_subtopic + "/mac";
char topicMac [50];
topicMac_string.toCharArray(topicMac, sizeof(topicMac));
client.publish(topicMac, bufMac);
// SEND TEMPERATURE
float temp = bme.readTemperature() + TEMP_SENS_OFFSET;
char buftemp[20];
sprintf (buftemp, "%f", temp);
String topicTemperature_string = "homesens/" + mqtt_subtopic + "/temperature";
char topicTemperature [50];
topicTemperature_string.toCharArray(topicTemperature, sizeof(topicTemperature));
client.publish(topicTemperature, buftemp);
// SEND PRESSURE
float pressure = bme.readPressure()/100;
char bufpressure[20];
sprintf (bufpressure, "%f", pressure);
String topicPressure_string = "homesens/" + mqtt_subtopic + "/pressure";
char topicPressure [50];
topicPressure_string.toCharArray(topicPressure, sizeof(topicPressure));
client.publish(topicPressure, buftemp);
// SEND HUMIDITY
float humidity = bme.readHumidity();
char bufhumidity[20];
sprintf (bufhumidity, "%f", humidity);
String topicHumidity_string = "homesens/" + mqtt_subtopic + "/humidity";
char topicHumidity [50];
topicHumidity_string.toCharArray(topicHumidity, sizeof(topicHumidity));
client.publish(topicHumidity, buftemp);
// FINISH
digitalWrite(LED_BUILTIN, HIGH);
// WAIT FOR GIVEN TIME PERIOD
delay(DELAY);
}
Das Gehäuse
Klassisch gezeichnet in Fusion 360, und mit dem 3D Drucker in die echte Welt gebracht.
Im Gehäuse selbst wird nur der BME280 mit einer Schraube befestigt. Das ESP Board wird nur in die Vorrichtung gelegt.
Dann kommt der Deckel drauf und wird angeschraubt. Der Deckel fixiert dann auch den ESP.
Real World Test
Anstecken und fertig. Das Teil liefert automatisch Daten sobald es Strom bekommt.
MQTT Explorer
MQTT Explorer aufmachen und staunen.
Grafana
Ich habe zwei davon im Einsatz. Eines am Balkon und eines im Wohnzimmer. Liefert seit März 2021 konstant Daten.
Wenn man die Daten in einer Datenbank mitschreibt, kann man sich mit Grafana hübsche Graphen zeichnen lassen. In der Vergangenheit hat das ein Python Script gemacht, das 24/7 gelaufen ist und über MQTT auf bestimmte Topics gelauscht hat. Mittlerweile macht das Node-RED.
Hier die Temperaturen der letzten 2 Jahre als Screenshot.
Blöd nur, dass mein Sensor am Balkon ab und zu von der Sonne angeschienen wird und sich dadurch die Temperatur erhöht, was zu falschen Messwerten führt.
Aber Mittlerweile habe ich dafür auch eine Lösung gefunden. Warum eigene Sensoren aufstellen, wenn die Wetterstationen der Nachbarn den kompletten Innenhof mit Daten fluten *sniff, sniff :>