Rasberry Pi Emoncms und RFM12Pi – Sensordaten visualisieren

Erstellt am 26. November 2014 von At2oo1

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.

RFM12Pi als Empfängermodul am Raspberry Pi

Emoncms auf dem Raspberry Pi zur Speicherung
und Visualisierung


Selbstgebauter Sender mit RFM12B, Attiny84
und DHT2 Sensor

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.

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.

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.

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.

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.

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.

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)
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

Da das Raspberry Pi Modul als Dienst läuft tragen wir es noch in den Autostart ein und starten den Dienst dann zum ersten mal manuell.

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

Die Installation ist hiermit abgeschlossen, die Initiale Konfiguration muss noch durchgeführt werden.

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://192.168.178.30/emoncms/)
  • 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.

SendRFM12Pi

//----------------------------------------------------------------------------------------------------------------------
// 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 <DHT22.h> // https://github.com/nathanchantrell/Arduino-DHT22
#include <JeeLib.h> // https://github.com/jcw/jeelib

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)
}