Arduino Ethernet S0 Monitor

Durch die "Bastelei" mit dem Arduino für eine Ethernet VoIP-Probe habe ich einen zweiten Einsatzzweck erkannt, nämlich den zum Zählen von Impulsen und dem Auslese per SNMP oder HTTP. Der Arduino Ethernet bringt dazu schon alles mit.

Das ganze hier ist natürlich nur eine Vorarbeit. Vielleicht kann ich den Arduino Ethernet auch als VoIP-Probe oder UCWA-Client einsetzen.

Die Komponenten

Folgende Dinge sind beim Arduino Ethernet schon vorhanden:

  • Massenhaft Anschlüsse
    die per Software als Eingang definiert und optional mit Pullup-Widerstand versehen werden können. Der von mir genutzte Arduino Ethernet hat nach Abzug von PIN 4,9,10-13 immer noch 6 digitale und 6 analoge Pins frei, wobei die analogen Ports wie digitale Ports genutzt werden können.
  • EEprom
    zum permanenten Speichern von Zählerständen, auch nach Stromausfall/unterbrechung kann das lokal vorhandene EEprom genutzt werden
  • Ethernet-Port
    Beim Arduino Ethernet ist der Anschluss schon "onBoard". Bei anderen Arduinos kann er als "Shield" nachgerüstet werden.
  • Libraries für SNMP und TCP
    Mit der "Ethernet"-Library ist ganz schnell ein einfacher TCP/UDP und damit auch Webserver gebaut.

Funktionsweise

Leider sind nur zwei Eingänge des Arduino auch als Interrupt zu verwenden, so dass man mehr Eingänge entweder aufwändig triggern muss oder man pollt gleich die Daten. Es gibt Beispiele im Internet, die ein Polling betreiben aber die Zeit zwischen zwei Impulsen müssen (Siehe http://blog.elektrowolle.de/2011/07/26/s0-messwerterfassung-von-stromzahlern/) und damit den aktuellen Verbrauch z.B. bei S0-Stromzählern ermitteln. Mein Ziel ist aber eine Erfassung der Zählerstände. Also ist das Programm recht einfach

  • Initialisiere die Eingänge und das Netzwerk
  • Polle die Eingänge (mit Entprellung)
    Erhöhe den Counter, wenn sich der Status geändert hat
  • Wenn ein Counter durch 1000 teilbar ist, dann schreibe ihn in das EEProm
    Da in der Regel 1000 Impulse ein 1kWh sind, wird so alle 1 kWh-Stunden ein Status gesichert. Bei angeblich 100.000 Schreibvorgängen müsste man schon 100 MWh (oder bei 30cent/kWh) für 30.000€ Strom bezogen haben. Wenn ihnen das nicht reicht, dann können Sie den Source gerne ändern.
  • Stelle eine SNMP/HTTP-Schnittstelle bereit
    Es dürfte das einfachste sein, wenn ein HTTP-Get einfach die Zahlen liefert und optional auch ein SNMPGet

Es bleibt bei dem Verfahren natürlich eine ungenauigkeit, z.B. wenn ein Impuls zu schnell gewesen sein sollte aber viel mehr Abweichung bekommt man beim Stromausfall o.ä. Daher ist es wichtig, dass man nicht nur den absoluten Zählerstand lesen sondern auch setzen kann. Aber auch wenn man den Zähler nicht setzen kann oder will, so können die meisten Programme problemlos Differenzen zwischen zwei Messungen ermitteln und verwerten.

Zählen ohne Prellen

Im Internet finden sich viele Quellen zum Zählen und müssen auch von Impulsen mit dem Arduino. Interessanterweise müssen viele Autoren die Zeit zwischen zwei Impulsen um damit dann den aktuellen Verbrauch in Watt zu müssen. Das ist nett, wenn man die Anzeige z.B.: auf einem LCD direkt ausgeben will. für meinen Einsatzzweck würde ich aber lieber einfach die Anzahl zählen und diese abfragbar machen. Die Ausgabe kann ja wieder PRTG ermitteln und liefern. Eine Realtime-Ausgabe ist gar nicht mein Ziel. Vielleicht später noch mal ein Logger.

Zuerst gilt es aber mal die Impulse zu zählen und zu entprellen. analoge Signale brauchen ggfls. ein paar Microsekunden um einen stabilen Stand zu erreichen. Zuerst wollte ich also mal sehen, wie schnell so ein Arduino denn die Signale abfragen kann. Also habe ich eine Schleife gebaut, die 100000 mal einen Wert ausliest und die vergangene Zeit als auch die "Signalwechsel ausgibt:

// Test Digital Reading Performance /* 
const int pushButton = 2;

void setup() {
  Serial.begin(9600);             // INIT Serial Port
  Serial.println("Setup");
  pinMode(pushButton, INPuT);     // make the pushbutton's pin an input:
  digitalWrite(pushButton, HIGH); // turn on pullup resistors
}

// the loop routine runs over and over again forever:
void loop() {
  Serial.println("Start");
  long start = millis();
  long countchange = 0;
  int oldstate = 0; int currentstate = digitalRead(pushButton); für (long i=0; i<100000; i++) {
    currentstate = digitalRead(pushButton);
    if (currentstate != oldstate) { // read the input pin:
      countchange++;
      oldstate = currentstate;
    }
  }
  Serial.print("Duration: ");    Serial.println(millis() - start);
  Serial.print("countchange: "); Serial.println(countchange);
  Serial.println("Done");
}

Der PIN 2 wird als Eingang definiert aber von Arduino selbst auf "High" gesetzt. Ich habe dann den PIN2 mit einem Kabel auf "GND" verbunden und einmal das Kabel schnell heraus gezogen und nach der ersten Messung wieder eingesteckt. Das Ergebnis liefert mit einige interessante Daten:

Die gemeldete "Duration" war immer um die 500ms  für wohlgemerkt 100.000 Messungen ohne weitere Verarbeitung oder 5us/Messing. Ganz schön flott, auch wenn es eine ganz einfach Aufgabe ist. Um das Thema "Prellen" zu klären, hab ich nun einfach mal die Anzahl der "High" und "Low" Wechsel gemessen. Und da ist ein einfaches "Kabel Rausziehen" schon 4-9 und mehr "Wechsel" und das Einstecken auch mehr als einen Wechsel erkennt. Also müssen mehrere Messwerte mit definiertem Abstand nacheinander gemessen werden, um einen eindeutigen Status zu ermitteln.

Achtung: 
Wenn Sie mit "Serial.print" Debug-Ausgaben generieren, dann verzögert dies merklich die Verarbeitung! Dies ist also nur für "nicht Echtzeit-Aufgaben.

Aus dem gleichen Grund ist "DELAY()" eine sehr unschöne Funktion. Der Arduino nutzt kein "Multitasking" sondern arbeitet sequentiell den Code immer und immer wieder ab. Ein "Delay" hält den kompletten Code für die aufgeführte Zeit an, was nervt, wenn quasi parallel z.B. IP-Pakete verarbeitet werden sollen oder mehrere Pin überwacht werden sollen. Nur Pin 2 und 3 können als Interrupt daraus Ausbrechen.

Elektrischer Anschluss S0

Die SO-Schnittstelle eines Zählers schließt eine Verbindung, wenn ein neuer Impuls zu melden ist. Dabei beschreibt Wikipedia ( http://de.wikipedia.org/wiki/S0-Schnittstelle), dass die Impulse mindestens 30ms lang sein müssen und die ansteigende Flanke unter 5ms liefen muss.


Basierend aus : http://commons.wikimedia.org/wiki/File:S0-Schnittstelle.jpg

Wer ein wenig mit Digital/Analogtechnik arbeitet, weiß dass diese Bilder idealisiert sind und Ströme durch Kapazitäten und Induktionen "unsauberer" sind. Wer also solche Signale misst sollte aufpassen, das nicht mehrere Messungen in der Flanke liefen und ggfls. "prellen" und so mehrfach gezählt werden.

Auch elektrisch gilt es Spannungen und Ströme zu beachten. Auch dabei hilft Wikipedia erst mal weiter, welche ein Ersatzschaltbild vorhält, welches ist noch etwas vereinfacht habe, da ich auf der Auswerteseite die 220V Einheit eher verwirrend finde:


Basierend aus : hhttp://commons.wikimedia.org/wiki/File:S0-Schnittstelle.jpg

Interessant ist hier nun, dass der Zähler (Links) keine eigene Spannung auflegt, sondern die Auswerteeinheit in einem weiten Bereich flexibel ist und der maximale Strom begrenzt sein muss. Der Arduino hat mehrere Pins, die per Default als "Input" geschaltet sind. Im Gegensatz zum Schaltbild würde ich aber eine etwas andere Schaltung anstreben, bei der der Arduino die +5V über einen internen Pullup-Widerstand liefert und der Zähler diese Leitung gegen Masse kurzschließt. Sollte das nicht gehen, dann kann ich immer noch die analogen Ports nutzen.

Ich nutze einen Arduino Ethernet, weil mir natürlich das Thema "Netzwerk" am Herzen liegt. Das bedeutet aber auch, dass einige der Pins nur eingeschränkt nutzbar sind (Siehe auch http://arduino.cc/en/Main/ArduinoBoardEthernet unter Input). Interessant sind hier die beiden Pins 2/3, da sie einen Interrupt auslösen können und die analogen Ports, die Spannungen zwischen 0 und 5 Volt mit 1024bit Auflösung müssen.

Also schalte ich einen freien digitalen PIN über den internen 20-100kOhm Pullup-Widerstand auf HIGH und warte, ob es der S0-Zähler mit seiner Diode und den 200Ohm diesen auf ausreichend "Low" zieht.

 

Alternativ kann man auch die analogen Eingänge nutzen, welche direkt die Spannung müssen. Man muss dann allerdings einen Spannungsteiler bauen, indem man z.B.: mit mit einem 4,7kOhm Widerstand den analogen Eingang auf "High" legt und der S0-Geber diesen auf "Low" zieht. Entsprechend werden sich dann Zwischenwerte zwischen 0 und 1024 einstellen.

Wenn man die analogen Eingänge nutzen will, dann geht das auch. 0 = 0Volt,  3,3V = ca. 680 .und 5V = 1023

Kopplung per Licht

Wenn ihr Zähler keinen S0-Anschluss hat oder sie die elektrische Verkabelung scheuen können sie natürlich auch "optisch" arbeiten. Eine im Takt des Verbrauchs "blinkende" LED oder eine Reflexlichtschranke, die den Stich auf einem Drehrad erkennt, ist natürlich auch eine Möglichkeit. Das kann ein Fotowiderstand, eine Fotodiode oder eine Fototransistor sein. Alle haben unterschiedliche Charakteristika. Wichtig ist dabei primär, dass die Impulse zuverlässig erkannt werden, d.h. der Sensor empfindlich und schnell genug auf die Lichtsignale reagieren muss und Fremdlicht möglichst nicht stört. Es gibt spezielle Versionen mit einer Tageslichtfilterung, die aber für die Erkennung einer roten LED natürlich nicht geeignet sind. Hier muss eine Version zum Einsatz kommen, die im Bereich 610-760nm empfindlich ist und mit einem Gehäuse das Umgebungslicht abschirmt. Eine einfache Schaltung ist allein durch einen Fototransistor und einen Widerstand möglich. Der Fototransistor schaltet durch, wenn er angeleuchtet wird. Denkbar sind daher drei Schaltungen

 

Über einen der analogen Eingänge des Arduino kann man dann die Helligkeit einlesen. So spart ersetzt Software den ansonsten für einen klaren Digitaleingang eventuell erforderlichen Schmitt-Trigger. Zur Sicherheit sollte man aber auch noch hier entprellen. Technisch bauen wir aber einen einfachen Optokoppler nach, bei dem die LED im Zähler schon vorhanden ist.

Die analogen Eingänge eines Arduino haben intern aber auch schon einen "Pullup" Widerstand (20-50kOhm). Auch wenn der Port ein "Eingang" ist kann man dort eine "1" drauf schreiben:

pinMode(A0, INPuT);
digitalWrite(A0, HIGH);

Oder (ab IDE 1.0.1)

pinMode(A0, INPuT_PuLLuP);

Normal macht man das bei Digitaleingängen, um diese "zuverlässig" auf 1 zu ziehen. Hier reicht es aber um genug Energie zu liefern um die Diode ,z.B. (z.B. BPW 40) einfach direkt anzuschließen.

Finaler Schaltplan und Code fehlt noch

Messen und Merken

Mit diesen Vorarbeiten kann ich nun das eigentliche Messprogramm starten, welches eine definierte Anzahl an Eingängen einliest. Dazu starte ich nach der Initialisierung erst mal damit, das ich die aktuelle Zeit in "milis()" auslesen und am Ende der Abfrage aller Sensoren einen "Delay" einbaue, um z.B. 10ms zu warten. Da ein S0-Signal mindestens 30ms dauert, müssen zwei aufeinanderfolgende Messungen den gleichen Wert haben um als "Gültig" angesehen zu werden.

Aufgrund der Probleme mit "DELAY" lege ich die Messwerte eines Durchlaufs in einem Speicher ab und starte die nächte Messung erst wieder, wenn eine definierte Wartezeit (z.B. 10ms) vergangen ist. Alles andere ist dann wieder normaler Spaghetti-Code, um die definierten Ports nacheinander auszulesen und den Status mit dem vorherigen Status zu vergleichen. Immer wenn sich der "stabile" Status von 0 auf 1 erhöht, wir der Counter des Sensors entsprechend um 1 erhöht.

Allerdings ist das mit dem Debuggen beim Arduino so ein Problem, denn es gibt keine "Break-Points" oder andere Debugging-Möglichkeiten. Einzig eine Ausgabe auf LEDs, LCDs, Netzwerk oder eben den seriellen Port ist ein Weg. Der serielle Ports ist am einfachsten zu nutzen aber verzögert die Messung. Um nun nicht massenhaft "Serial.PrintLn()"-Zeilen bei Bedarf zu aktivieren, habe ich mir eine kleine Funktion gebaut:

const int debuglevel = 1;  // 0 = nodebug 

void debug(int level, boolean newline, String message) {
  // 
  if (level == debuglevel ) {
    if (newline) {
      Serial.println(message);
    }
    else {
      Serial.print(message);
    }
  }
}

void setup() {
  debug(2,true,"-----  Initializing Pins START --------------");
}

Entsprechend kann ich nun einfach im Code die Interessanten Stellen einfügen und mit einem "Level" versehen. Den ganzen anderen Code gebe ich hier nicht komplett wieder, sondern den finden Sie als Download weiter unten.

Ausgabe per HTTP/SNMP

Damit ich die im Skript erfassten Daten abholen kann, habe ich mich zuerst für den HTTP-Zugang entschieden, da man den recht einfach auch mit einem Browser testen kann und Port 80 meist "frei" ist. für SNMP braucht man eine OID und einen passenden Client. Auf der Arduino-Seite gibt es schon eine gute Vorlage (Examples > Ethernet Library Web Server http://arduino.cc/en/Tutorial/WebServer), die schnell integriert werden kann.

Man braucht eine MAC-Adresse und eine IP-Adresse und startet einen Ethernet Server auf dem gewünschten Port.

Sie können gerne das Beispiel verwenden. Erst wenn Sie mehrere Arduinos im gleichen LAN verwenden, müssen Sie auf eindeutige Adressen achten. Leider hat das Ethernet-Modul selbst keine MAC-Adresse vorgegeben.

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress staticip(192,168,178,177);
EthernetServer httpserver = EthernetServer(80);

Um DHCP zu verwenden, habe ich den Code so erweitert, dass Sie einfach ip(0,0,0,0); eingeben. Welche IP-Adresse das Device bekommt hat, sollten Sie dann am DHCP-Server anhand der MAC-Adresse finden können. So sieht es bei einer Fritz!Box aus:

Dann muss man in der SETuP-Routine den Ethernet Service und den Server starten:

Ethernet.begin(mac, staticip)
httpserver.begin()

Der Server lauscht quasi im "Hintergrund" nach neuen Verbindungen. Sie müssen Sie dann in der Schleife nur ab und an abfragen und bearbeiten. Hier der Ausschnitt aus dem Code zum Senden der nackten Textinformation.

EthernetClient httpclient = httpserver.available();
if (httpclient == true) { für (int count = 0; count < pinCount; count++) {
      httpclient.print(count); 
      httpclient.print("="); 
      httpclient.println(counter[count]);
   }
   httpclient.stop();
}

Und damit bekomme ich bei einem HTTP-Zugriff auch ohne vorheriges GET oder POST sofort die Information:

 

Das kann nun natürlich wieder ganz einfach mit einem PRTG:Custom Sensor ausgelesen werden.

Status und Debugging

Auf dem Arduino Ethernet ist auch eine kleine LED  (L9) neben dem Ethernet-Anschluss verbaut, welche im Gegensatz zu den anderen Arduinos aber nicht auf Port 13, sondern auf Port 9 liegt. Im Code ist hinterlegt, dass beim normalen "Scan-Betrieb" die LED alle Sekunde einmal blinkt. Damit auch das nicht mit der unglücklichen "DELAY()" Funktion erfolgt, musste ich auch hier erst mal einen Code am ende der LOOP-Funktion addieren.

if ((millis() - starttimestatus) > 1000) {  // Wait für elapsed time to debounce
  starttimestatus = millis();
  statusledstate = !statusledstate;
  if (statusledstate) {
    debug(1,true,"HeartbeatLED HIGH");
    digitalWrite(statusled, HIGH);
  }
  else { 
    debug(1,true,"HeartbeatLED LOW");
    digitalWrite(statusled, LOW);
  }
}

Wer auch im Betrieb kein Terminal am COM-Port angeschlossen hat, wird sich aber auch über andere Indikatoren freuen. Entsprechend habe ich eine Funktion gebaut, mit der ich einen finalen Error als Blinkzeichen ausgeben kann und das Problem halte.

void finalerrorstatus (int errorcount, String message) {
  debug(0,true," Raeaching a finalerrorstatus!");
  while(true){      // no point in carrying on, so do nothing forevermore: für (int ledcount = 1; ledcount <= errorcount; ledcount++) {
      digitalWrite(statusled, HIGH);   // turn the LED on
      delay(500);
      digitalWrite(statusled, LOW);    // turn the LED off
      delay(500);
    }
    delay(4000);
  }
}

Hier kann ich natürlich problemlos mit "Delay" arbeiten, da das Programm sowieso nicht fortgesetzt wird. Folgende BLINK-Codes sind aktuell implementiert.

Blinkintervall  Bedeutung

3x 

Keine IP-Adresse per DHCP bekommen. Überprüfen Sie den Netzwerklink, den DHCP-Server, das VLAN und starten Sie dann den Arduino neu. Es wird nicht versucht erneut eine IP-Adresse anzufordern.

Parallel werden natürlich ein paar Informationen über die serielle Schnittstelle ausgegeben. Dies sind aber nur wenige, um den Ablauf des Programms nicht zu Stark zu verzögern. Im wesentlichen sehen Sie hier den Starte mit der Initialisierung mit der IP-Adresse und dann alle "Events" auf den verschiedenen Ports und Anfragen auf dem LAN.

Bei Gelegenheit kann ich überlegen diese Daten per UDP (Syslog) zu versenden und noch etwas auszuweiten und insbesondere auf Tastatureingaben eventuell zu reagieren.

Ich habe noch keinen Code eingebaut, der nach einiger Zeit selbst einen Reset ausführt, z.B. mit:

void (softReset){
   asm volatile ("  jmp 0");
}

Denkbar wäre natürlich einen Pin als Ausgang mit einer externen Elektronik zu versehen, die einen harten Reset auslöst, wenn das Programm nicht immer wieder über diesen Pin einen Kondensator entlädt.

Ebenso wenig mache ich aktuell gebrauch davon, über die serielle Schnittstelle mit Steuersequenzen auf das Programm einzuwirken. Hier ist sicher noch Potential, z.B.: um IP-Adressen oder MAC-Adressen über den Weg zu setzen und im EEProm zu speichern.

Downloads

Damit Sie die Lösung selbst umsetzen können, benötigen neben der eigentlichen Software, in der Arduino-Welt als "Sketch" bezeichnet, auch die Arduino IDE zum Download und einen Programmer.

Arduino IDE
http://arduino.cc/en/Main/Software

S02http.ino
s02http.ino.txt

Das passende PRTG-Skript liefe ich noch nach

Weiterentwicklung

An dem aktuellen Stand habe ich erst mal inne gehalten. Mit dem Arduino Ethernet kann ich problemlos 12 Pins erfassen und per HTTP abfragen. Ich habe noch keine Dauertests damit gemacht und kann daher noch nicht sagen, wie akkurat die Zählung ist. Aber gibt durchaus weitere Dinge, die interessant sein könnten, z.B.:

1Wire

Wenn ich eh nicht alle IO-Pins brauche, dann sollte irgendwo noch ein Pin frei sein, um über 1-Wire z.B. Temperaturen auszulesen. Beispiele gibt es dazu genügend und elektrisch kann eine alte gebrauchte ISDN-Spinne ja die verschiedenen Sensoren einfach verklemmen.

Eventuell ist es aber besser, hier einen Subcontroller zu nutzen, wenn die 1-wire-Library ansonsten das Programm "anhält" und damit S0-Impulse vielleicht überlesen werden.

SNMP

Basierend auf der Ethernet-Anbindung und der Abfrage per HTTP könnte man auch eine SNMP-Abfrage umsetzen.

Private Enterprise Number (PEN) was requested from iana http://pen.iana.org for the Arduino project.  Basically this allows users to implement their own objects under the following node; .iso.org.dod.internet.private.enterprises.arduino (.1.3.6.1.4.1.?????),
Quelle: http://forum.arduino.cc/index.php?topic=38103.0

EEprom-Speicher / SD Speicher

Aktuell sind die Messwerte flüchtig, d.h. man sieht immer nur die Summen seit dem letzten Reset. Auf dem Arduino sind aber durchaus ein paar Speicherstellen in einem EEprom, die beschrieben werden können. So könnte z.B. der Zählerstand immer mal wieder dort abgelegt werden. Wenn 1000 Impulse ein kWh sind, könnte man jedes kWh speichern.

Alternativ wäre natürlich eine Ablage auf der SD-Karte möglich. Durch den Platz könnte man hier natürlich deutlich mehr Daten ablegen und sogar einen Logger daraus bauen.

Funk (RF868, WiFi u.a.)

Nicht immer ist eine Ethernet-Dose in der Nähe und wenn man kein Kabel oder PowerLan legen will, dann könnte eine andere Plattform ohne Ethernet eine interessante Option sein. so z.B. die JeeNode Geräte:

Alternativ könnte der Arduino auch einen klassischen Funksender (HomeMatic, FS20 o.ä.) LCN, bedienen.

Andere "kleine Boards" mit (W)LAN-Anschluss

Der Arduino Ethernet ist nur ein Kandidat, den ich zufällig von früheren Experimenten noch rumliegen hatte. Es gibt natürlich noch sehr viele andere Boards, die mit GPIO-Pins und einer Ethernet Schnittstelle ausgestattet sind und so eine Aufgabe vollbringen können.

Interessant ist, dass viele Boards nicht viel teurer als der Arduino sind, aber mehr RAM und schnellere CPus, teilweise sogar noch Videoausgaben und USB-Ports haben und Linux darauf läuft. Auf der Soll-Seite stehen aber oft ein höherer Energieverbrauch, mehr Abwärme und die Komplexität eines eigenen Betriebssystems.

Boards ohne LAN/WLan

Interessant können aber auch reine autarke Datenlogger sein oder solche, die per USB an einen anderen Host angeschlossen sind. Es gibt sehr viele auch günstige Boards, die problemlos Takte zählen und speichern können und vielleicht über eine andere Funktechnik oder USB weitergeben wollen.

Weitere Links