Gaszähler BK-G4 an IP-Symcon anbinden

Wie schon im letzten Post zum Thema Hausautomatisierung angekündigt folgt nun der erste Basteltip. Nachdem ich bereits unseren intelligenten Stromzähler erfolgreich an IP-Symcon angebunden habe (Basteltip folgt) wollte ich mehr. Mehr Daten, mehr Transparenz. Wichtig zu erfassen sind meiner Meinung nach primär folgende drei Messwerte:

  • Energieverbrauch / Energie Einspeisung
  • Gasverbrauch
  • Wasserverbrauch

Entsprechend war der nächste Punkt klar, ich möchte meinen Gaszähler überwachen. Ich hatte zunächst angenommen, dass dies eine Aufgabe sein wird die durchaus komplizierter sein könnte. Doch ich hatte Glück, und unser Gaszähler vom Typ BK-G4 bietet im unteren Bereich des Zählers auf der rechten Seite eine Einbuchtung. An dieser Einbuchtung kann man einen Reed-Kontakt (Magnetschalter) platzieren. Charakterisierend für einen einfachen Reed-Kontakt ist es, dass dieser einen Kreislauf schließt sobald etwas Magnetisches in seine Nähe kommt. Bei dem Gaszähler ist das der Fall wenn sich die letzte Stelle des Zählers von 9 auf 0 ändert, also 0,01 cbm verbraucht wurde, weil genau an dieser Stelle des Segmentes ein Magnet innerhalb des Zählers eingebaut wurde, der genau für solche Einsatzzwecke gedacht ist. So ist es recht einfach Möglich den Verbrauch exakt zu speichern und später auszuwerten.

Was braucht man also ? Eine kleine Einkaufsliste:

Ihr fragt euch bestimmt, warum ein Raspberry Pi benötigt wird ? Nun der Gaszähler hat ja einen bestimmten Zählerstand (in der Regel wird dieser bei den wenigsten Lesern 0 betragen) und der Reed-Kontakt meldet uns nur einen Wechsel von 0 auf 1 (Kreislauf offen / geschlossen, low / high) nicht aber den Gesamtzählerstand welchen wir u.a. aber haben möchten. Um diese Daten im IP-Symcon halten zu können benötigen wir eine Schnittstelle, diese Schnittstelle stellt aus Hardware Sicht der Raspberry Pi dar.
Auf ihm läuft ein Bash Skript welches permanent auf den GPIO Pins lauscht und mitbekommt wenn der Kreislauf auf zwei vorher definierten Pins von low auf high wechselt und somit einen Stromkreis schließt. Wenn das der Fall ist, wird ein JSON Request an den IPSymcon Server abgesendet, diese Schnittstelle werden wir im weiteren Verlauf noch einrichten.

Das Skript für den Raspberry Pi sieht folgendermaßen aus:

[bash highlight=“4″]#!/bin/bash

# URL zur Ipsymcon JSON Schnittstelle
IPSURL=“http://<ipsymcon-server-ip-adresse>:9998″
# Identifier auf welchen dann die Ipsymvon JSON Schnittstelle reagiert
IDENTIFIER=“GAS_BKG4″
# GPIO Input Pin (auf welchen reagiert wird)
GPIO_PIN_IN=17
# Impulseinheit in Double (z.B. 1 Impuls entspricht 10.0 Einheiten oder 0.1 Einheiten)
UNIT=“0.01″
# DO NOT CHANGE
SCRIPTNAME=basename "$0"

if [[ ps aux | grep $SCRIPTNAME | wc -l -gt 4 ]]; then
exit 0
fi

if [[ ls -la /sys/class/gpio | grep gpio$GPIO_PIN_IN | wc -l -eq 0 ]]; then
echo „$GPIO_PIN_IN“ > /sys/class/gpio/export
sleep 0.5
echo „in“ > /sys/class/gpio/gpio$GPIO_PIN_IN/direction
sleep 0.5
fi
CURVAL=0
while true; do
PREVVAL=$CURVAL
CURVAL=cat /sys/class/gpio/gpio$GPIO_PIN_IN/value
if [[ $CURVAL -gt $PREVVAL ]]; then
JSON=“{\“Name\“:\““$IDENTIFIER“\“, \“Timestamp\“:\“date +%s\“, \“Values\“: [“
JSON=$JSON“{ \“Name\“:\“impulse\“, \“Value\“:\“1\“, \“Unit\“:\““$UNIT“\“}“
JSON=$JSON“] }“

curl -0 -i -X POST -d „json=$JSON“ $IPSURL &> /dev/null
echo $JSON
fi
sleep 0.1
done[/bash]

Das Skript setzt automatisch den definierten Eingangspin, sodass nichts weiter getan werden muss. Das Skript könnt ihr einfach jede Minute durch den Cron ausführen lassen:

[bash]# Gaszähler auslesen
*/1 * * * * root /opt/datacollector_bkg4.sh &> /dev/null[/bash]

Das Skript selbst gestattet nur eine Ausführung von sich selbst, somit ist der Aufruf über den Cron problemlos möglich.

Nun zur Verkabelung. Pin 1 dient hierbei als Stromversorgung mit 3V Ausgangsspannung, Pin 17 ist der Eingang wo bei Schließung des Stromkreises (wenn der Reed-Kontakt schließt) wieder die 3V Ausgangsspannung anliegen und somit ein Impuls erzeugt wird:

HM-GAS11

Pin 1 = 3V, Pin 17 = Eingang

Ein weiterer wichtiger Bestandteil der Lösung ist auf IP-Symcon Seite die vorhin angesprochene JSON Schnittstelle. JSON ist eine definierte Notation um Informationen zwischen verschiedensten Systemen auszutauschen. Es handelt sich hierbei konkret um ein PHP Skript welches mittels einer Register Variablen an einen Server Socket gebunden wird.

Doch zunächst erstellen wir das PHP Skript welches die eigentliche JSON Schnittstelle enthält und den JSON Request vom Raspberry Pi versteht und auch verarbeitet. Der Inhalt dieses Skriptes ist folgender:

[php collapse=“true“ highlight=“28″]
<?
// JSON Schnittstelle
// Schnittstellentyp: POST
// Schnittstelle nimmt POST Daten im JSON Format entgegen und speichert diese nach Definition in Variablen ab.
// Die Schnittstelle legt außerdem benötigte Variablen automatisch an.
//
// von: Daniel Schäfer (@gotteshand)
// Version: 1.1

if ($_IPS[‚SENDER‘] == „RegisterVariable“) {
$Handler = new IPSServerDataHandler($_IPS[‚INSTANCE‘], $_IPS[‚VALUE‘]);

$ok = $Handler->CheckData(function ($event) {
$data = ‚-1′;
$content = $event[‚content‘];

$result = null;
switch ($event[‚method‘]) {
case ‚GET':
break;
case ‚POST':
if (isset($content[‚json‘])) {
$json = json_decode($content[‚json‘]);

// Gaszähler
if (strtolower($json->Name) == „gas_bkg4″) {
foreach ($json->Values as $value) {
$variable_id = 4711; // !!!!!!!!!! ID ANPASSSEN !!!!!!!!!!

$ipsvalue = GetValue($variable_id);
$ipsvalue += ($value->Value*$value->Unit);
SetValue($variable_id, $ipsvalue);
}
}
}

break;
case ‚NOTIFY':
break;
case ‚CALL':
break;
}
return $data;
});
}

class IPSServerDataHandler
{
private $INSTANCE_ID = 0;
private $CALLBACKURL = “;
private $LAST_EVENT = null;
private $headLines = [];

function __construct($InstanceId, $value)
{
$this->INSTANCE_ID = $InstanceId;
$this->DATA = RegVar_GetBuffer($InstanceId).$value;
$this->DEBUG = $this->__read(‚DEBUG‘, 0);
if ($this->DEBUG) Debug(‚GET :‘.$this->DATA);
}

function CheckData($CallBack = null)
{
while ($ev = $this->__SplittEventData()) {
$sendData = ($CallBack) ? $CallBack($ev) : array(‚status‘ => ‚200 OK‘, ‚content‘ => “);
$this->__SendHttpResponse($sendData);
$this->LAST_EVENT = $ev;
}
$this->__write(‚LAST_REQUEST‘, „{$this->LAST_EVENT[‚method‘]}: „.ArrayToStr($this->LAST_EVENT[‚content‘]));
$this->__write(‚LAST_RESPONSE‘, ArrayToStr($sendData, 0, “));
RegVar_SetBuffer($this->INSTANCE_ID, $this->DATA);
return ($CallBack) ? (bool)$this->LAST_EVENT : $this->LAST_EVENT;
}

public function LastEvent()
{
return $this->LAST_EVENT;
}

private function __SplittEventData()
{
$methods = array(‚GET‘, ‚POST‘, ‚NOTIFY‘);
$content = $head = [];
$event = false;
$method = $mdata = “;
$dpos = $contlen = 0;
$lines = explode(„\n“, $this->DATA);
foreach ($lines as $lnum => $line) {
$dpos += strlen($line) + 1;
$line = trim($line);
if ($line) {
if (!$method) { // wenn noch keine methode gefunden wurde
foreach ($methods as $m) { // Zeile auf methoden prüfen
$find = preg_match(‚/‘.$m.‘ \/(.+)HTTP\//‘, $line, $matches);
if ($find) {
$method = $m;
$mdata = $matches[1];
break;
}
}
if ($method) {
$this->headLines = [];
continue;
}
}
$this->headLines[] = $line;
$m = explode(‚:‘, $line);
if (isSet($m[1])) {
$m[0] = strtoupper(trim($m[0]));
$head[$m[0]] = trim($m[1]);
if ($m[0] == ‚CONTENT-LENGTH‘) $contlen = (int)$m[1];
if ($m[0] == ‚CALLBACK‘) $this->CALLBACKURL = $m[1];
}
} else {
if ($contlen > 0) {
$content = substr($this->DATA, $dpos, $contlen);
$this->DATA = trim(substr($this->DATA, $dpos + $contlen));
} else {
$this->DATA = trim(substr($this->DATA, $dpos));
$content = $mdata;
}
break;
}
}
if ($method) {
$event[‚method‘] = $method;
$event[‚head‘] = $head;
$event[‚content‘] = ($content) ? $content : $mdata;
}
if ($event) $this->__prepareReadingData($event);
return $event;
}

// private function __prepareReadingData(&$Event){
public function __prepareReadingData(&$Event)
{
$data =& $Event[‚content‘];
$pa = null;
if (!$data || ($Event[‚method‘] == ‚NOTIFY‘)) return true;
if (($data[0] == ‚?‘) || ($data[0] == ‚&‘)) {
$pa = explode(‚&‘, substr($data, 1));
} else if ($Event[‚method‘] == ‚POST‘) {
$pa = explode(‚&‘, $data);
} else if ($Event[‚method‘] == ‚GET‘) {
if (preg_match(‚/(.*)\((.*)\)/‘, $data, $matches)) {
array_shift($matches);
$Event[‚method‘] = ‚CALL';
$da[‚name‘] = array_shift($matches);
$aa = array_shift($matches);
if ($aa != “) {
$da[‚arguments‘] = explode(‚,‘, $aa);
foreach ($da[‚arguments‘] as $k => $v) {
if (is_bool($v) === true)
$v = (bool)$v;
else if (is_numeric($v) === true) {
if (is_double($v) === true) $v = (double)$v;
else $v = (integer)$v;
}
$da[‚arguments‘][$k] = $v;
}
} else $da[‚arguments‘] = [];
$data = $da;
}
}
if (!$pa) return true;
$data = null;
foreach ($pa as $pl) {
$da = explode(‚=‘, $pl);
if (isSet($da[1])) {
$dn = array_shift($da);
if (strtoupper($dn) == ‚CALLBACK‘) {
$this->CALLBACKURL = implode(‚=‘, $da);
} else {
$data[$dn] = implode(‚=‘, $da);
}
}
}
return true;
}

private function __SendHttpResponse($Data = null)
{
$id = @IPS_GetInstance($this->INSTANCE_ID)[‚ConnectionID‘];
if (!$id) return false;
if (!$Data) $Data = array(‚status‘ => ‚200 OK‘);
if (!is_array($Data)) $Data = array(‚content‘ => $Data);
if (!isSet($Data[‚status‘])) $Data[‚status‘] = ‚200 OK';
if (!isSet($Data[‚content‘])) $Data[‚content‘] = “;
$res[] = „HTTP/1.1 {$Data[‚status‘]}“;
if ($Data[‚content‘]) {
if (!isSet($Data[‚size‘])) $Data[‚size‘] = strlen($Data[‚content‘]);
if (!isSet($Data[‚type‘])) $Data[‚type‘] = ‚text/plain';
$res[] = „CONTENT-TYPE: {$Data[‚type‘]}“;
$res[] = „CONTENT-LENGTH: {$Data[‚size‘]}“;
$res[] = „“;
$res[] = $Data[‚content‘];
}
$content = implode(„\r\n“, $res).PHP_EOL;
$this->__write(‚LAST_STATE‘, $Data[‚status‘]);
if ($this->DEBUG) Debug(‚SEND: ‚.$content);
return SSCK_SendText($id, $content);
}

private function __write($VarIdent, $VarData, $VarType = 3)
{
$id = @IPS_GetObjectIdByIdent($VarIdent, $this->INSTANCE_ID);
if (!$id) {
$id = IPS_CreateVariable($VarType);
IPS_SetParent($id, $this->INSTANCE_ID);
IPS_SetIdent($id, $VarIdent);
IPS_SetName($id, $VarIdent);
}
SetValue($id, $VarData);
}

private function __read($VarIdent, $VarType = 3)
{
$id = @IPS_GetObjectIdByIdent($VarIdent, $this->INSTANCE_ID);
if (!$id) {
$id = IPS_CreateVariable($VarType);
IPS_SetParent($id, $this->INSTANCE_ID);
IPS_SetIdent($id, $VarIdent);
IPS_SetName($id, $VarIdent);
}
return GetValue($id);
}

}

function ArrayToStr($a, $step = 0, $cr = „\n“)
{
if (!$a) return “; else if (!is_array($a)) return $a;
$r = “;
$sp = str_repeat(‚ ‚, $step);
foreach ($a as $k => $v) $r .= „$sp’$k’=“.(is_array($v) ? „[$cr{$sp}“.ArrayToStr($v, $step + 2).“$sp$sp]“ : „$v“).“$cr“;
return $r;
}

?>
[/php]

Der JSON Request enhält neben einigen Rumpfdaten immer auch einen Wert und dessen Multiplikator. Der Wert ist immer 1, der Multiplikator kann im Bash Skript angepasst werden – je nachdem an welcher Stelle der Reed-Kontakt ausgelöst wird.
Die Schnittstelle Multipliziert zunächst den Wert 1 mit dem Multiplikator und addiert dies dann zum eigentlichen Zählerstand hinzu. Der Zählerstand wird in einer Variablen gespeichert, welche manuell angelegt werden muss (als Typ Float). Dessen Variablen ID muss noch in Zeile 28 eingesetzt werden. Möchte man seinen aktuellen Zählerstand als Basiswert festlegen, ändert man einfach den Wert dieser Variablen ab.

Anschließend wollen wir einen neuen ‚Server Socket‘ unter dem Knoten IO-Instanzen erstellen (Insofern nicht schon vorhanden) und geben ihr den Namen ‚JSON Socket Listener':

2015-07-05 22_53_29-Instanz hinzufügen

Außerdem brauchen wir noch eine Register Variable welche wir unter dem PHP Skript anlegen welches wir vorhin erstellt und mit Leben gefüllt haben:

2015-07-05 22_54_27-Instanz hinzufügen

Als letztes müssen wir nur noch den Server Socket mit dem PHP Skript verbinden. Als Bindeglied dient uns die Register Variable. Hierzu öffnen wir die Eigenschaften der Register Variable und legen im folgenden Dialog im oberen Bereich die JSON Schnittstelle (PHP Skript) und im unteren Bereich den Server Socket fest:

2015-07-05 22_56_18-IP-Symcon Verwaltungskonsole

Nun haben wir Skript und Socket über die Register Variable miteinander verheiratet.
Zum Abschluss müssen wir die Schnittstelle noch aktivieren. Das tun wir indem wir die Eigenschaften vom Server Socket aufrufen:

2015-07-05 23_04_00-IP-Symcon Verwaltungskonsole

Eventuell müsst ihr den Port 9998 in eurer Umgebung anpassen und ggfs. auch in der Windows Firewall des Servers freigeben.

Zum Schluss noch ein paar Bilder die während der Installation geknipst wurden. Bei mir sitzt der Raspberry Pi direkt im Zählerschrank. Dorthin wurde auch über bestehende Leerrohre der Klingeldraht gezogen:

Wechsel von FHEM zu IP-Symcon

Lange ist der letzte Beitrag zum Thema Haussteuerung her, unterdessen hat das von mir entwickelte FHEM Frontend großen Anklang gefunden. Damit einhergehend stiegen natürlich auch die Ansprüche seitens der Community. Damit erhöhte sich der damit verbundene Aufwand meinerseits für die Entwicklung und die Tests, sodass ich mich gerne an die Community mit der Bitte um Unterstützung gewand habe. Leider war quasi niemand bereit sich an dem Projekt zu beteiligen, so dass ich mich am Ende dazu entschieden habe die Entwicklung am Frontend komplett einzustellen. Der Sourcecode liegt unterdessen auf Github und wartet darauf, dass sich jemand dessen annimmt. Eventuell Forke ich das Projekt auch und binde den Socket an die neue Automatisierung an – mal sehen.

Was kommt als nächstes ?
Die Frage habe ich mir selbst oft gestellt, habe viele Lösungen zur Hausautomatisierung unter die Lupe genommen. In der Endauswahl standen für mich Loxone oder IPSymcon.

Die Basisversion von IP-Symcon kostet 99,99 Euro dem entgegen stehen 493,85 Euro für einen Loxone Homeserver. Außerdem bietet IPSymcon an, dass System mittels der Programmiersprache PHP zu erweitern. Loxone verfolgt hier ein ganz anderes Konzept, welches sicher auch sehr interessant ist für Personen die sich eher in der Elektronik als in der Programmierung zuhause fühlen. Loxone Systeme werden mittels eines Logik Baukastensystems programmiert, d.h. man zieht sich innerhalb einer GUI beispielsweise UND, ODER Gatter oder Zählbausteine zur Hilfe und verbindet diese zu einer Gesamtschaltung die dann gewisse Dinge tun kann.

Ich habe zwar gelernt mittels Logikbausteinen Schaltungen aufzubauen, jedoch bin ich aus dem Thema seit über 18 Jahren raus und bewege mich deutlich sicherer auf der Seite der Programmiersprachen. Aus diesem Grund, und natürlich auch von den Anschaffungskosten, habe ich mich für IP-Symcon als Hausautomatisierungslösung unserer Wahl entschieden.

IP-Symcon ist nun seit gut einem halben Jahr bei uns im Einsatz und wird stetig erweitert und gewinnt zu meiner Freude immer mehr Akzeptanz bei meiner Frau die langsam aber sicher die Sinnhaftigkeit einer solchen Lösung erkennt :)

Klar ist auch, dass eine „fertige“ Lösung wie IP-Symcon weniger anpassungsfähig als ein selbst gebautes Frontend ist, aber es gibt die Möglichkeit das Frontend mittels einem eigenen Skin und CSS Techniken zu verschönern. Wem das nicht ausreicht, gibt es eine Software namens IPSView welche an IPSymcon andockt und mittels einem WYSIWYG Editor ganz schicke Gestaltungsmöglichkeiten für die Oberfläche bereitstellen. IPSView ist optional, allerdings habe ich diese kurz nach der Anschaffung gekauft und damit etwas herum experimentiert. Ich bin noch unsicher, ob ich lieber das mitgelieferte Webfrontend oder IPSView auf den Terminals im Haus einsetzen möchte.

Leider bietet IP-Symcon in der Basic Version nur die Möglichkeit ein Webfrontend zu haben, oftmals ist es ja so, dass man auf den unterschiedlichen Terminals ein auf der jeweiligen Auflösung angepasstes Frontend anzeigen möchte. Hier bietet IPSymcon eine Professional Version hat die dies neben einigen anderen coolen Features ermöglicht. In der Professional Version wird die maximale Variablenanzahl von 200 auf 1000 angehoben, was auch sehr praktisch ist. Leider ist die Anschaffung von 249,99 Euro nicht gerade günstig, ein Update von Basic auf Professional schlägt mit 179,99 Euro zu Buche.

Auf Kurz oder Lang war klar, die Professional muss her. Also erledigte ich das Upgrade kürzlich und bin nun auch mit den Webfrontend recht happy. Warum ? Nun in der Professional Version kommt auch ein Editor mit, mit dessen man nun auch die Seiten in Bereiche einteilen kann und so die Informationen übersichtlicher präsentieren kann.

Die Aktoranbindung (aufmerksame Leser wissen ja dass ich bisher nur Aktoren von Homematic einsetze) ging ebenfalls absolut unproblematisch über die Bühne. Es verrichtet bisher alles ohne großes Tamm Tamm seine Dienste sehr zuverlässig.

Aus diesem Grund wird es künftig wieder mehr zum Thema Hausautomatisierung von mir an dieser Stelle geben, nur eben nicht mehr mit dem Fokus auf FHEM sondern auf IP-Symcon.

Auch den einen oder anderen Basteltip möchte ich in naher Zukunft für euch bereitstellen, denn nicht immer muss man teure Sensoren oder Aktoren von der Stange kaufen. Nicht selten gibt es auch keine Komplettlösung am Markt, wo man am Ende einfach nicht drum herum kommt selbst etwas zu basteln.

Ihr dürft also gespannt sein!