Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
de:tech:rainsensor [2022/11/12 08:02] – [SDR (Software Defined Radio)] bullar | de:tech:rainsensor [2022/11/14 09:35] (aktuell) – [Software (ESPHome)] bullar | ||
---|---|---|---|
Zeile 3: | Zeile 3: | ||
===== Motivation ===== | ===== Motivation ===== | ||
- | Die üppige Vegetation hier in Thailand ist der Wärme aber auch den teils heftigen Niederschlägen geschuldet. Die Temperaturen bewegen sich im Bereich von 25-35°C und lassen keinen Sommer oder Winter erkennen. Schön ist das [[https:// | + | Die üppige Vegetation hier in Thailand ist der Wärme aber auch den teils heftigen Niederschlägen geschuldet. Die Temperaturen bewegen sich immer im Bereich von +25...35°C und lassen |
{{ : | {{ : | ||
- | Die Jahresregenmenge kann bis zu 1600 Liter/m2 betragen. In Deutschland ist es je nach Region weniger als die Hälfte. | + | Die Jahresregenmenge kann bis zu 1600 mm/m< |
{{ : | {{ : | ||
Zeile 15: | Zeile 15: | ||
===== Sensoren ===== | ===== Sensoren ===== | ||
- | Es gibt eine große Menge an Regensensoren die allerdings nur erkennen ob es gerade regnet. Das kann hilfreich um automatisch zum Beispiel eine Markise ein- oder auszufahren. Wir wollen aber die Regenmenge messen. Daher brauchen wir einen Sensor der die Regenmenge definiert auffängt. Dazu gibt es in [[https:// | + | Es gibt eine große Menge an Regensensoren die allerdings nur erkennen ob es gerade regnet. Das kann hilfreich um automatisch zum Beispiel eine Markise ein- oder auszufahren. Wir wollen aber die Regenmenge messen. Daher brauchen wir einen Sensor, der die Regenmenge definiert auffängt. Dazu gibt es in [[https:// |
- | Ein digitaler Niederschlagsmesser verwendet einen Trichter um das Regenwasser zu bündeln und dann auf eine Kippwaage zu führen. Ist die eine Seite vollgelaufen dann kippt die Waage, entleert die vollgelaufene Hälfte und die zweite Seite kann voll laufen. Zählt man die Anzahl der Kippvorgänge hat man ein gutes Maß für die Regenmenge. Natürlich muss das Maß für den Trichtereinlass sowie die Aufnahmemenge der Kippwaage bekannt sein. Genau solch einen Sensor kann man auch selber bauen. [[https:// | + | Ein digitaler Niederschlagsmesser verwendet einen Trichter um das Regenwasser zu bündeln und um es dann auf eine Kippwaage zu leiten. Ist die eine Seite vollgelaufen dann kippt die Waage, entleert die vollgelaufene Hälfte und die zweite Seite kann voll laufen. Zählt man die Anzahl der Kippvorgänge hat man ein gutes Maß für die Regenmenge. Natürlich muss das Maß für den Trichtereinlass sowie die Aufnahmemenge der Kippwaage bekannt sein. Genau solch einen Sensor kann man auch selber bauen. [[https:// |
- | {{ : | + | {{ : |
- | Der Sensor ist batteriebetrieben und sendet die Messwerte auf 433MHz. Er kann also irgendwo draussen im Garten positioniert werden. Um den Sensor für eigene Anwendungen wie Home Assistant verwenden zu können bedarf es Reverse Engineering. | + | Der Sensor ist batteriebetrieben und sendet die Messwerte auf 433MHz. Er kann also irgendwo draussen im Garten positioniert werden. Um den Sensor für eigene Anwendungen wie Home Assistant verwenden zu können bedarf es etwas //Reverse Engineering//. |
Das Trichtergehäuse läßt sich einfach entfernen. Zum Vorschein kommt die Kippwaage mit dem dahinter liegenden Gehäuse für die Elektronik. Ein Magnet in der Kippwaage stimuliert einen Reedkontakt im Innern. Das Batteriefach (2xAA) ist von unten zugänglich. | Das Trichtergehäuse läßt sich einfach entfernen. Zum Vorschein kommt die Kippwaage mit dem dahinter liegenden Gehäuse für die Elektronik. Ein Magnet in der Kippwaage stimuliert einen Reedkontakt im Innern. Das Batteriefach (2xAA) ist von unten zugänglich. | ||
- | {{ : | + | {{ : |
Im Innern residieren zwei Platinen. Eine mit dem Reedkontakt und COB (Chip On Board) und das zweite PCB ist der Sender (433MHz) | Im Innern residieren zwei Platinen. Eine mit dem Reedkontakt und COB (Chip On Board) und das zweite PCB ist der Sender (433MHz) | ||
- | {{: | + | <WRAP group> |
- | {{: | + | <WRAP half column> |
+ | {{ : | ||
+ | </ | ||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
Diese Trennung erlaubt uns einfach das Protokoll zum Sender abzugreifen. | Diese Trennung erlaubt uns einfach das Protokoll zum Sender abzugreifen. | ||
Zeile 40: | Zeile 46: | ||
{{ : | {{ : | ||
- | Zoomt man weiter hinein dann zeigt der [[https:// | + | Zoomt man weiter hinein dann zeigt der [[https:// |
{{ : | {{ : | ||
- | Schauen wir uns num mal an, ob das auch so gesendet wird. | + | Schauen wir uns nun mal an, was dann wirklich |
===== SDR (Software Defined Radio) ===== | ===== SDR (Software Defined Radio) ===== | ||
- | Ein preiswerte Möglichkeit für Untersuchungen im Frequenzbereich so bis ca. 1766 MHz. Das reicht für die 433MHz des Regensensors aus. | + | Eine preiswerte Möglichkeit für Untersuchungen im Frequenzbereich so bis ca. 1766 MHz ist ein Software Defined Radio (SDR). Das reicht für die 433MHz des Regensensors |
- | Als Hardwarebasis dient ein ' | + | |
{{ : | {{ : | ||
Zeile 61: | Zeile 67: | ||
- [[https:// | - [[https:// | ||
- | Sie sind alle kostenlos und Open Source. Für meine Untersuchung habe ich URH verwendet, da es alle Analyzeschritte in einem Program | + | Sie sind alle kostenlos und Open Source. Für meine Untersuchung habe ich URH verwendet, da es alle Analyzeschritte in einem Programm |
- | Zuerst schauen wir uns mal das Frequenzspektrum um 433MHz an, ob tatsächlich ein Signal vom Regensensor gesendet wird. Dazu starten wir im URH die Funktion //Spectrum Analyzer...// | + | Zuerst schauen wir uns erstmal |
{{ : | {{ : | ||
Zeile 68: | Zeile 74: | ||
Im blauen Block ist der zeitliche Verlauf aufgezeichnet (x=Frequenz, | Im blauen Block ist der zeitliche Verlauf aufgezeichnet (x=Frequenz, | ||
- | {{: | + | <WRAP group> |
- | {{: | + | <WRAP half column> |
+ | {{ : | ||
+ | </ | ||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Das Ergebnis zeigt nun das aufgezeichnete Signal im zeitlichen Verlauf. Wir erkennen die sich achtmal wiederholenden Frameanteile und im Zoom die schon im digitalen Signal vorher beschriebenen Pulse und Pausen. Diese Aufzeichnung speichern wir an einer wiederzufindenen Stelle ab. | ||
+ | |||
+ | Jetzt ist es an der Zeit in URH ein neues Projekt anzulegen damit wir die nächsten Schritte auch abspeichern können. Dann gehen wir auf den Reiter // | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | Auch hier ist wieder die Pulsfolge mit den zwei unterschiedlich langen Pausen zu finden '' | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Um die vorhandene Decoderoption //Morse Code// nutzen zu können, invertieren wir das Signal vorher mit //Invert//. Dann müssen wir noch die Morsecode Parameter festlegen. Nach der Invertierung haben wir genau eine '' | ||
+ | * Maximum length of 1-sequence for Low (0) = 4 | ||
+ | * Minimum length of 1-sequence for High (1) = 7 | ||
+ | * Number of 0s between 1-sequences (just for encoding) = 1 | ||
+ | Wenn alles richtig eingestellt ist, wird der Frame ohne Fehler decodiert. Die 8 Frames erscheinen jetzt in der Liste. Jeder Frame besteht aus 36 Bits deren Bedeutung wir nun empirisch herausfinden müssen. Dazu legen wir einen neuen Message Type // | ||
+ | |||
+ | < | ||
+ | | ||
+ | 0000 0000 0011 1111 1111 2222 2222 2233 3333 | ||
+ | 0123 4567 8901 2345 6789 0123 4567 8901 2345 | ||
+ | |----|----|----|----|----|----|----|----|---- | ||
+ | aaaa aaaa RRbs tttt tttt tttt rrrr rrrr CCCC | ||
+ | LSB - MSB LSB ------ MSB LSB - MSB | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | t: 12 bit signed temperature * 10 (eg: 23,1°C send as 231) | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | |||
+ | Mit der Erkenntnis kann es nun an den neuen Empfänger gehen, der in [[https:// | ||
+ | |||
+ | |||
+ | ===== Hardware ===== | ||
+ | |||
+ | Um an das Signal des Regenmengensensors zu kommen brauchen wir zuerst einen passenden Empfänger für 433MHz. Die gibt es vielfältig im Online-Handel. Bei der Auswahl ist darauf zu achten, dass es sich um einen Superhet- (Superheterodyne) und nicht um einen Geradeausempfänger handelt. Geradeausempfänger haben eine zu große Bandbreite und eine geringe Verstärkung. Meine Wahl fiel auf den DollaTek RXB6 Superheterodyne Receiver: | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | {{ : | ||
+ | </ | ||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Bei der Bestellung gleich die passende 433MHz Helical-Antenne mit ordern. | ||
+ | |||
+ | Das Empfängermodul arbeitet mit 5V Versorgungsspannung und stellt einen digitalen Ausgang zur Verfügung, der direkt mit einem ESP Modul verbunden werden kann. Hier meine Lösung auf einer Rasterplatine mit ESP8266 Modul, dem Empfänger RXB6 und einem Micro-USB Anschluss. Da wir im Haus an allen Steckdosen auch 5V USB Buchsen haben, können wir auf ein externes Netzteil verzichten. | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | |||
+ | ===== Software (ESPHome) ===== | ||
+ | |||
+ | Jetzt geht es an die Software. Für die Integration in Home Assistant bietet sich wieder ESPHome und ein //custom component// an. Das folgende C-Programm erledigt die Auswertung und stellt vier Sensoren zur Verfügung: | ||
+ | * Rain Sensor | ||
+ | * Rain Temperature Sensor | ||
+ | * Lowbat Rain Sensor | ||
+ | * ID Rain Sensor | ||
+ | |||
+ | Der Pin an dem der Empfängerausgang angeschlossen ist wird über ''# | ||
+ | |||
+ | ++++ rf433_rain_sensor.h | | ||
+ | <file C rf433_rain_sensor.h> | ||
+ | include " | ||
+ | |||
+ | //#define DEBUG | ||
+ | //#define MSB | ||
+ | |||
+ | #ifdef DEBUG | ||
+ | #define debug_print(x, | ||
+ | #define debug_println(x, | ||
+ | #else | ||
+ | #define debug_print(x, | ||
+ | #define debug_println(x, | ||
+ | #endif | ||
+ | |||
+ | #define RX_PIN 3 | ||
+ | #define IS_START_PULSE(interval) | ||
+ | #define IS_MARK_PULSE(interval) | ||
+ | #define IS_LOW_PULSE(interval) | ||
+ | #define IS_HIGH_PULSE(interval) | ||
+ | #define IS_GLITCH(interval) (interval < 250) | ||
+ | |||
+ | enum sampling_state {WAIT_FOR_START, | ||
+ | |||
+ | static const char* TAG = " | ||
+ | bool have_data = false; | ||
+ | bool have_batt_data = false; | ||
+ | bool have_id_data = false; | ||
+ | bool lowbat = false; | ||
+ | int Temp = 0; | ||
+ | int Rain = 0; | ||
+ | int Id = 0; | ||
+ | |||
+ | // --------------------------------------------------------------------------- | ||
+ | class RF433_Rain_Sensor : public PollingComponent { | ||
+ | | ||
+ | Sensor *temp_sensor = new Sensor(); | ||
+ | Sensor *rain_sensor = new Sensor(); | ||
+ | |||
+ | RF433_Rain_Sensor() : PollingComponent(500) { } | ||
+ | |||
+ | float get_setup_priority() const override { return esphome:: | ||
+ | |||
+ | static void ICACHE_RAM_ATTR pin_ISR() { | ||
+ | |||
+ | static unsigned long last = 0; | ||
+ | static unsigned long micros_now; | ||
+ | static byte nibble[9] = {0, | ||
+ | static uint16_t pulse; | ||
+ | static byte sampling_state = WAIT_FOR_START; | ||
+ | static byte bits = 0; | ||
+ | static byte npos = 0; | ||
+ | static byte chksum = 0; | ||
+ | |||
+ | micros_now = micros(); | ||
+ | pulse = micros_now - last; | ||
+ | |||
+ | // ignore glitches | ||
+ | if (IS_GLITCH(pulse)) | ||
+ | pulse = 0; | ||
+ | else | ||
+ | last = micros_now; | ||
+ | |||
+ | // state machine for decoding on the fly | ||
+ | switch (sampling_state) { | ||
+ | case WAIT_FOR_START: | ||
+ | if (IS_START_PULSE(pulse)) | ||
+ | sampling_state = DATA_MARK; | ||
+ | bits = 0; | ||
+ | } | ||
+ | break; | ||
+ | case DATA_MARK: | ||
+ | if (IS_MARK_PULSE(pulse)) | ||
+ | sampling_state = DATA_BIT; | ||
+ | } else { | ||
+ | sampling_state = WAIT_FOR_START; | ||
+ | } | ||
+ | break; | ||
+ | case DATA_BIT: | ||
+ | // LSB | ||
+ | if (IS_LOW_PULSE(pulse)) | ||
+ | #ifdef MSB | ||
+ | nibble[bits/ | ||
+ | #else | ||
+ | nibble[bits/ | ||
+ | #endif | ||
+ | bits++; | ||
+ | sampling_state = DATA_MARK; | ||
+ | } else if (IS_HIGH_PULSE(pulse)) | ||
+ | #ifdef MSB | ||
+ | nibble[bits/ | ||
+ | #else | ||
+ | nibble[bits/ | ||
+ | #endif | ||
+ | bits++; | ||
+ | sampling_state = DATA_MARK; | ||
+ | } else { | ||
+ | sampling_state = WAIT_FOR_START; | ||
+ | } | ||
+ | if (bits == 36) sampling_state = DATA_RECEIVED; | ||
+ | break; | ||
+ | case DATA_RECEIVED: | ||
+ | chksum = 0; | ||
+ | for (npos=0; | ||
+ | chksum += nibble[npos]; | ||
+ | } | ||
+ | chksum &= 0x0F; | ||
+ | ESP_LOGD(TAG, | ||
+ | if (chksum == nibble[8]) { | ||
+ | Temp = (nibble[5]<< | ||
+ | Rain = (((nibble[2] & 0xC0)>> | ||
+ | have_data = true; | ||
+ | if ((nibble[2] & 0x8) > 0) { | ||
+ | lowbat = true; | ||
+ | } else { | ||
+ | lowbat = false; | ||
+ | } | ||
+ | have_batt_data = true; | ||
+ | Id = (nibble[1]<< | ||
+ | have_id_data = true; | ||
+ | } | ||
+ | bits = 0; | ||
+ | sampling_state = WAIT_FOR_START; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ------------------------------------------------------------------------- | ||
+ | void setup() override { | ||
+ | // Prepare RF input pin | ||
+ | Serial.end(); | ||
+ | pinMode(RX_PIN, | ||
+ | attachInterrupt(RX_PIN, | ||
+ | } | ||
+ | |||
+ | // ------------------------------------------------------------------------- | ||
+ | void update() override { | ||
+ | // This is the actual sensor reading logic. | ||
+ | if (have_data) | ||
+ | { | ||
+ | temp_sensor-> | ||
+ | rain_sensor-> | ||
+ | have_data = false; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // --------------------------------------------------------------------------- | ||
+ | class Batt_RF433_Rain_Sensor : public PollingComponent, | ||
+ | public: | ||
+ | |||
+ | // constructor | ||
+ | Batt_RF433_Rain_Sensor() : PollingComponent(10000) {} | ||
+ | |||
+ | void setup() override { | ||
+ | // This will be called by App.setup() | ||
+ | } | ||
+ | |||
+ | void update() override { | ||
+ | if (have_batt_data) { | ||
+ | if (lowbat) { | ||
+ | publish_state(true); | ||
+ | } else { | ||
+ | publish_state(false); | ||
+ | } | ||
+ | have_batt_data = false; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // --------------------------------------------------------------------------- | ||
+ | class ID_RF433_Rain_Sensor : public PollingComponent, | ||
+ | public: | ||
+ | |||
+ | // constructor | ||
+ | ID_RF433_Rain_Sensor() : PollingComponent(10000) {} | ||
+ | |||
+ | void setup() override { | ||
+ | // This will be called by App.setup() | ||
+ | } | ||
+ | |||
+ | void update() override { | ||
+ | if (have_id_data) | ||
+ | { | ||
+ | publish_state(Id); | ||
+ | have_id_data = false; | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | ++++ | ||
+ | In ESPHome generieren wir ein neues Device und fügen den eigenen Code mit einem // | ||
+ | |||
+ | <code yaml> | ||
+ | esphome: | ||
+ | name: rain-sensor | ||
+ | includes: | ||
+ | - rf433_rain_sensor.h | ||
+ | </ | ||
+ | |||
+ | Dann definieren wir die vier Sensoren gemäß dem folgenden Beispiel. | ||
+ | |||
+ | <code yaml> | ||
+ | sensor: | ||
+ | - platform: custom | ||
+ | lambda: |- | ||
+ | auto rf433_sensor_data = new RF433_Rain_Sensor(); | ||
+ | App.register_component(rf433_sensor_data); | ||
+ | return {rf433_sensor_data-> | ||
+ | |||
+ | sensors: | ||
+ | - name: "Rain Temperature Sensor" | ||
+ | unit_of_measurement: | ||
+ | accuracy_decimals: | ||
+ | - name: "Rain Sensor" | ||
+ | unit_of_measurement: | ||
+ | accuracy_decimals: | ||
+ | |||
+ | - platform: custom | ||
+ | lambda: |- | ||
+ | auto id_rf433_sensor_data = new ID_RF433_Rain_Sensor(); | ||
+ | App.register_component(id_rf433_sensor_data); | ||
+ | return {id_rf433_sensor_data}; | ||
+ | |||
+ | sensors: | ||
+ | - name: "ID Rain Sensor" | ||
+ | unit_of_measurement: | ||
+ | accuracy_decimals: | ||
+ | |||
+ | binary_sensor: | ||
+ | - platform: custom | ||
+ | lambda: |- | ||
+ | auto batt_rf433_rain_sensor = new Batt_RF433_Rain_Sensor(); | ||
+ | App.register_component(batt_rf433_rain_sensor); | ||
+ | return {batt_rf433_rain_sensor}; | ||
- | Das Ergebnis zeigt nun das aufgezeichnete Signal im zeitlichen Verlauf. Wir erkennen die sich achtmal wiederholenden Frameanteile und im Zoom die schon im digitalen Signal vorher beschrieben Pulse und Pausen. Diese Aufzeichnung speichern wir an einer wiederzufindenen Stelle ab. | + | binary_sensors: |
+ | - name: " | ||
+ | </ | ||
+ | Nach der Integration stehen die Daten nun im Home Assistant zur Verfügung. Die gemessene Regenmenge wird vom Sensor akkumuliert übertragen. Ob und wann der Zähler wieder zurückgesetzt muss ein Langzeitbetrieb zeigen. Maximal kann dieser Zähler (2< | ||
+ | ===== Ergebnisse ===== | ||
+ | Im Haus getestet funktioniert die Übertragung völlig problemlos. Ein längerer Ausseneinsatz steht noch aus, da sich das Gerät im Moment in Deutschland befindet. | ||
+ | Ich werde diesen Part in 2023 ergänzen... | ||
- | =====Downloads===== | ||
- | * | ||
=====Links===== | =====Links===== |