Präzise Regenvorhersage: Arduino-Wetterstation mit Python & Keras selbst bauen
Komponenten und Materialien
| Kleines Kunststoffgehäuse mit abnehmbarem Deckel (geschraubt), ca. 11×7×5 cm | 1 |
| 3× AAA-Batteriehalter | 1 |
| 3× AAA-NiMH-Akkus (wiederaufladbar) | 1 |
| 6 V Solarmodul (klein) | 1 |
| SparkFun Arduino Pro Mini 328 – 5 V/16 MHz | 1 |
| Diode 1N4004 (Rückstromsperre Solarmodul → Akku) | 1 |
| NPN-Transistor (Allzweck) + 1 kΩ Widerstand (Leistungsschaltung für Sensoren) | 1 |
| Regensensor | 1 |
| HC-12 Funkmodul (Seriell, 433 MHz) | 1 |
| HC-12 USB-Seriell-Modul (Basisstation) | 1 |
| SparkFun BME280 Breakout (Luftfeuchte, Temperatur, Druck) | 1 |
| BH1750 Lichtsensor | 1 |
| PCB, Kabel, Lötzinn, KF301-2P Schraubklemmen, Steckverbinder, Kleber | 1 |
| 3,3 V Spannungsregler | 1 |
Benötigte Werkzeuge
- USB-Seriell-Adapter FTDI FT232RL (zum Programmieren des Pro Mini)
- Arduino IDE
- Bohrmaschine
- Feinsäge
- Schraubendreher
- Lötkolben
- Seitenschneider
Projektüberblick
Ziel: Vorhersage, ob es in den nächsten 6 Stunden regnet (Ja/Nein). Der komplette Weg:
- Wetterstation bauen – netzunabhängig (Solar), Extrem-Stromsparmodus (wenige µA).
- Firmware programmieren – alle 10 Min. Sensordaten an Basisstation senden.
- Daten sammeln – auf Basisstation (Linux-PC/Board) in Datenbank speichern.
- Daten aufbereiten & Modell trainieren – Python, Pandas, Keras (Neuronales Netz).
- Vorhersage & Benachrichtigung – E-Mail bei Regenwahrscheinlichkeit.
Mit ca. 600 Tagen Daten erreicht das System ~80 % Trefferquote (abhängig von Parametern). Das Tutorial deckt alles ab: Hardwarebau, Arduino-Code, Datenbank, Machine Learning – ohne externe Wetter- oder ML-APIs.
Schritt 1: Teile & Werkzeuge
(Siehe Tabellen oben. Basisstation: dauerhaft laufender Linux-Rechner mit HC-12 USB-Modul.)
Fertigkeiten: Löten, Arduino-Grundlagen, Linux-Dienste/Pakete, Programmiererfahrung.
Schritt 2: Wetterstation bauen
1. Gehäuse & Solarmodul: 4 Bohrungen (Solarkabel, 3 Sensoren). Solarmodul aufkleben, Kabel nach innen führen.
2. PCB-Bestückung:
- Zwei weibliche PCB-Steckleisten für Arduino Pro Mini (Arduino-Pins männlich bestücken).
- Wichtig: Arduino-Onboard-LED entfernen oder mindestens ein Bein abtrennen – sie zieht zu viel Strom!
- Transistor, 1 kΩ, 3,3 V-Regler einlöten.
- Zwei KF301-2P Klemmen (Solar, Akku).
- Drei weibliche Steckleisten für BH1750, BME280, Regensensor.
- Alle Verbindungen laut Bildern/Fritzing-Plan verdrahten.
3. Akku: 3 geladene AAA-NiMH in Halter, in Gehäuse legen, an Klemme anschließen.
4. Sensoren: BME280 & BH1750 außen an Steckverbinder. Regensensor: 3 Drähte (GND, VCC, Signal) anlöten, andere Seite mit Steckpins für PCB.
5. Aufstellung: Geschützter Standort für Hauptbox; Regensensor separat im Freien mit längeren Kabeln. Befestigung per starkem Klebeband o.ä.
Fritzing-Datei: sketch.fzz
Schritt 3: Arduino-Code
Die Station sendet alle 10 Min. codierte Sensordaten per Software-Serial (HC-12). Format:
H1:78|T1:12|PS1:1022|L1:500|R1:0|V1:4010|
(Station 1: Feuchte 78 %, Temp 12 °C, Druck 1022 hPa, Licht 500 lx, Regen 0, Spannung 4010 mV)
Ablauf: Sensoren lesen → Senden → Peripherie abschalten → 10 Min. Deep-Sleep (< 50 µA) → Wiederholung. Bei Spannung > 4,2 V wird normales delay() genutzt (schneller Entlade-Schutz gegen Überladung).
Code: GitHub Repository oder unten kopieren. Zeile transmitSenzorData("V", sensors.voltage); entfernen.
#include "LowPower.h"
#include "SoftwareSerial.h"
#include "Wire.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"
#include "BH1750.h"
SoftwareSerial serialComm(4, 5); // RX, TX
Adafruit_BME280 bme;
BH1750 lightMeter;
const byte rainPin = A0;
byte sensorsCode = 1;
int voltageDeepSleepThreshold = 4200;
const byte peripherialsPowerPin = 6;
struct sensorData { byte humidity; int temperature; byte rain; int pressure; long voltage; int light; };
sensorData sensors;
void setup() {
Serial.begin(9600); serialComm.begin(9600);
pinMode(peripherialsPowerPin, OUTPUT);
digitalWrite(peripherialsPowerPin, HIGH);
delay(500);
if (!bme.begin()) { Serial.println("BME280 nicht gefunden!"); while(1) customSleep(100); }
Serial.println("Initialisierung OK");
delay(50); digitalWrite(peripherialsPowerPin, HIGH);
}
void loop() { updateSenzors(); transmitData(); customSleep(75); }
void updateSenzors() {
bme.begin(); lightMeter.begin(); delay(300);
sensors.temperature = bme.readTemperature();
sensors.pressure = bme.readPressure() / 100.0F;
sensors.humidity = bme.readHumidity();
sensors.light = lightMeter.readLightLevel();
sensors.voltage = readVcc();
sensors.rain = readRain();
}
void transmitData() {
emptyIncommingSerialBuffer();
Serial.print("Temp:"); Serial.println(sensors.temperature);
Serial.print("Humid:"); Serial.println(sensors.humidity);
Serial.print("Pressure:"); Serial.println(sensors.pressure);
Serial.print("Light:"); Serial.println(sensors.light);
Serial.print("Voltage:"); Serial.println(sensors.voltage);
Serial.print("Rain:"); Serial.println(sensors.rain);
transmitSenzorData("T", sensors.temperature);
transmitSenzorData("H", sensors.humidity);
transmitSenzorData("PS", sensors.pressure);
transmitSenzorData("L", sensors.light);
transmitSenzorData("V", sensors.voltage); // <-- ENTFERNEN
transmitSenzorData("R", sensors.rain);
}
void emptyIncommingSerialBuffer() { while (serialComm.available() > 0) { serialComm.read(); delay(5); } }
void transmitSenzorData(String type, int value) {
serialComm.print(type); serialComm.print(sensorsCode);
serialComm.print(":"); serialComm.print(value); serialComm.print("|");
delay(50);
}
void customSleep(long eightSecondCycles) {
if (sensors.voltage > voltageDeepSleepThreshold) { delay(eightSecondCycles * 8000); return; }
digitalWrite(peripherialsPowerPin, LOW);
for (int i = 0; i < eightSecondCycles; i++) LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
digitalWrite(peripherialsPowerPin, HIGH); delay(500);
}
byte readRain() { return map(analogRead(rainPin), 0, 1023, 0, 100); }
long readVcc() {
// Interne 1,1 V Referenz gegen AVcc messen
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(2); ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC));
uint8_t low = ADCL; uint8_t high = ADCH;
long result = (high << 8) | low;
result = 1125300L / result; // 1,1*1023*1000
return result; // mV
}
Benötigte Bibliotheken (Arduino IDE → Sketch → Bibliothek einbinden → .ZIP oder Bibliotheksverwalter):
Schritt 4: Basisstation vorbereiten (Linux)
HC-12 USB-Modul an Linux-Rechner (z. B. Ubuntu). Dauerhaft online.
- Anaconda installieren (Python-Paketmanager, Versionen kontrollierbar). Anleitung (Schritte 1–8).
- MongoDB installieren (schemalos, ideal für Zeitreihen). MongoDB 3.4.6 (neuere Versionen funktionieren auch). Optional Index:
db.weather_station.createIndex({"date": 1}). - Projekt klonen:
git clone https://github.com/danionescu0/home-automation.git→ Ordnerweather-predict. - Conda-Umgebung:
Pakete: Keras, Pandas, PyMongo, scikit-learn.cd weather-predict conda create --name weather python=3.6.2 conda activate weather pip install -r requirements.txt
Schritt 5: Konfiguration & Betrieb
config.py anpassen: MongoDB Host/Port, Serial-Port (ls -l /dev/tty* vor/nach HC-12 Einstecken → meist /dev/ttyUSB0, 9600 Baud).
Daten importieren (meine Testdaten)
mongoimport -d weather -c weather_station --file sample_data/weather_station.json
Warnung: Kombinieren mit eigenen Daten kann Genauigkeit senken (Hardware-/Standortunterschiede).
Eigene Daten sammeln
conda activate weather
python serial_listener.py
# Alle 10 Min. erscheinen Daten: [Sensor: type(temperature), value(14.3)] ...
Modell trainieren
python train.py --days_behind 600 --test-file-percent 10 --datapoints-behind 8 --hour-granularity 6
--days_behind: Wie viele Tage Historien-Daten nutzen.--test-file-percent: Testdaten-Anteil.--hour-granularity: Vorhersagehorizont (6 h).--datapoints-behind: Wie viele vergangene 6-h-Blöcke als Features (8 = 48 h).
Diagramme anzeigen
python graphs.py --days-behind 10
Vorhersage & E-Mail
python predict.py --datapoints-behind 8 --hour-granularity 6 \
--from-addr ihre@gmail.com --from-password gmail_pw --to-addr ziel@email.de
Gmail: „Weniger sichere Apps zulassen“ aktivieren. Für stündliche Vorhersage per Cronjob eintragen.
Schritt 6: Wie es funktioniert (Technischer Deep-Dive)
1. Architektur
Trennung: Wetterstation (nur Messen/Senden, extrem sparsam) ↔ Basisstation (Datenbank, Training, Vorhersage). Vorteile: Strombedarf, Portabilität, Skalierbarkeit (mehrere Stationen), niedrige Kosten.
Datenbank: MongoDB (BSON/JSON-ähnlich). Eintrag pro Minute (10-Min-Intervall aggregiert).
ML-Stack: Python, Pandas (Datenverarbeitung), Keras (High-Level NN über TensorFlow).
2. Machine-Learning-Grundlagen
Künstliche neuronale Netze ahmen biologische Neuronen nach: Eingänge → Gewichte → Bias → Aktivierungsfunktion → Ausgabe. Lernen = Gewichte anpassen per Backpropagation (Optimierer: RMSprop, Loss: MAE, Metrik: Accuracy). Überwachtes Lernen: Inputs + gewünschte Outputs bekannt.
3. Datenaufbereitung (Kernschritt)
- Rohdaten aus MongoDB laden (Pandas DataFrame).
- Fehlwerte entfernen (
dropna()). - Gruppierung: 6-h-Intervalle. Pro Sensor (Feuchte, Regen, Temp, Licht, Druck) Metriken berechnen: Min, Max, Mittelwert, Perzentile (10/30/70/90), Anstiege/Abfälle/konstante Phasen.
- Label erzeugen: Spalte
has_rain(0/1) basierend aufrain_avg > 0.1. - Datumsspalte entfernen, Zeilen mit
temperature_min < 0löschen (kein Schneesensor). - History-Features: Für jede Zeile Spalten der vorangegangenen 8 Blöcke (48 h) anfügen (
temperature_1,temperature_2…). Optimale Tiefe: 8. - Unvollständige Anfangszeilen (NaN) löschen. Aktuelle Sensorwerte (außer Label) entfernen – Vorhersage nur aus Historie.
- Train/Test-Split (sklearn, z. B. 90/10).
- Features & Labels trennen (
X = data.iloc[:,1:],y = data.iloc[:,0]). - Skalierung (StandardScaler, 0–1), Scaler speichern (
joblib.dump).
4. Keras-Modell
from keras.models import Sequential
from keras.layers import Dense, Dropout
input_dim = X.shape[1]
model = Sequential()
inner = int(input_dim / 2)
model.add(Dense(inner, activation='relu', input_dim=input_dim, kernel_initializer='uniform'))
model.add(Dropout(0.05))
model.add(Dense(inner, activation='relu', kernel_initializer='uniform'))
model.add(Dropout(0.05))
model.add(Dense(1, activation='sigmoid', kernel_initializer='uniform'))
model.compile(optimizer='rmsprop', loss='mean_absolute_error', metrics=['accuracy'])
model.fit(X, y, batch_size=1, epochs=50)
model.save('model_file_name')
Dropout verhindert Overfitting. Parameter (Layer, Neuronen, Dropout, Optimizer, Loss, Batch, Epochs) stark tunen!
5. Zukunftsverbesserungen
- Windgeschwindigkeit/-richtung, UV, Gas/Partikel-Sensoren ergänzen.
- Mehrere Stationen für räumliche Mittelung.
- Jahreweise Daten sammeln.
- Externe Daten (z. B. Weather Underground) einbinden.
- Keras-Hyperparameter-Optimierung:
python train.py ... --grid-search(sucht Batch, Epochen, Optimizer, Dropout). - Andere Architekturen testen (LSTM brachte hier leicht schlechtere Ergebnisse).
Schritt 7: Bonus – Offizielles Wetterdatenset (Dark Sky API)
Test mit Dark Sky API (historische Daten, mehr Parameter: Wind, UV, Sichtweite). Ergebnis: Genauigkeit steigt von ~80 % auf ~90 %, besonders bei Regentagen.
- Daten herunterladen (API-Key, Koordinaten in
download_import/config.py) →python download_import/darksky.py -d 1000(Free-Tier: 1000 Requests/Tag). Oder meine Bukarest-Daten importieren:mongoimport -d weather -c darksky --file sample_data/darksky.json. - Training auf Dark-Sky-Datensatz:
python train.py -d 2000 -p 20 -dp 4 -hg 6 --data-source darksky. - Batch-Prediction:
python predict_batch.py -f sample_data/test_data.csv.
Fazit: Datenqualität & -umfang sind entscheidend.
Code-Schnipsel (Referenz)
Im Originalprojekt sind zahlreiche Code-Schnipsel für Datenverarbeitung, Training, Evaluation, Vorhersage hinterlegt. Die wichtigsten sind im GitHub-Repo (danionescu0/home-automation) im Ordner weather-predict zu finden.
Schaltplan
Arduino-Sketch: GitHub
Herstellungsprozess
- Fern-Temperatursensor mit Raspberry Pi & Python bauen – Schritt-für-Schritt-Anleitung
- DIY Photoshop-Steuerkonsole mit Arduino Nano RP2040 bauen
- Dateien und Verzeichnisse in Python mit os.rename() umbenennen – Schritt-für-Schritt-Anleitung
- RFID-Anwesenheitssystem mit Arduino & Python – Daten direkt in phpMyAdmin (XAMPP) speichern
- Universelle Android-Fernbedienung mit Arduino, 1Sheeld & IR-Steuerung
- Intelligentes Voltmeter mit Arduino & Smartphone bauen – Einfaches DIY-Projekt
- DIY Infrarot-Herzschlag-Sensor mit Arduino: Schritt-für-Schritt-Anleitung
- Frequenz & Tastverhältnis präzise messen mit Arduino UNO
- DIY-Voltmeter mit Arduino und Nokia 5110 Display – Schritt-für-Schritt-Anleitung
- Echtzeit-Entfernungsmessung mit Arduino Sonar & Processing-Visualisierung