ECMD

Aus FHEMWiki
Zur Navigation springen Zur Suche springen

ECMD bedeutet "Ethersex Command"[1] und ist laut FHEM-Dokumentation

Any physical device with request/response-like communication capabilities over a TCP connection[2],

das heisst, irgendein physisches Gerät, welches request/response-artige Kommunikationsfähigkeiten aufweist, sei es über eine TCP-Verbindung (Netzwerk) oder eine serielle Schnittstelle.

In FHEM sind enthalten:

  • Modul ECMD für die Bearbeitung physischer I/O-Schnittstellengeräte und
  • Modul ECMDDevice für einzelne logische Geräte, deren Kommunikation über ein ECMD-Gerät läuft

Beispiele & Links

Links

Siehe Thread im FHEM-Forum

Siehe Radioaktivitätsmessung mit DIYGeigerCounter

Beispiel DIY Sensor via HC-12

Ein BME280 Temperatur/Druck/Feuchte-Sensor liefert via eines HC-12 Pärchens seine Daten an der seriellen Schnittstelle eines RasPi ab. Der Sketch im Nano, an dem sowohl der BME280 als auch einer der HC-12 hängen, liefert alle 10 Minuten ein Datagramm ab und schickt seinen Wirt in den Tiefschlaf bis zum nächsten Datagramm, um mit den notwendigen Batterien über ein Jahr aushalten zu können.

Das Datagramm hat die Form

DevId T-3.45°C P995.31hPa H63.27%RH\r\n
Klassendefinition

Daraus werden die notwendigen Definitionen für eine Klassendefinition abgeleitet. Die z.B. unter '/opt/fhem/BME280.classdef' abgelegte Datei erhält den Inhalt

params devId
reading temperature match "%devId[^\n]+\n"
reading temperature postproc { /%devId[^T]+T([-+.,0-9]+).*/; $1 }
reading airpressure match "%devId[^\n]+\n"
reading airpressure postproc { /%devId[^P]+P([-+.,0-9]+).*/; $1 }
reading humidity match "%devId[^\n]+\n"
reading humidity postproc { /%devId[^H]+H([-+.,0-9]+).*/; $1 }
state humidity

Die regex für match ist in allen Zeilen gleich gestaltet. Alle Werte werden durch diese Vorgehensweise gleichzeitig und erst dann gelesen, wenn das gesamte Datagramm eingetroffen ist. Es ließe sich auch auf Teil-Strings abfragen, Konflikte bei mehreren Sensoren sind dann denkbar.

Die letzte Zeile sorgt dafür, dass der state nur dann einen neuen Wert erhält, wenn (in diesem Fall) 'humidity' ein Update erfährt. Bei den gewählten regex trift das hier eigentlich für alle zu, aber da humidity der letzte übertragene Wert ist, ist die Wahl auf ihn gefallen. Außerdem steht nun nur ein Wert im state ohne einleitenden reading Namen, der wäre hier im Beispiel erwartungsgemäß 'humidity', was hier insofern irritiert, weil ja 3 Werte gelesen wurden und man sich unwillkürlich fragt, wo den die anderen geblieben sind. Zum state daher später unter Notify noch weiteres.

Schnittstelle

Sollte bereits eine ECMD Schnittstelle bestehen, kann nun die Definition hinzugefügt werden. Z.B. die ECMD Schnittstelle namens 'SerDevDef' erhält eine zusätzliche Klasse 'BME280' mit den Definitionen aus der vorher generierten Datei, Beispiel siehe oben.

set SerDevDef classdef BME280 /opt/fhem/BME280.classdef

Ist noch keine ECMD Schnittstelle definiert, wird diese erstellt und gleich mit passenden Attributen versehen.

define SerDevDef ECMD serial /dev/ttyS0@9600
set SerDevDef classdef BME280 /opt/fhem/BME280.classdef
attr SerDevDef partial 2

Die letzte Zeile ist für die serielle Schnittstelle gerade bei längeren Datagrammen ein Muss. Sie sorgt dafür, dass die kleineren Informationsbröckchen zu einer vollständigen Einheit werden. Kommt das nächste Päckchen innerhalb von (hier) 2 Sekunden, wird das neue Päckchen an die bereits gelesenen Daten angehängt, ansonsten startet eine neue Aufzeichnung beginnend mit dem Päckchen.

Für den Start kann noch zur Erleichterung der Fehlersuche folgendes ergänzt werden

attr SerDevDef logTraffic 5
attr SerDevDef verbose 5

Rückstelllen dieser Attribute nicht vergessen, so ist's reichlich gesprächig und füllt schnell das Log !!!

Device

Damit ist alles vorbereitet den ECMD Device zu erstellen.

define BME280_42356 ECMDDevice BME280 42356
attr BME280_42356 alias Gartenhaus
attr BME280_42356 room Grundstück
attr BME280_42356 IODev SerDevDef 
attr BME280_42356 userReadings airpressure_sealevel:humidity:.* { sprintf("%.2f", ReadingsVal("BME280_42356","pressure",0)*1.023);; }

Der fragliche Sensor beginnt seine Datagramme immer mit '42356', das ist seine DevId, die über den Parameter 'devId' in der Klassendefinition (siehe oben) Eingang in die readings findet. Über 'BME280' wird die Klassendefinition festgelegt, über '42356' die devId. Sinnigerweise wird im Beispiel hier 'BME280_42356' als Devicename festgelegt, Systematik ist Trumpf.

Die letzte Zeile macht aus dem vom Sensor gemessenen Luftdruck noch den auf NormalNull Bezogenen. Nicht jeder wohnt direkt am Meer und kann auf diesen Schritt verzichten. Ansonsten weichen die Werte des Sensors immer von der Wettervorhersage ab, je höher der Sensor, um so markanter. Das Beispiel hier gilt für ca. 180m üNN. Die Erzeugung des Wertes für "airpressure_sealevel" ist übrigens an den Event "humidity geknüpft, sonst hat man im Event-Log in diesem Fall 3 Einträge, nämlich den beim Temperatur-, beim Druck- und beim Feuchte-Event (siehe auch beim folgenden Notify und dem linken Teil des Beispiel-Plots weiter unten).

Notify

Da der state nach einer Übermittlung aufgrund der Festlegungen aus der Klassendefinition nun nur den Wert der Luftfeuchte enthält, ist es hilfreich ein 'notify' für eine gefälligere Darstellung zu definieren.

define ECMDDevState notify BME280_42356:humidity:.* { Log 1, "$NAME has new values";;sleep 1;;fhem "setstate BME280_42356"." T:".ReadingsVal("BME280_42356","temperature",0)." P:".ReadingsVal("BME280_42356","airpressure_sealevel",0)." H:".ReadingsVal("BME280_42356","humidity",0) }

Für die Übersicht zerlegt:

1 define ECMDDevState notify BME280_42356:humidity:.* 
2 {
3   Log 1, "$NAME has new values";;
4   sleep 1;;
5   fhem "setstate BME280_42356".
6     " T:".ReadingsVal("BME280_42356","temperature",0).
7     " P:".ReadingsVal("BME280_42356","airpressure_sealevel",0).
8     " H:".ReadingsVal("BME280_42356","humidity",0)
9 }

① 'ECMDDevState' wird immer dann benachrichtigt und ausgeführt, wenn der Device 'BME280_42356' ein reading 'humidity' mit einem beliebigen (.*) Wert hat. ③ Dies wird zunächst im Log vermerkt und ④ als nächstes ein wenig gewartet, damit eine Änderung des state auf jeden Fall nach dem Befüllen des state durch das humidity reading stattfindet und auch der Luftdruck fertig berechnet ist. Dann wird ⑤ der state mit einer Verkettung der gerade gelesenen bzw. berechneten Daten aller Werte ⑥,⑦,⑧ versorgt.

Log HISTORY

Die Werte sammeln sich nun im Log. Dort haben die Werte ebenfalls eine Einheit, in diesem Fall "°C" für reading-Namen, die mit "temperature" beginnen und "%" für reading-Namen, die mit "humidity" beginnen. Andere Einheiten oder andere reading-Namen lassen sich entweder über ein Attribut in der Log-Definition

attr myDbLog valueFn {
  if ($DEVICE eq "BME280_42356"){
    if ($READING=~ m(^airpressure)){
      $UNIT = "hPa";
    }
    if ($READING=~ m(^pressure)){
      $UNIT = "Pa";
    }
  }
}

oder direkt im Device ergänzen bzw. beeinflussen.

attr BME280_42356 DbLogValueFn {
  if ($READING=~ m(^airpressure)){
    $UNIT = "hPa";
  }
  if ($READING=~ m(^pressure)){
    $UNIT = "Pa";
  }
}

Damit lassen sich freilich auch ungewöhnliche Einheiten festlegen, Blutdruck wird z.B. in "mmHg" gemessen.

Weiterführend

Abschließend soll noch ein Plot der Messwerte gemacht werden, eine Kurve liest sich leichter. Als Erstes wird eine Datei z.B. '/opt/fhem/www/gplot/SVG_BME280_42356.gplot' mit folgendem Inhalt erstellt, der anzuzeigenden Linien und Beschriftungen des Plots beschreibt.

set terminal png transparent size <SIZE> crop
set output '<OUT>.png'
set xdata time
set timefmt "%Y-%m-%d_%H:%M:%S"
set xlabel "Verlauf"
set title 'Wetter'
set ylabel "Temperatur"
set y2label "Luftdruck"
#myDbLog BME280_42356:temperature:0:
#myDbLog BME280_42356:airpressure_sealevel:0::$val=$val>1100?1100:$val<800?800:$val
#myDbLog BME280_42356:humidity:0::$val=$val/2-10
plot "<IN>" using 1:2 axes x1y1 title 'Temperatur' ls l0 lw 1 with lines,\
     "<IN>" using 1:2 axes x1y2 title 'Luftdruck' ls l1 lw 1 with lines,\
     "<IN>" using 1:2 axes x1y1 title 'Feuchte' ls l2 lw 1 with lines

Dabei wird der Luftdruck auf einen plausiblen Bereich eingegrenzt, Drücke außerhalb dieses Bereichs hat es noch nicht gegeben auf diesem Erdball (:$val=$val>1100?1100:$val<800?800:$val). Der Wert der Feuchte wird dem zu erwartenden Temperaturbereich (-10 bis 40) angeglichen, das ergibt eine schönere Kurve (:$val=$val/2-10). Eine Y-Achse für die Feuchte wäre ja die dritte Y-Achse, die haben wir also nicht. Also landet 100% Feuchte bei 40°C und 0% Feuchte bei -10°C.

Dann wird der Plot definiert. In diesem Fall mit den Daten aus DbLog, für FileLog muss geringfügig angepaßt werden. Dann fehlten freilich auch die FileLog -Definitionen für 'BME280_42356'.

defmod SVG_BME280_42356 SVG myDbLog:SVG_BME280_42356:HISTORY

Der Term 'SVG_BME280_42356' liest dabei die Definitionen aus der vorher angelegten Datei 'SVG_BME280_42356.gplot'.

GHplotECMD.png

FIN

Viel Spaß bei den eigenen Experimenten.

Quellen