Gaszähler BK-G4 an IP-Symcon anbinden

Veröffentlicht von Daniel am

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:


1 Kommentar

Wolfram · 26. Januar 2017 um 23:56

Hallo Daniel,

die Loesung fuer den Gaszaehler ist genau das was ich gesucht habe. Ich werde es die Tage versuche umzusetzen wenn alle Teile eingetroffen sind. Somit vielen Dank fuer die detailierten Infos in deinem Blog.

Wenn ich mir die Bilder genauer anschaue, vermute ich das du den Stromzaehler am selben Raspi haengen hast oder? Hast du dazu auch ein Script welches du verwendest um IP Symcon zu fuettern? Ideal waere es ja wenn der eine Raspi Gas sowie Strom abgreiftund weiterreicht. Solltest du hier eine Loesung parat haben waere ich sehr daran interessiert.

Viele Gruesse,
Wolfram

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.