ENEN DEDE
home
Station ladebereit

Damit unsere Besucher und auch wir selbst den aktuellen Verbrauch und die Kosten direkt an der Ladestation ablesen können, musste ein Monitor in die Wallbox eingebaut werden. Außerdem sollte der Ladezustand der Box auf der Website angezeigt und eine Info per Mail verschickt werden.

Hier eine Anleitung, wie wir an die Informationen in der Ladestation herankommen:

Den Ladezustand erfahren wir über den nachträglich eingebauten, digitalen Drehstromzähler (siehe Hardware). Dieser sendet über den "S0-Bus" 800 Impulse pro Kilowattstunde. Nun muss nur noch der Impuls ausgelesen und in Kilowattstunde und Euro umgerechnet werden, dafür eignet sich prima ein Raspberry Pi.

Das Raspberry Pi ist ein kleiner, sehr preiswerter Mini-Computer, auf dem ein vollwertiges Linux-Betriebssystem installiert werden kann - in unserem Fall "Debian Wheezy". Über die Grundkonfiguration eines Raspberry Pi's gibt es jede Menge Informationen im Netz. Hier alles Notwendige, um unsere Wallbox zu modifizieren.

  1. Der Dienst, der die Impulse ausliest und aufbereitet:

    Der S0-Bus wird an den GPIO 2 (+) und GROUND (-) am Pi verkabelt (Pins 3 und 6). Die Programmiersprache des Raspberry Pi ist Python, deshalb ist auch der Dienst in Python geschrieben. Die Datei heißt "impsim" und wird unter /usr/local/bin/emob/ abgespeichert. Bitte unbedingt beachten: die Datei muss über ausführbare Rechte verfügen!

    Update 20.03.2016

    Durch die super nette Zusammenarbeit mit Benjamin, der den Code in seiner Ladestation eingebunden hat, sind wir dahinter gekommen, dass der Dienst unter 100% Prozessorlast läuft. Daher habe ich alles etwas umgeschrieben und jetzt läuft alles nur noch auf maximal 25% Prozessorlast. Der Vollständigkeit halber unter diesem Link der alte Code.

    Update 21.05.2016

    Wir mussten feststellen, dass der Leaf zum Ausgleich der Batterie drei Nachladungen macht. Dabei fällt die Wattzahl unter der Zählschwelle des Zählers und impsim dachte der Ladevorgang wäre dann beendet. Somit startete er wieder nach 4 Minuten und beendete sich wieder nach 2 Minuten. Insgesamt 7 Mails ... Nervig ...

    Was ich gemacht habe, ist den Code so angepasst, dass eine Ladestartmail nur geschickt wird, wenn der letzte Ladevorgang mindestens 330 Sekunden her ist und die Ladeendmail nur geschickt wird, wenn mindestens 0.3 kWh verbraucht wurden.

    Damit ist die Mailflut gestoppt. Das Ganze hat aber den Nachteil, wenn innerhalb von 5 Minuten zwei verschiedene Autos laden, das als ein Vorgang angezeigt wird. Das kommt bei mir aber nie vor. Stau an der Ladestation :-) Das wäre nur zu lösen, wenn man das "Kabel angeschlossen" Signal des Piloten an einen weiteren GPIO des Pi's schickt und das als Ladeanfang / ende Signal hernimmt. Wäre cool, vielleicht ein zukünftiges Update ...

    Der Vollständigkeit halber unter diesem Link der Code ohne Leaf-Korrektur.

    Und ab hier der neue Code:

    /usr/local/bin/emob/impsim:
  2. #!/usr/bin/env python
    
    import subprocess
    import smtplib
    import socket
    
    from email.mime.text import MIMEText
    
    import time
    import MySQLdb as mdb
    import sys
    
    import RPi.GPIO as GPIO, time, os
    
    from sys import stdout 
    
    from datetime import datetime
    
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    GPIO.setup(2, GPIO.IN)
    
    gread = 0
    dread = 0
    hcount = 0
    load = 0 
    lch = 0
    imp = 0
    lcheck = 0
    first = 1
    
    now = datetime.now()
    last = now
    lastlast = last
    delta = now - last
    
    pstart = 1
    
    Empf = 'your(at)mail(dot)here'
    Abs = 'your(at)mail(dot)here'
    Passw = 'password'
    smtpserv = smtplib.SMTP_SSL('mailserver_hostname',465)
    smtpserv.ehlo()
    
    # In Account einloggen
    smtpserv.login(Abs, Passw)
    
    Wtxt = 'Ladestation neu gestartet'
    cmsg = MIMEText(Wtxt)
    cmsg['Subject'] = 'Nachricht von der Ladestation'
    
    cmsg['From'] = 'ladestation'
    cmsg['To'] = Empf
    
    smtpserv.sendmail(Abs, [Empf], cmsg.as_string())
    smtpserv.quit()
    
    def RCtime (channel):
    	global first
    	global last
    	global lcheck
    	global gread
    	global dread
    	global hcount
    	global imp
    	imp = 1
    	now = datetime.now()
    	delta = now - last
    	lastlast = last
    	last = now
    	delta = last - lastlast
    	if delta.seconds > 330:
    	    first = 1
    	    hcount = 0
    	    gread = 0
    	    dread = 0
    	if delta.seconds < 40:
    	    if lcheck == 1:
    		first = 0
    		hcount += 1
    		if hcount > 100:
    		    hcount = 1	    
    		gread += 0.00125
    		dread += 0.00125
        		writedb(gread,hcount)
    	    else:
    		setload()
    		lcheck = 1
    		writerem(1)
    		if first == 1:
    		    writedb(gread,hcount)
    		    postit(1, 0)
    		first = 0
    
    def setload ():
        con = None
        try:
            con = mdb.connect('localhost', 'db_user', 'db_pass', 'emob');
            cur = con.cursor()
    	cur.execute("UPDATE emob.settings SET loading='1'")
        	con.commit()
        except mdb.Error, e:
            print "Error %d: %s" % (e.args[0],e.args[1])
            sys.exit(1)
        finally:        
            if con:    
                con.close()
    
    def unsetload ():
        con = None
        try:
            con = mdb.connect('localhost', 'db_user', 'db_pass', 'emob');
            cur = con.cursor()
    	cur.execute("UPDATE emob.settings SET loading='0'")
        	con.commit()
        except mdb.Error, e:
            print "Error %d: %s" % (e.args[0],e.args[1])
            sys.exit(1)
        finally:        
            if con:    
                con.close()
    
    def writedb (akw,fcount):
        loading = 0
        con = None
        try:
            con = mdb.connect('localhost', 'db_user', 'db_pass', 'emob');
            cur = con.cursor()
    	cur.execute("SELECT loading FROM emob.settings")
    	ldata = cur.fetchone()
    	loading = ldata[0]
    	if loading == 1:
    	    cur.execute("SELECT total FROM emob.countings")
    	    data = cur.fetchone()
    	    tkw = data[0]
    	    if fcount == 100:    
    		tkw += 1
    	    sql = "UPDATE emob.countings SET actual=%s, total=%s"
    	    cur.execute(sql, (akw,tkw))
        	con.commit()
        except mdb.Error, e:
            print "Error %d: %s" % (e.args[0],e.args[1])
            sys.exit(1)
        finally:        
            if con:    
                con.close()
        return loading
    
    def writerem (rload):
        con = None
        try:
            con = mdb.connect('remote_host', 'db_user', 'db_pass', 'ips');
            cur = con.cursor()
    	sql = "UPDATE ips.station SET loading=%s"
    	cur.execute(sql, (rload))
        	con.commit()
        except mdb.Error, e:
            print "Error %d: %s" % (e.args[0],e.args[1])
            sys.exit(1)
        finally:        
            if con:    
                con.close()
    
    def postit(pload, pkw):
        Empfaenger = 'your(at)mail(dot)here'
        Absender = 'your(at)mail(dot)here'
        Passwort = 'password'
        smtpserver = smtplib.SMTP_SSL('mailserver_hostname',465)
        smtpserver.ehlo()
    
        # In Account einloggen
        smtpserver.login(Absender, Passwort)
    
        # Text
        if pload == 1:
    	Wert = 'Ladevorgang gestartet'
        if pload == 0:
    	Wert = 'Ladevorgang beendet: %s kWh' % pkw
        msg = MIMEText(Wert)
    
        # Betreff + Datum
        msg['Subject'] = 'Nachricht von der Ladestation'
    
        # Absender
        msg['From'] = 'ladestation'
    
        #Empfaenger
        msg['To'] = Empfaenger
    
        # E-Mail abschicken
        smtpserver.sendmail(Absender, [Empfaenger], msg.as_string())
        smtpserver.quit()
    
    unsetload()
    writedb(0,0)
    
    GPIO.add_event_detect(2, GPIO.RISING, callback = RCtime, bouncetime = 200)
    
    try:
        while True:
    	imp = 0
            time.sleep(60)
    	if imp == 0 and lcheck == 1:
    		unsetload()
    		lcheck = 0
    		writerem(0)
    		if dread > 0.3:
    		    postit(0, gread)
    		dread = 0
    
    except KeyboardInterrupt:
        GPIO.remove_event_detect(2)
    

    Einen kleinen Haken hat der neue Code. Die Beendigung des Ladevorgangs wird erst nach einer Minute erkannt. Das lässt sich ändern, indem die Zeilen

    if delta.seconds < 40:
    und
    time.sleep(60)

    geändert werden in z.B.

    if delta.seconds < 10:
    und
    time.sleep(15)

    Das bedeutet dann aber das nur Impulse die mindestens alle 10 Sekunden kommen als Ladevorgang erkannt werden. 800 Impulse pro kW, 6 Impulse pro Minute, 360 Impulse pro Stunde. Das Script triggert dann also erst bei einem Verbraucher von ca. 500 Watt. Gut zu wissen beim Testen :-)

  3. Der Dienst nimmt die Impulse entgegen und entscheidet, ob der letzte Impuls älter ist als 30 Sekunden. Wenn ja, bedeutet das: keine Ladeaktivität vorhanden. Sind die Impulse aber kürzer als 30 Sekunden in Folge, wird geladen (das entspricht einem Ladestrom von ca. 150 Watt). Wenn geladen wird (oder das Laden beendet wird), werden Informationen in eine lokale (und in eine remote) MySQL-Datenbank gespeichert. Die lokale Datenbank heißt "emob" und muss angelegt werden. Die Importdatei für die Datenbank können Sie hier downloaden. Es muss in MySQL noch ein Benutzer mit Passwort und Zugriffsrechte für die emob-Datenbank angelegt werden. Diese Informationen müssen auch im Quellcode von impsim jeweils beim "mdb.connect localhost" als db_name und db_pass eingetragen werden.
     
  4. Auf einem (remote) Mail- und Webserver im Internet muss auch eine MySQL-Datenbank vorhanden sein. Hier wird die Datenbank "ips" angelegt. Die Importdatei dazu finden Sie hier. Auch hier einen Benutzer mit Passwort und Zugriffsrechte auf "ips" anlegen und im impsim-Quellcode unter ""mdb.connect remote_hostname" eintragen. Dabei auch "remote_hostname" gegen den Hostname des Internetservers austauschen.
     
  5. Damit das Versenden der Mails klappt, müssen Sie noch die Informationen "your(at)mail(dot)here", das dazugehörige Passwort "password" und den Mailserver "mailserver_hostname" im impsim-Quellcode eintragen. Die Mails werden SSL-verschlüsselt übertragen.
     
  6. Der impsim-Dienst muss auf dem Raspberry Pi automatisch beim Systemstart anfahren. Dafür wird die Datei /etc/rc.local angepasst:
     
  7. #!/bin/sh -e
    #
    # rc.local
    #
    # This script is executed at the end of each multiuser runlevel.
    # Make sure that the script will "exit 0" on success or any other
    # value on error.
    #
    # In order to enable or disable this script just change the execution
    # bits.
    #
    # By default this script does nothing.
    
    # Print the IP address
    _IP=$(hostname -I) || true
    if [ "$_IP" ]; then
      printf "My IP address is %s\n" "$_IP"
    fi
    
    /usr/local/bin/emob/impsim &
    exit 0

    Update: Ein Hinweis von Benjamin, der die Software bei sich implementiert hat, war, dass er die Zeile "/usr/local/bin/emob/impsim &" in "(/bin/sleep 60 && /usr/local/bin/emob/impsim) &" ändern musste. Thanx!

  8. Der Verbrauch und die Kosten werden auf dem kleinen Monitor über eine Website im Kioskmodus dargestellt. Dazu muss auf dem Raspberry Pi der Apache Webserver installiert sein. Unter der document root (/var/www) wird diese Datei entpackt. In der index.php müssen db_user und db_pass wieder für die emob-Datenbank angepasst werden. In der emob-Datenbank kann unter "pricing" der Kilowattstunden Preis angepasst werden.
     
  9. Wenn der impsim-Dienst, die Datenbank und der Webserver funktionieren, müssen wir noch dafür sorgen, dass das Raspberry Pi im grafischen Modus hochfährt und dann einen Webbrowser im Kioskmodus mit unserer Anzeige startet. Zuerst also der grafische Modus:

    Dazu legen Sie die Datei custom-x-startup.sh ab unter "/etc/init.d".

    Installieren Sie mit "apt-get install midori" den Midori Browser und passen dann die Datei /etc/xdg/lxsession/LXDE/autostart an:
     
  10. @lxpanel --profile LXDE
    @pcmanfm --desktop --profile LXDE
    @xscreensaver -no-splash
    @xset s off
    @xset -dpms
    @xset s noblank
    @midori -e Fullscreen -a http://localhost/index.php
    
  11. Stellen Sie sicher, dass Sie auf das Raspberry Pi mittels ssh zugreifen können, nach dem nächsten Start haben Sie keine (einfache) direkte Konsole mehr! Wenn das sichergestellt ist, führen Sie auf der Konsole "sudo update-rc.d custom-x-startup.sh defaults" aus. Bei einem Neustart startet das Pi nun in den grafischen Modus mit der Anzeige im Kioskmodus:



     
  12. Bleibt noch die Anzeige des Ladezustands auf dem Remote_Host. Dazu folgenden Code in eine php Seite einfügen:
     
  13. <?php
    $loading = 0;
    $link = mysql_connect('localhost', 'db_user', 'db_pass');
    if (!$link) {die('Could not connect: ' . mysql_error());}
    mysql_select_db('ips', $link);
    
    $result = mysql_query("SELECT * FROM station", $link);
    
    if (!$result) {
        die('Invalid query: ' . mysql_error());
    }
    
    while ($row=mysql_fetch_array($result)){
        $loading = $row['loading'];
    }
    
    ?>
    Auch hier wieder db_user und db_pass entsprechend für die ips-Datenbank anpassen.

    Die tatsächliche Anzeige des Ladezustands erfolgt dann mit diesem Code:

    <?php
    if ($loading == 1) {
        echo '<div style="padding-top:33px; font-face:Arial; font-size:20px;font-weight:bold; color:green;">Station charging</div>';
        } else {
        echo '<div style="padding-top:33px; font-face:Arial; font-size:20px;font-weight:bold; color:green;">Station available</div>';
        }
    ?>
    

Und fertig ist unser "smart little project" :-)

Nachtrag 1:

Da die Anwendung Webserverbasierend ist und die Webseite auf dem Display sich alle 3 Sekunden refreshed, läuft mit der Zeit das Log-File des Apache Webservers über. Damit das nicht passiert in der Datei /etc/apache2/sites-available/default die Zeile "CustomLog ${APACHE_LOG_DIR}/access.log combined" mit der # auskommentieren zu "#CustomLog ${APACHE_LOG_DIR}/access.log combined". Dann noch in der /etc/apache2/apache2.conf den Eintrag "Errorlog" auf "ErrorLog /dev/null" ändern und die Log-Dateien werden nicht mehr erstellt.

Nachtrag 2:

Das Pi scheint doch ein Wenig belastet zu sein mit der Anwendung. Es kommt ca. einmal im Monat zu einem Überlaufen des Arbeitsspeichers. Da springt dann der "oom-killer" ein und der schaltet mir u.a den Browser ab. Damit fällt dann das Dsiplay aus ... Damit das Pi in dem Fall von sich aus neu startet, einfach in der Datei /etc/sysctl.conf die zwei Zeilen:

vm.panic_on_oom = 1
kernel.panic = 5

eintragen und wir haben einen schnellen Workaround.

Nachtrag 3:

Benjamin wollte gerne eine Möglichkeit haben, dass sich der Monitor beim Ladevorgang selbständig einschaltet und, nach einer Pufferzeit, beim Ende des Ladevorgangs wieder ausschatet. Dazu hat er diesen Code geschrieben. Zum Autostart eine Zeile "/usr/local/bin/emob/screen.py &" (ohne Anführungsstriche) in die /etc/rc.local einfügen.