In meiner letzten Artikelserie zum Thema Funksensoren und Empfänger selbst bauen habe ich gezeigt wie auf Basis eines RFM12B und eines Atmel Attiny 84 ein eigener Funksender und Empfänger gebaut werden kann, in meinem Fall hatte ich als Sensor einen DHT22 Temperatur- und Feuchtigkeits-Sensor. Die Temperatur Daten habe ich am Raspberry Pi über eine PHP Anwendung in eine Datenbank gespeichert und via Google Charts ausgewertet.
Heute möchte ich als Alternative zum Selbstbau ein fertiges Empfänger Modul ebenfalls auf Basis des RFM12B vorstellen, allerdings kommt hier ein etwas leistungsfähigerer ATmega328 zum Einsatz. Das RFM12Pi ist extrem klein und passt direkt auf die GPIO Pins des Raspberry Pi. Es ist sowohl als 433MHz als auch mit 868 und 915MHz verfügbar, ich verwende die 433MHz Variante. Als Sensoren benutze ich meine selbstgebauten mit DHT22 Temperatur- und Feuchtigkeitsmessung.
Update: Bitte schaut euch als Alternative noch die Option via Google Charts an oder via EmonCMS mit DS18B20 Sensor. Alle Methoden benötigen allerdings etwas Wissen rund um die eingesetzten Technologien
RFM12Pi als Empfängermodul am Raspberry Pi |
Emoncms auf dem Raspberry Pi zur Speicherung |
Selbstgebauter Sender mit RFM12B, Attiny84 und DHT2 Sensor |
Inhaltsverzeichnis
Was ist Emoncms?
Emoncms ist eine Open Source Webanwendung zum Verarbeiten, Speichern und Visualisieren von Temperatur, Stromverbrauch und anderen Daten. Das Emoncms ist Teil des OpenEnergyMonitor Projekts und ist angepasst für den Raspberry Pi verfügbar. Um Daten eines direkt angeschlossenen Empfängers zu verarbeiten wird das Modul „Raspberry Pi“ bzw. „EmonHub“ verwendet, beide lauschen am UART des Raspberry Pi.
Die Anwendung ist Modular aufgebaut sodass die Teile zum Empfangen / Verarbeiten von den Teilen der Software, welche die Speicherung und Präsentation der Daten übernehmen, getrennt installiert werden können. Wer möchte kann also den RasPi als reinen Datensammler mit Funkmodul verwenden und die Daten an ein anderes System weiterleiten.
Was kann der RFM12Pi?
Das RFM12Pi Modul basier genau so wie meine selbstgebaute Lösung auf dem RFM12B und einem Atmel Chip (ATmega328) für die Logik. Das RFM12B-Modul empfängt die Daten, der Chip bereitet diese entsprechend auf und sendet diese via UART an der Raspberry Pi. Die Daten können dort entweder wie gezeigt über ein Perl Script verarbeitet werden oder sie werden direkt anhand eines des „Raspberry Pi Emoncms Moduls“ verarbeitet. Wie das funktioniert zeige ich euch in diesem Artikel.
Das RFM12Pi Modul verwendet wie meine Selbstbau-Lösung die JeeLib und daher kann auch der Selbstbau Empfänger mit Emoncms verwendet werden, Voraussetzung dafür ist natürlich die richtige Software auf den Sendern. Da ich aktuell das RFM12Pi Modell teste verwende ich diese in meinem Artikel.
Installation des RFM12Pi Empfängers
Die Einrichtung des Moduls ist vergleichsweise simpel, ihr müsst nur darauf achten dass ihr das Modul richtig auf den RasPi aufsteckt, der mit dem GPIO Pin 1 zu verbindende PIN am Stecker ist entsprechend gekennzeichnet. Dann können wir auch direkt das Emoncms zur Verwaltung unserer Home Automation Sensoren installieren.
Als erstes fügen wir das Repository, in welchem sich die Installationspakete befinden, zu unserern Software Quellen auf dem Raspberry Pi hinzu und aktualisieren die Paket Liste und veraltete Pakete via apt.
1 2 |
sudo sh -c "echo 'deb http://emon-repo.s3.amazonaws.com wheezy unstable' >> /etc/apt/sources.list" sudo apt-get -y update && sudo apt-get -y upgrade |
Es ist wichtig das vor der Installation UART auf dem Raspberry Pi aktiviert wurde sodass unser Modul mit dem Raspberry Pi bzw. der Software kommunizieren kann, wie das geht zeige ich euch in meinem Artikel UART am Raspberry Pi aktivieren. Dann kann mit der Installation des Emoncms begonnen werden, das Debian Paket installiert direkt alle notwendigen Komponenten wie den Webserver und die Datenbank, zusätzlich führt uns der Installer durch die Grundkonfiguration.
1 |
sudo apt-get -y --force-yes install emoncms |
Der Setup Dialog fragt nach Informationen zum Root Passwort für die Datenbank, dem Benutzer für die MySQL Emoncms Datenbank und Infos zum SMTP Server zum Versand von E-Mails. Euren SMTP Server könnt ihr, falls ihr Mails versenden wollt, bei eurem Provider erfragen oder einsehen. Für meine Installation habe ich als Benutzer zum Zugriff auf die Emoncms Datenbank ebenfalls den root verwendet, ihr solltet euch allerdings nach der Anlage der Datenbank z.B. über phpmyadmin einen speziellen User mit eingeschränkten Rechten anlegen und diesen durch erneutes Aufrufend er Konfiguration eintragen.
Die Konfiguration kann jeder Zeit durch folgenden Befehl wiederholt werden.
1 |
sudo dpkg-reconfigure emoncms --force |
Apache wurde durch das Emoncms installiert, allerdings müssen wir noch die Seite aktivieren und das Module Rewrite, zum Aktivieren der Änderungen an Apache ist ein Restart notwendig.
1 2 3 4 |
sudo a2ensite emoncms sudo a2enmod rewrite sudo /etc/init.d/apache2 restart |
Da mein System später Mails versenden soll und ich meine SD-Karte vor unnötigen Zugriffen bewahren möchte installiere ich noch zwei Module hierfür mit den entsprechenden Abhängigkeiten.
1 2 3 4 5 |
sudo apt-get install -y php-pear php5-dev redis-server php5-mcrypt php5-curl sudo pear channel-discover pear.swiftmailer.org sudo pecl install redis swift/swift |
Nach der Installation der Moduke müssen wir noch die PHP Konfiguration anpassen und die neuen Module eintragen.
1 2 3 4 |
sudo sh -c 'echo "extension=redis.so" > /etc/php5/apache2/conf.d/20-redis.ini' sudo sh -c 'echo "extension=redis.so" > /etc/php5/cli/conf.d/20-redis.ini' sudo sh -c 'echo "extension=dio.so" > /etc/php5/apache2/conf.d/20-dio.ini' sudo sh -c 'echo "extension=dio.so" > /etc/php5/cli/conf.d/20-dio.ini' |
Dann Installieren wir noch das Raspberry Pi Modul für Emoncms und weitere die ich verwende, die Energy Module für den Energieverbrauch eures Zuhauses spare ich mir da ich hierfür keinen Sensor habe. Da es für das Event Modul keinen Installer gibt installieren wir dieses manuell via Git.
Module:
- Raspberry Pi – Schnittstelle zwischen Empfänger und Emoncms
- Event – Funktionen ausführen wenn ein bestimmter Wert überschritten wird (z.b. E-Mail versenden)
- Notify – Modul für allgemeine Benachrichtigungen (nachträgliche Anpassung notwendig, siehe git)
1 2 3 4 5 6 |
cd /usr/share/emoncms/www/Modules sudo git clone https://github.com/emoncms/event.git sudo git clone https://github.com/emoncms/raspberrypi.git sudo git clone https://github.com/emoncms/notify.git sudo pecl install channel://pecl.php.net/dio-0.0.6 |
1 2 3 4 5 6 |
sudo cp /var/www/emoncms/Modules/raspberrypi/rfm12piphp /etc/init.d/ sudo chmod 755 /etc/init.d/rfm12piphp sudo update-rc.d rfm12piphp defaults sudo /etc/init.d/rfm12piphp start sudo /etc/init.d/apache2 restart |
Konfiguration vom Emoncms
Nun sind noch einige kleine Anpassungen am Emoncms zum Betrieb auf unserem Raspberry Pi notwendig. Hierzu öffnen wir die Webseite von Emoncms in einem Browser:
- http://<IP-Raspberry-Pi>/emoncms/ (Fügt eure IP-Adresse ein, zum Beispiel
- http://localhost/emoncms/ (falls ihr den Browser auf dem RasPi Startet)
Als erstes legen wir einen neuen Account an, das erledigen wir durch einen Klick auf Register.
Gebt eure Daten an und ihr werdet nach dem Anlegen eines Accounts direkt zur Anwendung weitergeleitet.
WICHTIG!
Bevor wir anfangen uns mit dem System zu beschäftigen müssen wir erst noch die Datenbank aktualisieren um alle installierten Module verwenden zu können. Klickt hierzu im Menu auf Admin und dann auf Update & Check, bestätigt auf der folgenden Seite das Update.
Danach können wir den Empfänger Kofigurieren, die Settings findet ihr unter „Raspberry Pi„, die Seite sollte melden RFM12 to Pi interface script is up and running.
An dieser stelle können wir unserem Empfänger die korrekten Einstellungen verpassen, die NodeID und die Network Group müssen mit den Angaben in den Sender Programmen übereinstimmen. Bei mir ist das Group 210 und Nide ID für den Empfänger 22. Läuft das RasPi Interface nicht könnt ihr wie im Git Repository beschrieben das Logging aktivieren und nachsehen wo das Problem liegt.
Um unseren Empfänger mit Daten zu versorgen müsst ihr nun euren Sender Chip (ATTiny84) mit dem korrekten Code zum Senden programmieren und in Betrieb nehmen. Die Anleitung und das passende Sketch für euren Sender findet Ihr am Ende dieses Artikels.
Es kann kurz dauern bis euer Sender Daten übeträgt, falls ihr eine Status LED habt erkennt ihr das an dem kurzen zweimaligen Blinken, wird nichts gesendet Kontrolliert die Einstellungen für Node ID und Network Group am Empfänger und Sender sowie die Funktion des Dienstes. Sobald Daten am Empfänger ankommen erkennt das System diese und zeigt uns dann unter Input die Empfangenen Werte an.
Ich habe über den Editieren Button rechts den Empfangenen Werten noch Namen zugeordnet. Bei mir ist der Wert 1 die Temperatur, Wert 2 die Volts der Batterien und Wert 3 die Temperatur.
Da die Werte aus technischen Gründen ohne Komma/Punkt ankommen fügen wir über den Schraubenschlüssel als erstes einen Prozess hinzu der uns den Empfangenen Wert immer mit 0.01 multipliziert. Das geschieht durch die Auswahl von „Calibrate -> X“ in der „Add Process“ Liste. Da wir die Daten auch speichern wollen fügen wir als Prozess zwei einen „Log to Feed“ ein, ich benenne den Feed immer mit <Standort>-Wert, ich habe das Sendeintervall meiner Sender auf 5 Minuten eingestellt und wähle daher aus die Daten auch nur alle 5 Minuten zu verarbeiten. Das Selbe erledigt ihr natürlich für alle Werte aller Sender.
Hier die Konfiguration eines Wert-Inputs:
Jetzt könnt ihr die Werte unter „Feed“ einsehen oder unter „Dashboard„, was ich absolut super finde, ein eigenes Dashboard gestallten, unten ein Beispiel. Zusätzlich könnt ihr euch natürlich Infos via E-Mail senden lassen oder Alarmierungen für bestimmte Werte einstellen. Die Konfiguration findet ihr unter Extras.
Viel Spaß!
Der Code für die Sensoren / Sender
Das ist der Code welcher auf die Sender aufgespielt wird, ich habe die Basis Version von Nathan etwas angepasst da nur eine Maximale Sendepause von 60 Sekunden möglich war und die Status LED Unterstützung fehlte. Die Sketches für andere Sernsoren wie den DS18B20 findet ihr auf GitHub, allerdings ohne die erwähnten Anpassungen.
Zum Schreiben des Codes auf euren Sender Chip könnt ihr wie in meinem Raspberry Pi Projekt beschrieben vorgehen. Ladet euch hierzu das Sketch nach /usr/share/arduino/, bennent es in SendRFM12Pi.ino um und öffnet es in der ArduinoIDE.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
//---------------------------------------------------------------------------------------------------------------------- // TinyTX - An ATtiny84 and RFM12B Wireless Temperature & Humidity Sensor Node // // Updated to Support Status LED by Philipp from http://raspberry.tips - Original made by Nathan Chantrell // // Using the DHT22 temperature and humidity sensor // // Licenced under the Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) licence: // http://creativecommons.org/licenses/by-sa/3.0/ // // Requires Arduino IDE with arduino-tiny core: http://code.google.com/p/arduino-tiny/ //---------------------------------------------------------------------------------------------------------------------- #include #include ISR(WDT_vect) { Sleepy::watchdogEvent(); } // interrupt handler for JeeLabs Sleepy power saving #define myNodeID 16 // RF12 node ID in the range 1-30 #define network 210 // RF12 Network group #define freq RF12_433MHZ // Frequency of RFM12B module #define SENDDELAY 60000 // the Atmega internal sleep is max 60 seconds #define SENDDELAYMULTIPLY 5 // We use a for function to multiply the max sleep SENDDELAY * SENDDELAYMULTIPLY #define USE_ACK // Enable ACKs, comment out to disable #define RETRY_PERIOD 5 // How soon to retry (in seconds) if ACK didn't come in #define RETRY_LIMIT 5 // Maximum number of times to retry #define ACK_TIME 10 // Number of milliseconds to wait for an ack #define DHT22_PIN 10 // DHT sensor is connected on D10/ATtiny pin 13 #define DHT22_POWER 9 // DHT Power pin is connected on D9/ATtiny pin 12 #define LEDpin 8 // LED Pin D8, PA2 - set to 0 to disable LED DHT22 myDHT22(DHT22_PIN); // Setup the DHT //######################################################################################################################## //Data Structure to be sent //######################################################################################################################## typedef struct { int humidity; // Humidity reading int supplyV; // Supply voltage int temp; // Temperature reading } Payload; Payload tinytx; // Wait a few milliseconds for proper ACK #ifdef USE_ACK static byte waitForAck() { MilliTimer ackTimer; while (!ackTimer.poll(ACK_TIME)) { if (rf12_recvDone() && rf12_crc == 0 && rf12_hdr == (RF12_HDR_DST | RF12_HDR_CTL | myNodeID)) return 1; } return 0; } #endif //-------------------------------------------------------------------------------------------------- // LED //------------------------------------------------------------------------------------------------- static void activityLed (byte state, byte time = 0) { if (LEDpin) { pinMode(LEDpin, OUTPUT); if (time == 0) { digitalWrite(LEDpin, state); } else { digitalWrite(LEDpin, state); Sleepy::loseSomeTime(time); digitalWrite(LEDpin, !state); } } } // blink led static void blink (byte pin, byte n = 3) { if (LEDpin) { pinMode(pin, OUTPUT); for (byte i = 0; i < 2 * n; ++i) { Sleepy::loseSomeTime(100); digitalWrite(pin, !digitalRead(pin)); } } } //-------------------------------------------------------------------------------------------------- // Send payload data via RF //------------------------------------------------------------------------------------------------- static void rfwrite(){ #ifdef USE_ACK for (byte i = 0; i <= RETRY_LIMIT; ++i) { // tx and wait for ack up to RETRY_LIMIT times rf12_sleep(-1); // Wake up RF module while (!rf12_canSend()) rf12_recvDone(); rf12_sendStart(RF12_HDR_ACK, &tinytx, sizeof tinytx); rf12_sendWait(2); // Wait for RF to finish sending while in standby mode byte acked = waitForAck(); // Wait for ACK rf12_sleep(0); // Put RF module to sleep if (acked) { return; } // Return if ACK received Sleepy::loseSomeTime(RETRY_PERIOD * 1000); // If no ack received wait and try again } #else rf12_sleep(-1); // Wake up RF module while (!rf12_canSend()) rf12_recvDone(); rf12_sendStart(0, &tinytx, sizeof tinytx); rf12_sendWait(2); // Wait for RF to finish sending while in standby mode rf12_sleep(0); // Put RF module to sleep return; #endif } //-------------------------------------------------------------------------------------------------- // Read current supply voltage //-------------------------------------------------------------------------------------------------- long readVcc() { bitClear(PRR, PRADC); ADCSRA |= bit(ADEN); // Enable the ADC long result; // Read 1.1V reference against Vcc #if defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); // For ATtiny84 #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // For ATmega328 #endif delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Convert while (bit_is_set(ADCSRA,ADSC)); result = ADCL; result |= ADCH<<8; result = 1126400L / result; // Back-calculate Vcc in mV ADCSRA &= ~ bit(ADEN); bitSet(PRR, PRADC); // Disable the ADC to save power return result; } //######################################################################################################################## void setup() { rf12_initialize(myNodeID,freq,network); // Initialize RFM12 with settings defined above rf12_sleep(0); // Put the RFM12 to sleep pinMode(DHT22_POWER, OUTPUT); // set power pin for DHT to output PRR = bit(PRTIM1); // only keep timer 0 going ADCSRA &= ~ bit(ADEN); bitSet(PRR, PRADC); // Disable the ADC to save power activityLed(1,1000); // LED on for 1000ms } void loop() { activityLed(1); // LED on digitalWrite(DHT22_POWER, HIGH); // turn DHT sensor on DHT22_ERROR_t errorCode; Sleepy::loseSomeTime(2000); // Sensor requires minimum 2s warm-up after power-on. errorCode = myDHT22.readData(); // read data from sensor activityLed(0); // LED off if (errorCode == DHT_ERROR_NONE) { // data is good tinytx.temp = (myDHT22.getTemperatureC()*100); // Get temperature reading and convert to integer, reversed at receiving end tinytx.humidity = (myDHT22.getHumidity()*100); // Get humidity reading and convert to integer, reversed at receiving end tinytx.supplyV = readVcc(); // Get supply voltage rfwrite(); // Send data via RF if (LEDpin) { blink(LEDpin, 2); // blink LED } } digitalWrite(DHT22_POWER, LOW); // turn DHT22 off for (byte i = 0; i < SENDDELAYMULTIPLY; ++i) // We need to loop because the max sleep is 60sec Sleepy::loseSomeTime(SENDDELAY); //JeeLabs power save function: enter low power mode for 60 seconds (valid range 16-65000 ms) } |