Posts mit dem Label fhem werden angezeigt. Alle Posts anzeigen
Posts mit dem Label fhem werden angezeigt. Alle Posts anzeigen

Freitag, 19. August 2022

HowTo - FHEM Umzug von System A nach System B

Scenario: Umzug auf neues System

  • System Alt: altes Linux, alte Hardware ...
  • System Neu: neues Linux, neue/andere Hardware Plattform ...

Zwei verschiedenen Hardware Systeme sind etwas einfacher zu handhaben, es geht aber auch auf der gleichen Hardware (z.B. Raspberry Pi mit SD Card Wechsel oder USB-Bootstick). Ich habe alle Schritte mit einem Raspberry Pi(Raspberry OS Lite) und einer Virtuellen Maschine(debian 11 netinst) getestet. Dieses HowTo ist auch eine Ergänzung zu meinem vorjährigen Artikel.

Mit dem Raspberry Pi Imager kann man eine neue SD Card effektiv vorbereiten und das System grundlegen konfigurieren (Zahnradsymbol am rechten unteren Rand nachdem man das OS ausgewählt hat) 

Es gab von mir schon ein paar ähnliche Artikel, dieser hier soll ganz gezielt Schritt für Schritt zeigen was zu tun ist. Windows 10 hat ssh und scp eingebaut, andere Tools braucht man nicht. Wer nicht mit Windows CMD arbeiten will findet die Codezeilen für Schritt 1 am Ende auch für Linux und Powershell.

Man kann alle Schritte beliebig oft wiederholen / durchspielen und dabei lernen.

Achtung (03.2024): Das FHEM Installermodul wird leider nicht mehr gepflegt, es häufen sich die Meldungen, das mein hier gezeigtes Verfahren der Ermittlung der fehlenden Debianpakete nicht mehr funktioniert. Ich habe derzeit keine Problem Behebung!

Dienstag, 30. November 2021

selbstsignierte Zertikate

Will man im eigenen Netzwerk HTTPS/SSL fehlerfrei verwenden, braucht man ein Zertikat was von den Endgeräten akzeptiert wird. Das Ergebnis meiner Experimente mit FHEM habe ich hier mal aufgeschrieben.

Es gibt zwei Ansätze in FHEM:

  1. Im FHEMWEB Device einfach attr <name> HTTPS 1 setzen
  2. FHEM Wiki SSL-Zertikate mit eigener ca erstellen.

Beide Wege führen nur bedingt und bei entsprechender Kenntnis zum Erfolg.

Mittwoch, 18. November 2020

Schreibe einen Patch

da ich jedes mal wieder überlegen und basteln muss - hier ein kurzes Howto - aufbauend auf das HowTo im Wiki.

Am einfachsten nimmt man sich vielleicht irgendeine virtuelle Maschine mit debian drauf. 

Man benötigt dafür keinen SVN Account!

Was man braucht ist nur ein Tool:

sudo apt update
sudo apt install subversion

Dann erstellt man eine Arbeitskopie im aktuellen User Home Directory.

svn checkout https://svn.fhem.de/fhem/trunk/fhem ~/fhem-code

Jetzt macht man seine gewünschte Änderung, am einfachsten mit nano (hier gibt es ein paar nützliche Tastenkürzel) Zum arbeiten macht es sich einfacher, wenn man im aktuellen "Working-Copy" Directory steht! Nach getaner Änderung lässt man sich den Patch anzeigen:

cd ~/fhem-code
svn diff

bzw. erstellt man gleich eine patch Datei in seinem Homeverzeichnis mit einem sprechenden Namen.

svn diff > ~/cref_motd.patch

Will man den Original Zustand der gesamten Arbeitskopie wiederherstellen:

svn revert --recursive .

Man kann die aktuellen Dateien aus dem SVN holen, treten Konflikte auf, kann man diese mit tf überschreiben.

svn update

Ein erneutes checkout holt zwar Änderungen vom Server, überschreibt aber lokale Änderungen nur wenn neue Versionen auf dem Server vorliegen!

Den eigene Patch wieder anwenden und testen:

svn patch ~/cref_motd.patch

Ist man fertig und kann von der Maschine nicht veröffentlichen, kopiert man sich das schnell auf den Arbeits PC:

scp ~/cref_motd.patch user@host:

Code Einchecken

Wie es geht steht hier im Wiki. Die Einrichtung der Umgebung steht auf der Webseite. Ich brauche nur trunk/fhem .
cd ~/fhem-ssh
svn update

Man kann den Code komfortabel mit notepad++ und dem NppFTP Plugin von Windows aus über ssh auf einer linux Umgebung editieren. 

Hat man die Änderungen fertig und getestet, erfolgt nochmal eine Überprüfung auf Konflikte mit eventuellen aktuellen Änderungen und dann commit. 

svn diff
svn commit -m "Dateiname:Beschreibung"

Da man dies selten tut, kann man die alten Befehle aus der Versenkung holen: 

history|grep 'svn'
grep "svn co " ~/.bash_history

Man kann sich vom aktuellen Pfad oder von einem anderem Pfad auch den Stand anzeigen lassen:

svn info
svn info ~/fhem-neu

...

Mittwoch, 9. September 2020

ssh mit public key

ich habe dazu schon mehrfach was geschrieben - aber man lernt ja immer wieder dazu. Deshalb noch ein ziemlich komprimierter Artikel: 

Ein HowTo für die Automatisierung von Remote Aufgaben in FHEM über ssh. 

Nachtrag 2023: Es gibt einen neuen Artikel, der die Einrichtung ganz kompakt mit einem Script zeigt.

Sonntag, 7. Juni 2020

Worx Landroid per MQTT

Vor einem Jahr habe ich den neuen Mähroboter an FHEM angebunden. Nach einiger Zeit wurde mir irgendwie klar: Die Cloud Anbindung des Roboters läuft doch mit MQTT!?
Die Anbindung über zwei nodejs Services und ein FHEM Modul erschien mir ziemlich aufwendig:
 Cloud-MQTT - iobroker.worx - fhem-landroid-s - 74_LANDROID.pm
Ein Beitrag im Forum gab die entscheidenden Hinweise. Ich habe das ganze noch etwas weiter geführt und mich dabei auch mal etwas eingehender mit attrTemplate beschäftigt.

Vorbereitung

Zur Vorbereitung der Installation braucht man ein Tool um die Informationen für den Zugang zum Cloudserver zu bekommen.
  • Broker Server Adresse
  • Board ID z.B. bei den 2019 Modellen PRM100
  • Mac zwölf stellig
  • SSL Zertifikat AWS.p12
  • Eine neue Uuid -> kann einfach selbst erzeugt werden: 
    • cat /proc/sys/kernel/random/uuid 
    • oder im Browser hier .
Ich habe die Version V0.0.24 - Net.zip verwendet. Das Tool erfordert keine Installation. Man trägt einfach seinen Cloudzugang ein, nach der Verbindung zeigt das Tool die notwendigen Daten und das Zertifikat liegt im gleichen Pfad.
Das Zertifikat muss noch zerlegt werden, um es in FHEM mit MQTT2_CLIENT verwenden zu können.

Einrichtung

Mit scp kopiert man die Datei AWS.p12 auf den FHEM Server: 
scp "d:\Downloads\V0.0.24 - Net\AWS.p12" pi@raspib3:

Ab hier sind die Befehle für die FHEM Kommandozeile oder die Raw Definition aufbereitet!

Das Zertifikat muss zerlegt werden damit fhem die Dateien verwenden kann.
"openssl pkcs12 -in /home/pi/AWS.p12 -nokeys -passin pass: -out aws.cer -clcerts"
"openssl pkcs12 -in /home/pi/AWS.p12 -nodes -passin pass: -out aws.key -nocerts"
"chmod 0600 aws.*"
Die Template Datei kann man jederzeit aktualisieren ohne ein komplettes Update machen zu müssen. Nichtsdestotrotz muss FHEM halbwegs aktuell sein und attrTemplate funktioniert.
{ Svn_GetFile("FHEM/lib/AttrTemplate/mqtt2.template", "FHEM/lib/AttrTemplate/mqtt2.template", sub(){ AttrTemplate_Initialize() }) }
Jetzt in FHEM den Connector zum Broker herstellen und testen.
define MQTT_Worx MQTT2_CLIENT <broker-server-adresse>:8883
attr MQTT_Worx SSL 1
attr MQTT_Worx autocreate simple
{my $uuid=qx(cat /proc/sys/kernel/random/uuid);;fhem("attr MQTT_Worx clientId android-$uuid")}
attr MQTT_Worx keepaliveTimeout 600
attr MQTT_Worx mqttVersion 3.1.1
attr MQTT_Worx msgAfterConnect <BoardID>/<MAC>/commandIn {}
attr MQTT_Worx sslargs SSL_version:TLSv12 SSL_cert_file:aws.cer SSL_key_file:aws.key SSL_use_cert:1
attr MQTT_Worx subscriptions <BoardID>/<MAC>/commandOut
Hat man alles richtig gemacht, geht der state des Devices MQTT_Worx sofort auf opened.

Wechselt der Status hektisch zwischen opened und disconnected stimmt etwas mit dem Zertifikat nicht.

Die erste Übertragung sollte automatisch ein neues MQTT_DEVICE erstellen.

Auf dieses Device wird jetzt das Template angewendet und damit konfiguriert.
set MQTT2_.*:FILTER=IODev=MQTT_Worx attrTemplate worx_landroid
Die Readings und die Steuerung sind ähnlich das Moduls 74_LANDROID.pm
Problemlösung
Bleibt der Connector MQTT_Worx auf disconnected -> die Zertifikate prüfen:
{qx(ls -lha /opt/fhem/aws.*)}

Das Ergebnis sollte in etwa so aussehen:
-rw------- 1 fhem dialout 1.4K Jun  9 13:27 /opt/fhem/aws.cer
-rw------- 1 fhem dialout 1.8K Jun  9 13:27 /opt/fhem/aws.key/

Support

Es gibt dazu auch einen Beitrag im Forum.

Sonntag, 31. Mai 2020

sonos2mqtt - so weit bin ich


Ich habe derzeit die aktuellste Anleitung ins FHEM Wiki geschrieben, aufgeteilt in 2 Artikel.

sonos2mqtt ist (ein 2 Jahre altes?) Projekt welches aktiv entwickelt wird, offenbar primär für die Anbindung an Homeassistent. Aber von der Sache her ist es eine Bridge Sonos - MQTT.
Ich habe die "lokale Installation" gewählt (es gibt auch eine Docker Version).
Aus meiner Sicht ist die Sache unkritisch, man kann es in jede Umgebung einbinden.

Voraussetzungen für diese Anleitung

  • Nodejs und pm2 installiert - Anleitung
  • MQTT2_SERVER bereits in FHEM definiert - Anleitung.
    • Damit die Automatik funktioniert: autocreate simple (default) 
  • FHEM aktuell - Stand mindestens 28.7.2020
  • Alles läuft auf einer Maschine (localhost)

Setup auf Systemebene

Diese Abschnitt wird auf der Kommandoebene von Linux ausgeführt (Terminal).
Man installiert die aktuelle sonos2mqtt Release Version (siehe Projekt Webseite):
sudo npm install -g sonos2mqtt
Es wird derzeit heftig entwickelt, die Installation oder Aktualisierung auf eine neue Beta Version geht mit dem gleichen Befehl und der Zusatz Angabe zur Beta, Beispiel "sonos2mqtt@3.0.6-beta.3".

Man kann kurz überprüfen ob bisher alles gut gegangen ist und die Version abfragen:
node /usr/lib/node_modules/sonos2mqtt --version

Setup in FHEM

Die Einbindung in FHEM erfolgt wesentlich mit generischen MQTT2_DEVICEs. Eine Bridgedevice fungiert als Verteil- und Auffangstation für MQTT Nachrichten. Dadurch werden die Player als separate MQTT2_DEVICEs erzeugt. Ein paar Templates vereinfachen die Konfiguration.

Der folgende Code ist für die Raw Definiton bzw. Kommandozeile von FHEM und realisiert diese Schritte:
  • definiere die Bridge mit Template, der Topic (default: sonos) wird abgefragt!
    • Das Template definiert 2 notify welche die Player automatisch konfigurieren:
      • Das Erste wird getriggert wenn ein MQTT2_RINCON_ Device definiert wird. Es wird das Basistemplate für Player angewendet und die Abfrage der ZonenInfo gestartet.
      • Das Zweite wird beim Eintreffen der ZonenInfo gestartet und konfiguriert den Player "fein".  
  • starte die sonos2mqtt Bridge auf Systemebene im Kontext User fhem
  • nach kurzer Zeit sollten die Player fertig sein.
Nur falls es noch keinen MQTT2_SERVER gibt:
define mqtt2s MQTT2_SERVER 1883 global
attr mqtt2s room MQTT_IO
Falls ein existierender MQTT2_SERVER verwendet wird, bitte unbedingt prüfen - das autocreate auf simple steht!

Der folgende Codeschnipsel richtet die Bridge manuell ein und startet die automatische Erzeugung der Player. 
define SonosBridge MQTT2_DEVICE
attr SonosBridge IODev mqtt2s
attr SonosBridge room MQTT2_DEVICE
define n_pm2_sonos notify global:INITIALIZED "pm2 -s start sonos2mqtt"
sleep global:ATTR.SonosBridge.stateFormat.connected;trigger n_pm2_sonos start
set SonosBridge attrTemplate sonos2mqtt_bridge_comfort
Ist die Bridge schon automatisch erzeugt worden, wendet man nur das Template an. (Dann ist ein rename empfohlen!)
Nach wenigen Sekunden sollten alle Player sichtbar sein. Die haben eine schlichte Oberfläche und ein paar Dinge fehlen noch. Das ganze ist aber sowieso mehr ein Baukasten als ein fertiges Modul.

Sprachausgabe - der "speak" Befehl mit Boardmitteln

Gegenüber der Beschreibung im Wiki kann man auch die bestehende Sonos Umgebung verwenden:
  • Samba installiert, 
  • Freigabe SonosSpeak auf /var/SonosSpeak. 
Dazu wird Text2Speech etwas anders konfiguriert:
demod SonosTTS Text2Speech none
attr SonosTTS TTS_CacheFileDir /var/SonosSpeak
attr SonosTTS userReadings cifsName:lastFilename.* {my $host=ReadingsVal($name,'host','set host first');;my $lastFileName=ReadingsVal($name,'lastFilename','');;my @arr=split('/',$lastFileName);;$arr[0]='x-file-cifs:/';;$arr[1]=$host;;join('/',@arr)}
Die Weiterentwicklung des Speak Befehls im Wiki könnte der sayText Befehl sein (Für die DEF setList):
sayText:textField { my $tts=ReadingsVal('SonosBridge','tts','SonosTTS');my ($cmd,$text)=split(' ', $EVENT,2);fhem("setreading $tts text ".ReadingsVal($tts,'text',' ').' '.$text.";sleep 0.4 tts;set $tts tts [$tts:text];sleep $tts:playing:.0 ;set $NAME notify [$tts:vol] [$tts:httpName];deletereading $tts text")}
Bei diesem Befehl muss das Volume vorher als Reading gesetzt werden. Der Befehl funktioniert also bewusst anders.
setreading SonosTTS vol 15

Tipps zur Verwendung der Player

Die automatische Konfiguration setzt den alias entsprechend dem Namen im Sonos vergeben. Die langen MQTT2_RINCON_ Namen sind unhandlich und schlecht lesbar. Man kann Player mit einem devspec ansprechen, das simpelste ist:
set alias=Büro ...
Oder alle Player
set model=sonos2mqtt_speaker leaveGroup

Was passiert im Hintergrund?

Das Template "sonos2mqtt_bridge_comfort" wendet ein Template auf das Bridge Device an und definiert zwei notify für die automatische Konfiguration.
Das Erste triggert auf den autocreate Event, wendet ein Template für den Player an und fragt über MQTT weitere Informationen für den Player ab.
define n_configSonos1 notify global:DEFINED.MQTT2_RINCON_[A-Z0-9]+ sleep 1;;set $EVTPART1 attrTemplate sonos2mqtt_speaker;;set $EVTPART1 x_raw_payload {"command": "adv-command","input": {"cmd":"GetZoneInfo","reply":"ZoneInfo"}}
Das zweite notify triggert auf die Namen der Player wenn als Antwort auf den Request von notify 1 das Reading IPAdresse gesetzt wird.
defmod n_configSonos2 notify MQTT2_RINCON_[A-Z0-9]+:IPAddress:.* {\
  no warnings 'experimental::smartmatch';;\
  my @tv = ("S14","S11");;\
  my $url="http://$EVTPART1:1400";;\
  my $xmltext = GetFileFromURL("$url/xml/device_description.xml");;\
  my ($mn)=$xmltext =~ /<modelNumber>(S[0-9]+)/;;\
  my ($img)=$xmltext =~ /<url>(.*)<\/url>/;;\
  my $icon="Sonos2mqtt_icon-$mn";;\
  my $setList=AttrVal($NAME,'setList','');;\
  fhem("setreading $NAME modelNumber $mn");;\
  fhem("\"wget -qO ./www/images/default/$icon.png $url$img\"");;\
  fhem("attr $NAME icon $icon;;sleep 4 WI;;set WEB rereadicons");;\
  if ($mn ~~ @tv) {$setList=~s/input:Queue \{/input:Queue,TV \{/};;\
  $setList=~s/;;/;;;;/g;;\
  fhem("attr $NAME setList $setList")\
}
Mit diesem Befehl wird das Anlegen aller Player getriggert:
"pm2 start sonos2mqtt"

Radio starten

In der setList sind derzeit die einzelnen Steuerbefehle. setAVTUri setzt zwar eine neue Abspielquelle, stoppt aber den Player und beginnt nicht automatisch mit spielen. Eigentlich wäre es aber praktisch.

Anmerkung: Der Befehl playUri ist im aktuellen Template enthalten! 

Die Sonos Umgebung braucht offenbar dazu einen Moment, meine derzeitige Idee ist also:
  1. setAVTUri ausführen
  2. 1 sec später den play Befehl starten
Die Ergänzung in der setList sähe also so aus:
playUri:textField {fhem("set $NAME setAVTUri $EVTPART1; sleep 1; set $NAME play")}
Als vorübergehende Lösung für sowas wie Radiosender habe ich mir so etwas gebaut. Das notify schreibt den Titel und die uri beim Start (auch über die Sonos App) in den Dummy. Diesen kann man dann selbst zum Starten verwenden: [Titel:DeutschlandfunkKultur]
define Titel dummy
define n_MQTT2_RINCON notify MQTT2_RINCON_[0-9A-Z]{17}:currentTrack_Title:.* {my $title=ReadingsVal($NAME,"enqueuedMetadata_Title","");;$title=~s/\W//g;;my $uri=ReadingsVal($NAME,"currentTrack_TrackUri","");;fhem("setreading Titel $title $uri") }

Nicht einfach default

Hat man den MQTT2_SERVER anders definiert, nicht lokal bzw. mit allowed abgesichert, muss beim Start von sonos2mqtt der Parameter --mqtt übergeben werden. 
Dem pm2 Befehl folgenden durch die Angabe "--" die weiteren Parameter für sonos2mqtt.
"pm2 start sonos2mqtt -- --mqtt mqtt://myuser:the_secret_password@192.168.0.3:1800"
Mit dem Parameter --prefix könnte man den Basistopic setzen (default: sonos).

Weitere Entwicklung

Für die weitere Entwicklung ist im Player der set Punkt x_raw_payload eingebaut. Damit kann einfach json Payload "publishen". Das Original Beispiel aus der Doku, Sound abspielen:
{
  "command": "notify",
  "input": {
    "trackUri": "https://cdn.smartersoft-group.com/various/pull-bell-short.mp3",
    "onlyWhenPlaying": false,
    "timeout": 10,
    "volume": 15,
    "delayMs": 700
  }
}

Werte abfragen

Der Entwickler von sonos2mqtt hat jetzt auch die Abfrage ermöglicht. Um bei Get Befehlen etwas zurück zu bekommen, muss ein Topic übergeben werden:
{
  "command": "adv-command",
  "input": {
    "cmd": "GetFavoriteRadioStations",
    "reply": "DougRadio"
  }
}
Das Beispiel fragt die Favoriten ab. Die Ergebnisse erscheinen dann in einem Reading mit der Struktur Result_8_ItemId. Weiter Informationen findet man hier.

Anmerkung/Tipps

Anstatt FHEM komplett zu aktualisieren kann man auch nur die Templates neu laden:
{ Svn_GetFile("FHEM/lib/AttrTemplate/mqtt2.template", "FHEM/lib/AttrTemplate/mqtt2.template", sub(){ AttrTemplate_Initialize() }) }

Mir ist aufgefallen, dass offenbar System abhängig der Ordner node_modules bei dieser Installationsart an unterschiedlichen Orten angelegt wird! Ich hatte /usr/lib/node_modules und /node_modules. Der Befehl sudo find / -name "sonos2mqtt" hilft dabei den richtigen Pfad zu finden.

Will man auf Systemebene mit pm2 sonos2mqtt monitoren o.ä. muss man dies als User fhem tun. Ein anderer hat auf die Umgebung keinen Zugriff!
sudo -su fhem pm2 list

Montag, 4. November 2019

MQTT - ich will das testen

... und aufschreiben. Das ist alles ziemlich spannend.
Entstanden ist letztendlich ein kleiner Workshop als Trockentest - Es wird keine extra Hardware benötigt!

Testumgebung

Ein frisches (oder gut gebrauchtes) FHEM und dann
define mqtt2s MQTT2_SERVER 1883 global
attr mqtt2s room MQTT_IO
Ein Linux oder Windows mit mosquitto installiert (install mosquitto-clients oder Windows )
Dort Terminal oder CMD Fenster und folgende Zeile eingeben:
mosquitto_pub -h raspib2 -i COMPUTER -t CPU/raspib/temperature -m 22 
Was passiert? Es entsteht sofort ein Device in FHEM:
defmod MQTT2_COMPUTER MQTT2_DEVICE COMPUTER
attr MQTT2_COMPUTER IODev mqtt2s
attr MQTT2_COMPUTER readingList COMPUTER:CPU/raspib/temperature:.* temperature
attr MQTT2_COMPUTER room MQTT2_DEVICE

setstate MQTT2_COMPUTER 2019-10-21 12:57:00 temperature 22

Empfänger

Weitere Publish Befehle aktualisieren nur das Reading temperature. 

Hinweis 2023: Ich habe ein universelles Script geschrieben um Daten von einem Linux System zu einem MQTT Server zu senden.

Beispiel: Endlosschleife (Abbruch ctrl+c) die alle 30 sec die aktuelle CPU Temperatur schreibt:
while [ true ] ; do mosquitto_pub -h raspib2 -i COMPUTER -t CPU/$(hostname)/temperature -m $(($(</sys/class/thermal/thermal_zone0/temp)/1000)) ; sleep 30 ; done
Der wird sogar geloggt, autocreate hat gleich ein FileLog mit angelegt.
Diese Device bedient jetzt eine wesentliche Funktion: den MQTT Empfänger.

Hinweis: Die autocreate Funktion erzeugt ein Device mit der CID (COMPUTER).
Wird die while Schleife auf einem anderen Computer gestartet, ändert sich die Topic Liste (wegen CPU/$hostname). Dadurch wird die readingList ergänzt aber das Reading bleibt das Gleiche (temperature) d.h. alle Werte von unterschiedlichen Quellen kommen in das gleiche Reading! Weiter unten zeige ich wie man das manuell ändert.

Man wird in der Praxis nicht umhin kommen die automatisch erzeugten MQTT_Geräte manuell zu bearbeiten! (Stand November 2019)

Monitor

Ich kann die Nachrichten auch ganz andere Stelle sehen, z.B. in einem Terminal. Dort einfach als Subscriber für alle Topics registrieren:
mosquitto_sub -h raspib2 -v -t '#'
Damit wird jede Nachricht vom Terminal 1 an den MQTT Server auch in Terminal 2 empfangen. Im mqtt2s sieht man im reading nrclients die Verbindung. Das eigene Device wird dort nicht angezeigt.

Sender

Als nächstes will ich von dem Device etwas publishen (eigentlich ein unnützer Versuch)
attr MQTT2_COMPUTER setList off:noArg cmnd/raspib/POWER off\
on:noArg cmnd/raspib/POWER on
Damit bekomme ich im Device ein on und off "Button" und sehe in meinem Terminal 2 zwischen den Temperaturen die ankommende Nachricht. Die aber jetzt nichts bewirkt.
CPU/raspib/temperature 53
cmnd/raspib/POWER on
cmnd/raspib/POWER off
CPU/raspib/temperature 52

Gerät steuern

Versuch: Simulation und Steuerung eines externen mqtt Gerätes
Ich erzeuge in einer zweiten FHEM Instanz ein Gerät (MQTT2_DEVICE) und einen IO (MQTT2_CLIENT).
Über die readingList will ich die Nachricht POWER (von dem Publish Versuch eben) in das Reading state schreiben:
define mqtt2c MQTT2_CLIENT raspib2:1883
attr mqtt2c room MQTT_IO

define MQTT2_Test MQTT2_DEVICE Test
attr MQTT2_Test IODev mqtt2c
attr MQTT2_Test readingList cmnd/raspib/POWER:.* state
attr MQTT2_Test room MQTT2_DEVICE
Ergebnis: Ich kann jetzt sowohl von FHEM (MQTT2_COMPUTER) als auch von extern das Gerät MQTT2_Test schalten:
mosquitto_pub -h raspib2 -t cmnd/raspib/POWER -m off
Damit das Gerät sich selbst "steuern" kann und mqtt Nachrichten erzeugt erweitere ich es noch um die setList:
attr MQTT2_Test setList off:noArg cmnd/raspib/POWER off\
on:noArg cmnd/raspib/POWER on
Funktion
  • Das ursprünglich automatische Gerät "COMPUTER" kann das Gerät "Test" in der zweiten Instanz steuern.
  • Über eine separate Publish Nachricht kann das Gerät "Test" ebenfalls gesteuert werden 
  • Das Gerät COMPUTER reagiert selbst nicht auf die Nachrichten des Gerätes Test oder von "außen". 
Mit dem attr mqtt2s rePublish 1 oder mit einem zusätzlichen IO (MQTT2_CLIENT) funktioniert dies auch in der ersten FHEM Instanz.
Ich bin nicht sicher ob es einen gutem Grund(Schleifen?) gibt, die eigenen Publish-Nachrichten nicht per default an die eigenen Devices zu verteilen.

Erweiterung des Versuches:
Man kopiert die komplette Definition von "Test" inklusiver der Definition mqtt2c in die erste Instanz.
Ergebnis: Beide FHEM Instanz haben jetzt ein Gerät "Test" welches synchron schaltet.
Zusätzlich können beide Geräte von "außen" und über das Gerät "COMPUTER" gesteuert werden. Durch Manipulation der IOs mit disable 1/0 und beim mqtt2s mit rePublish 1/0 kann man das Verhalten und die Auswirkungen gut testen.

MQTT über Internet

Steve hat hier eine Übersicht über kostenfreie MQTT Broker erstellt.
Bei myqtthub.com gibt es einen "open Plan" der kostenfrei einen MQTT Broker bietet (Oktober 2020).
Bei cloudmqtt.com gab es mal die "cute cat" kostenfrei. 
Nachtrag Juli 2020:  Mein Account funktioniert weiter, obwohl das Angebot nicht mehr existiert:
  • den Zugang "normal", 
  • SSL/TLS, Websocket und 
  • API Zugriff! 
  • 5 User Connections und 10 kbit/s Datentransfer Volumen. 
Achtung: Die CID muss beim Zugriff auf cloudmqtt einmalig sein, eine zweite Verbindung mit gleicher CID beendet die Erste!
So bekommt die Testumgebung (2. Instanz) einen weiteren IO.
define mqtt2Cloud MQTT2_CLIENT xxxxxx.cloudmqtt.com:21234
attr mqtt2Cloud SSL 1
attr mqtt2Cloud room MQTT_IO
attr mqtt2Cloud username uuuuuuu
set mqtt2Cloud password pppppppppppp
Um die Verbindung zum cloudmqtt zu testen habe ich mosquitto_pub oder auch ein Android App MyMQTT ausprobiert. Die Android App kann leider keine SSL Verbindung.
Hinweis: Will man mit mosquitto eine SSL Verbindung machen, muss man die Option --insecure verwenden?! (leider weiß ich nicht warum)
mosquitto_sub -h xxxxxx.cloudmqtt.com -p 21234 -u uuuuuuu -P pppppppppppp -v -t '#' --capath /etc/ssl/certs --insecure
Um eine Reaktion im System zu erhalten, habe ich noch ein MQTT2_Test1 Device in der 2. Instanz erstellt:
define MQTT2_Test1 MQTT2_DEVICE Test1
attr MQTT2_Test1 IODev mqtt2Cloud
attr MQTT2_Test1 readingList mobil/POWER:.* state
attr MQTT2_Test1 room MQTT2_DEVICE
attr MQTT2_Test1 setList off:noArg mobil/POWER off\
on:noArg mobil/POWER on
Das reagiert jetzt auf den Topic mobil/POWER (analog zu dem vorhanden MQTT2_Test) Device und ist "mobil" steuerbar.

Nach kurzer Zeit ist mir etwas aufgefallen:
Erweitert man die readingList um den topic vom Gerät MQTT2_Test:
attr MQTT2_Test1 readingList mobil/POWER:.* state\
cmnd/raspib/POWER:.* Teststate
wird das Reading Teststate analog zum Reading state von Gerät MQTT2_Test aktualisiert.
  • Mehr noch: Ich kann den topic cmnd/raspib/POWER über den cloudmqtt Server publishen und das Gerät Test in der 2. Instanz schalten. Das attr IODev muss dazu nicht verändert werden: MQTT2_Test hat IODev mqttc und 
  • MQTT2_Test1 hat IODev mqttCloud! 
  • Das IODev hat ausgehend Bedeutung, eingehend verarbeiten die MQTT2_DEVICEs nur den Topic!

Brücke schlagen

Ich will den Versuch erweitern und Werte zwischen FHEM Instanzen übertragen, die gar nicht aus dem mqtt Umfeld kommen.
Dazu erzeuge ich in meiner zweiten Instanz ein paar "manuelle Temperaturfühler":
define fuehler1 dummy
attr fuehler1 readingList temperature humidity
attr fuehler1 room Fühler
attr fuehler1 setList temperature:25,25.5,26,26.5,27,27.5,28 humidity:50,55,60,65,70,75,80
attr fuehler1 userReadings state {my $temp=ReadingsVal($name,"temperature",99);;my $hum=ReadingsVal($name,"humidity",99);;sprintf "T: $temp H: $hum"}
Zur Übertragung nehme ich ein notify (die Idee ist von hier). Das notify macht folgendes:
  • es reagiert auf Events der Geräte fuehler1 oder fuehler2 usw. sowie deren Readings temperature und humidity
  • es verwendet set magic
  • es befreit den Readingnamen im Event vom ":" (wobei ich nicht sicher bin ob das wirklich sein muss). Die Idee stammt von hier.
define n_publish3 notify fuehler.:(temperature:|humidity:).* set mqtt2c publish -r home/states/$NAME/{((split(":","$EVTPART0"))[0])} $EVTPART1
attr n_publish3 room Fühler
In der ersten Instanz ensteht jetzt durch autocreate eine Art Sammeldevice MQTT2_mqtt2c.
Aus diesem erzeugt man sich die passenden Devices. Wichtig hier ist die Anpassung der Topic "Kette"
define fuehler1 MQTT2_DEVICE mqtt2c
attr fuehler1 readingList home/states/fuehler1/temperature:.* temperature\
home/states/fuehler1/humidity:.* humidity
attr fuehler1 room Fühler
attr fuehler1 userReadings state {my $temp=ReadingsVal($name,"temperature",99);;my $hum=ReadingsVal($name,"humidity",99);;sprintf "T: $temp H: $hum"}

Modifikation

Die attr readingList wird schnell unübersichtlich und schwierig zu warten. Mit nur zwei Modifikationen stellt man die Übertragung auf das JSON Format um.

  • Die Definition der mqtt Geräte wird einheitlicher und einfacher.
  • Man kann auch leicht mehrere Readings in einer Nachricht übertragen.

Erste Instanz
Die readingList reagiert so auf alle Nachrichten die mit dem Topic home/states/ beginnen und wo im Topic der eigene Gerätename vorkommt.
attr fuehler. readingList home/states/.* { if ($TOPIC=~m/$NAME/) { json2nameValue($EVENT) } }
Zweite Instanz
Das notify erzeugt ab sofort Nachrichten im JSON Format.
defmod n_publish3 notify fuehler.:(temperature:|humidity:).* set mqtt2c publish -r home/states/$NAME { "{((split(":","$EVTPART0"))[0])}": $EVTPART1 }

Alle Readings auslesen

Ich habe mal noch zwei Codezeilen gebaut, die entweder alle Readings eines Gerätes oder bestimmte Readings (Array) als JSON String ausgeben. Dieser Code ist zum Test in der Kommandozeile gedacht!
# Für Kommandozeile - alle Readings
{my $d="fuehler1";;my $hash = $defs{$d};;my $readings = $hash->{READINGS};;my $message="{ ";;foreach my $a ( keys %{$readings} ) {my $val=ReadingsVal($d,$a,"error");;$message .= toJSON($a)." : ".toJSON($val)." ," };;chop($message);;$message.="}"}

# Für Kommandozeile - nur zwei Readings
{my $d="fuehler1";;my @readings = ("temperature","humidity");;my $message="{ ";;foreach my $a ( @readings) {my $val=ReadingsVal($d,$a,"error");;$message .= toJSON($a)." : ".toJSON($val)." ," };;chop($message);;$message.="}"}
Will man im obigen set ... publish eine JSON formatierte Nachricht mit allen Readings des triggernden Gerätes absetzen, sieht das wie folgt aus (Dieser Code ist für die DEF!):
{(my $d="$NAME";;my $hash = $defs{$d};;my $readings = $hash->{READINGS};;my $message="{ ";;foreach my $a ( keys %{$readings} ) {$message .= toJSON($a)." : ".toJSON(ReadingsVal($d,$a,"error"))." ," };;chop($message);;$message.="}")}
Ich habe bewusst auf Formatierung verzichtet, in der DEF kann man den Code "lesbarer" formatieren.
AchtungDie FHEM Kommandozeile und set magic haben gegenüber dem normalen Perl Code immer ein paar Besonderheiten. -> ;; {(...)} "$NAME"

Tipp:

Nimmt man als MQTT Server einen Server in der Cloud, kann man damit die FHEM Instanzen ziemlich simpel übers Internet koppeln, ohne irgendeine Portfreigabe!

Allgemeine Brücke

Das Modul MQTT_GENERIC_BRIDGE scheint für die Kopplung der MQTT- und Nicht-MQTT Welt designed zu sein - eigentlich wollte ich dies als nächstes testen. Die Installation ist deutlich aufwendiger, deswegen sehe ich erstmal davon ab.

Ganz verrückt

Man kann aber auch mit einem kleinen  Shell Script mqtt Nachrichten auf System Ebene verarbeiten.

Notizen und Infos

Die mosquitto Windows Version installiert Server und Clients in einem Setup, man kann den Service aber "aushaken". Unter Linux installiert das Paket mosquitto den Server und mosquitto-clients nur die Client Tools.

Achtung: Alle MQTT Server laufen per default auf Port 1883! (Auch Mosquitto Server und MQTT2_SERVER) Es kann nur einen geben!

Gibt man bei mosquitto_pub keine CID an (Option -i) dann wird dies per default so gesetzt: "Defaults to mosquitto_pub_ appended with the process id." MQTT2_DEVICE ignoriert diese CID und legt dafür kein neues Device an!

Topic: Nach ersten Versuchen habe ich irgendwie gelesen, man soll bei den Topics keine führenden Slash's nehmen. Dadurch entsteht eine leere Ebene (bad practise).
Man kann jeden beliebigen Topic monitoren, das # gibt alle weiteren Level zurück. Das # allein liefert alle Topics, damit die Shell das nicht als Kommentar interpretiert muss man '#' schreiben!

Von steves-internet-guide habe ich mir auch ein paar Anregungen geholt.

Mittwoch, 30. Oktober 2019

File Log - und gut?

Ich suche schon lange einen Anfang um vom FileLog auf eine Datenbank umzusteigen. Es soll ja Vorteile bringen ...
Das sind einfach ein paar Notizen, eine Anleitung wird das so erstmal nicht werden.

Ein paar Gedankenstriche

  • Ich hätte Bedenken eine SQL Datenbank auf einer SD Card zu betreiben.
  • SQLite ist eine Datei basierte, lokale Datenbank, es ist kein Datenbankserver.
  • MySql ist ein Datenbankserver, der Zugriff kann auch Remote erfolgen, man könnte die Server-Instanzen auf mehrere Maschinen aufteilen.
  • Einzelne Textdateien können beim Stromausfall unbrauchbar werden, bei einem Datenbankserver kann die gesamte Datenbank unbrauchbar werden.
  • Backup muss anders gemacht werden, einfach alle Dateien eines laufenden DB Servers sichern ist nicht sinnvoll.
  • Die Erzeugung von SVG-Plots ist aus meiner Sicht derzeit schwieriger als aus FileLogs und hat Stolpersteine.

Um in das Thema einzusteigen, habe ich MariaDB installiert, ein DBLog und ein FileLogConvert Device in FHEM angelegt und erstmal geschaut wie es sich mit einem importierten Jahreslog von 2018 mit der Temperatur und Luftfeuchte meines Aussensensors so arbeiten lässt.
Ich konnte die Temperatur plotten, die Luftfeuchte funktionierte nicht. Obwohl ich dachte, ich hätte die Erklärungen im Wiki für den Umstieg verstanden. Hatte ich - aber meine Daten waren Murks.

Plot erzeugen

Was habe ich beim Erzeugen eines Plot bisher verstanden und für mich als Handlungsgrundlage festgelegt habe:
Das Anlegen eines Plots geht quasi los wie immer:
Am DbLog Device gibt es oben einen Link: Create SVG Plot from DbLog
Im Menüpunkt "Diagram label, Source" wählt man als Quelle sein DbLog Device.
Dahinter kommt die Wertzuweisung in der Form (für den Fall das man nicht nur ein Reading mit einem Wert pro Zeile geloggt hat).
<Device>:<Reading>:::$val=~s/<regex>/$1/eg
$val ist die interne Variable die letztendlich den Wert für diese Plotzeile enthält (z.B. die Temperatur)
Auf $val wird ein regex angewendet (=~) welches mit suchen (<regex>) und ersetzen ($1) den Inhalt von $val verändert. Trifft das regex nicht zu, bleibt $val unverändert!
$1 (oder auch $2 usw) enthält dabei den "Groupmatch", also normal einen Teilstring.

Match

Dezimalzahlen mit Punkt kann man so als Gruppe erkennen: ([-\d.]+) -> erkennt eine Zeichengruppe aus "- . und Ziffer" zwischen 1 und unendlich mal. (* erkennt zwischen 0 und unendlich mal, Punkt ist hier wirklich der Punkt und nicht ein beliebiges Zeichen)
Ein "T: " am Beginn eines Wertes kann man so erkennen: ^T:\s -> Anfang der Zeichenkette, ein T, ein : und ein Whitespace.
Einen Trenner zwischendrin kann man so erkennen: .H:\s -> Ein Zeichen, ein H ein : und ein Whitespace
Mein geloggtes state Reading sieht so aus: T: 20.7 H: 69 und mit dem regex   ^T:\s([-\d.]+).H:\s([-\d.]+) habe ich den Zahlenwert hinter T in $1 und den Zahlenwert für H in $2.
Nach diesem Schema ließen sich jetzt beliebige Kolonnen analysieren. Anstatt in jeder Zeile die Klammern zu versetzen und immer eine Ersetzung mit $1 zu machen, finde ich es übersichtlicher jede Zeile mit gleichen (allen) Klammern auszustatten und von Zeile zu Zeile $1 auf $n zu wechseln.

Resultat

Zeile in der Datenbank
select * from history where (TIMESTAMP like "2019-08-19 10:25:21");

+---------------------+--------------+-------+----------------------+---------+---------------+------+
| TIMESTAMP           | DEVICE       | TYPE  | EVENT                | READING | VALUE         | UNIT |
+---------------------+--------------+-------+----------------------+---------+---------------+------+
| 2019-08-19 10:25:21 | SensorAussen | DUMMY | state: T: 20.7 H: 69 | state   | T: 20.7 H: 69 |      |
| 2019-08-19 10:25:21 | SensorAussen | DUMMY | tempMax: 30.3        | tempMax | 30.3          |      |
| 2019-08-19 10:25:21 | SensorAussen | DUMMY | tempMin: 18.6        | tempMin | 18.6          |      |
+---------------------+--------------+-------+----------------------+---------+---------------+------+

Zeilen im SVG Plot
SensorAussen:state:::$val=~s/^T:\s([-\d.]+).*H:\s([-\d.]+)/$1/eg
SensorAussen:state:::$val=~s/^T:\s([-\d.]+).*H:\s([-\d.]+)/$2/eg

Für die Zeilen wo nur ein Wert in Value steht sieht die Sache viel einfacher aus:
SensorAussen:tempMax::

Fragen

Was der Wert an dritter Stelle (::0:) bewirkt den man laut Wiki hinschreiben kann? - ist mir nicht klar.
Auch ist nicht klar, warum bei der Angabe von $val= noch ein vierter Trenner ":" sein muss.
Device:Reading:?:?:$val= ...
Der Umgang mit Platzhalter <SPEC1> (attr plotfunction) oder mit attr plotReplace ist mir auch noch nicht klar. Es würde SVG gplot Dateien universell nutzbar machen.

Achtung

Wenn die current Tabelle gefüllt ist, steht der Fenster Device:Reading nicht zum Schreiben zur Verfügung. Man kann dann nur "Pärchen" aus der Klappliste auswählen. Wenn man dann speichert wird jede Zeile die man vorher mit :$val= eingetragen hat gelöscht!
Will man den Inhalt der current löschen geht das mit einem SQL Statement:
delete from current;

Fazit

Es ist aufwendig und der Nutzen scheint mir derzeit gering. Ich muss mich noch weiter damit beschäftigen

Dienstag, 8. Oktober 2019

schaltbare Steckdose auf Wlan

Vorbemerkung:
An der hier erwähnten Software tuya-convert und auch an der MQTT(2) Implementierung in FHEM wird quasi ständig gearbeitet. Konkrete Befehle usw. können sich mittlerweile geändert haben!

Der Markt ist förmlich geflutet von diesen "Plugs". Alle arbeiten irgendwie ähnlich, haben einen ESP Chip als "Herz" mit einer Software die meist über irgendeine Cloud, mit einer App auf dem Smartphone "spricht". Und dann kann "Oma" vom Kreuzfahrtschiff aus, zu Hause das Licht an und aus machen. Viele dieser Geräte bedienen sich der Cloud von Tuya und diese kann man leicht umprogrammieren und sie damit "Cloud Frei" betreiben.
Wie verbindet man diese Dinger jetzt mit FHEM? Ein Zauberwort ist MQTT - mal wirklich so etwas wie der Standard in der Automatisierung.

Ich habe vier Gerätetypen ausprobiert:
  • Gosund SP111 (2500 Watt und 3450 Watt Version)
  • Shelly Plug S
  • Gosund SP1
Die ersten Beiden sind vom äußeren her besonders schick und klein, die Dritte ist größer und kann 16 Ampere schalten. So groß und klobig wie meine "alten" Homematic Dosen sind sie alle nicht. Alle Dosen können die angeschlossene Leistung messen, was für Automatisierungsaufgaben sehr hilfreich ist!

Richtige Firmware flashen

Achtung: Für tuya-convert empfehle ich eine extra SD Card /extra System zu nehmen! Nicht einfach ins produktive System installieren!
Es gibt eine neuer Version, die arbeitet etwas anders als hier beschrieben.

Viele Wlan Dosen können ohne sie zu öffnen nach der Anleitung im obigen Link geflashed werden. Man holt sich zunächst die aktuelle Software vom GitHub und startet das Script
git clone https://github.com/ct-Open-Source/tuya-convert
cd tuya-convert
sudo ./install_prereq.sh

danach kann der Flashvorgang gestartet werden
sudo ./start_flash.sh
Man muss den ersten Dialog mit yes + enter beantworten. Danach muss man mit einen Gerät (Android oder Windows PC, alles andere geht wohl nicht) eine Wlan Verbindung zu vtrust-flash machen und behalten!
Jetzt startet man die Dose, sie muss schnell blinken! Nach ein paar Sekunden sollte ein Download Dialog zu sehen sein. Wenn es beim ersten Mal nicht funktioniert, lohnt es sich mehrfach zu probieren!
Ist der Vorgang komplett abgeschlossen, ist nur eine Übergangsfirmware auf der Dose, hier käme man noch zurück oder flashed jetzt tasmota. Der erste Befehl zeigt nur die Bereitschaft, der zweite Befehl flashed Tasmota Basic.
curl http://10.42.42.42/flash2
curl http://10.42.42.42/flash3

Nach einiger Zeit muss sich ein neues Wlan melden sonoff-1234.
Damit beendet man den Vorgang:
sudo ./stop_flash.sh

tuya convert flashed eine Sonoff/Tasmota Basic Version 6.6.0.7(digiMIN). Ich habe festgestellt, dass vor allem bez. der Messung das Standard Image wesentlich besser läuft. Das kann man nach dem ersten Start in der Oberfläche des ESP OTA flashen.
Achtung: Aktuell (ab Version 7?) haben die Entwickler alles umbenannt: sonoff.bin heisst jetzt tasmota.bin. Man muss also die url ändern:

http://thehackbox.org/tasmota/release/tasmota.bin
# oder eine andere Sprache
http://thehackbox.org/tasmota/release/tasmota-DE.bin

Danach Werksreset machen und neu konfigurieren! Jetzt heisst das Gerät auch nicht mehr sonoff-1234 sondern tasmota-1234

Wenn es gar nicht funktioniert: öffnen, Drähte anlöten und mit USB-Serial Wandler flashen.
zu offenem sonoff-1234 verbinden (die vierstellige Zahl merken) und (falls es nicht automatisch funktioniert) zu 192.168.4.1 verbinden. Dort Wlan konfigurieren.

Konfiguration der Steckdose an sich per eigener Weboberfläche

Tasmota

Eigentlich muss man primär nur 2 (3) Punkte konfigurieren.

1. Menüpunkt: Configure Module
Gosund SP1 - direkt als SP1 vorhanden
Gosund SP111 - als BlitzWolf SHP konfigurieren
SP111 neue Version (3450 Watt) - Für diese Dose braucht man ein Template!

1.1 Menüpunkte: Configure Other / Template
Ich habe hier ein Template gefunden:
Rot Wlan, Blau Status
{"NAME":"Gosund SP111 2","GPIO":[56,0,57,0,132,134,0,0,131,17,0,21,0],"FLAG":0,"BASE":18}
Rot Status, Blau Wlan
{"NAME":"Gosund SP111 2","GPIO":[57,0,56,0,132,134,0,0,131,17,0,21,0],"FLAG":0,"BASE":45}

2. Menüpunkt: Configure MQTT
Server, Port, User, Password einrichten und Haken bei Passwort setzen!

Shelly Plug

Im Menüpunkt Internet&Security/Developer "enable action excution via mqtt" aktivieren und
  • Benutzer/Passwort
  • Server:Port 
eintragen.

MQTT in FHEM

Ist absolut easy. Man definiert
define mqtt2s MQTT2_SERVER 1883 global
und das war es schon. Für die Sicherheit sollte noch ein allowed definiert werden.
define allowedMqtt allowed
attr allowedMqtt validFor mqtt2s
set allowedMqtt basicAuth username password
Die Steckdosen werden, nachdem sie etwas gesendet haben, automatisch angelegt (Raum MQTT2_Device), man muss nur etwas warten. Die Meldung der Dose beim mqtt Server bewirkt ein autocreate.
Um die Dose schalten zu können braucht man lediglich ein passendes Template anzuwenden.
Dort passieren laufen noch Entwicklungen, deswegen fällt die Beschreibung hier nur kurz aus. Beispiel:
set MQTT2_DVES_xxxxx attrTemplate tasmota_basic

Empfehlung

Wer es klein und hübsch haben will und keine große Leistung braucht, nimmt den Shelly Plug S. Den braucht man nicht umflashen, der misst zuverlässig die Leistung, lässt sich leicht über die eigene Firmware konfigurieren und in MQTT einbinden.
Für größere Leistung nimmt man den Gosund SP1 (gibt es baugleich unter anderen Bezeichnungen). Der misst zuverlässig und ist mit Tasmota Software gut konfigurierbar.
Der Gosund SP111 ist (mit Tasmota) aus meiner Sicht nicht so doll. Die Messwerte schwanken in einem Bereich +- 10%.
Es gibt von der SP111 eine Version 3500 Watt. Die hat eine andere Platine, kann aber auch OTA geflashed werden.
Alle Stecker haben einen Eigenverbrauch:
ca. Angabe in Watt (aus - ein)
  • 0,5-0,8 (Shelly) 
  • 0,3-0,6 (SP111)  
  • 0,3-1,1 (SP1)
Man kann bei allen drei Dosen festlegen wie das Verhalten nach einem Stromausfall ist. Bei Tasmota ist es per default so wie vor dem Stromausfall. Bei dem Shelly ist es per default aus.
Alle drei Dosen können bei einer Überlast abschalten.
Der Shelly Plug S kann auch bei einer überhöhten Eigen-Temperatur abschalten.
Es lässt sich eine maximale Einschaltzeit konfigurieren, bei der egal ist ob lokal oder über MQTT geschaltet wurde.

Tasmota

In der Weboberfläche von Tasmota lassen sich so grundlegende Sachen einstellen. Viele interne Dinge, so zum Beispiel auch Messwerte kalibrieren oder Schwellen für die sofortige Datenübertragung festlegen passiert in der "Konsole", die erreicht man aus dem Hauptmenü und die Kommandos findet man auf der Tasmota Docu Seite unter Commands. Leider ändert sich diese Seite ständig, also notfalls auf der Hauptseite nach Doc und commands suchen.

Beispiele Konsole
Durch Eingabe des Kommandos an sich ohne Parameter bekommt man den momentanen Wert. Mit Parameter setzt man den Wert neu.
TelePeriod 60
PowerDelta 101

ToDo?

Code Block

Dienstag, 30. Juli 2019

Definitionen in FHEM komplett umziehen

Ich habe mehrere System auf denen ich etwas teste und manchmal schleicht sich nach langer Zeit quasi "produktiver Betrieb" ein. Dann möchte ich gern die getestete Definition und alles was dazu gehört: Log, Logdateien, Plots usw. in das produktive System übernehmen. Da steht also die Aufgabe neben der eigentlichen Definition auch noch ein paar Dateien zu übernehmen. Ich habe mir dazu folgendes Vorgehen ausgedacht:
  1. Alle Dateien auf das Zielsystem kopieren oder das Quellsystem mounten
  2. Die wirklich benötigten Dateien an die richtige Stellen, mit den richtigen Berechtigungen, anhand der Definitionen in den FHEM Ordner kopieren.
  3. Die Definitionen vom Quell- auf das Zielsystem kopieren.
Dabei soll so viel wie möglich "automatisch" laufen. Damit ich wenig installieren und konfigurieren muss, hole ich alle potentiellen Dateien mit scp einfach pauschal lokal in ein temporäres Verzeichnis. Hat man große log Dateien - kann man auch einschränken.
host1='Hostname Instanz1'                        #Quell FHEM Instanz
tDir=fhem                                        #relativer Pfad der im lokalen Pfad (HomeDir des Users) angelegt und verwendet wird
mkdir -p $tDir/www
scp -r pi@$host1:/opt/fhem/log/ $tDir/           #kopiert den Remote Pfad log/ nach fhem/log/  
scp -r pi@$host1:/opt/fhem/www/gplot/ $tDir/www/ #kopiert den Remote Pfad www/gplot/ nach fhem/www/gplot/
Jetzt werden die wirklich notwendigen Dateien anhand der Definitionen ermittelt und lokal an die richtige Stelle mit den richtigen Berechtigungen kopiert. Ich mache das deshalb über FHEM.
Zu allen Schritten brauche ich meinen HTTP Client
wget -O fhemcl.sh https://raw.githubusercontent.com/heinz-otto/fhemcl/master/fhemcl.sh

Um die Einzeiler etwas übersichtlicher zu halten, definiere ich noch ein paar Variablen zu Beginn, $host1 und $tDir wurden im Befehlsblock oben schon definiert. In den Einzeilern wird einfach davon ausgegangen, dass der lokale Zugriff nur die Port Nummer 8083 erfordert.
inst1=$host1:8083                #URL Remote Instanz [http://[<user:password@>][<hostName>:]]<portNummer> 
rDir=$(pwd)/$tDir/               #absoluter Pfad für die (kopierten) Quelldateien
gDir='./www/gplot/'              #Pfad in dem die gplot Datei in FHEM liegen
devSpec='FILTER=NAME=.*Sensor.*' #wird in den Befehlen um TYPE= ergänzt
Man kann selbstverständlich die Quelldateien remote mit mount einbinden (z.B. nfs). Dann enthält $rDir den gemounteten Pfad.

Achtung: Die Einzeiler enthalten keine Fehlerbehandlung! Es kann also zu unvorhergesehenen Auswirkungen kommen!
Datensicherung!
Die Kopiervorgänge werden eventuell nicht oder falsch ausgeführt, können aber gleichnamige Dateien überschreiben! Die Änderungen in der cfg werden nicht automatisch gesichert, ein restart von FHEM stellt den alten Zustand wieder her.
Die devSpec sollte man im Zweifel immer testen!
bash fhemcl.sh $inst1 "list TYPE=.*:$devSpec"
Man sollte auch immer zunächst die Einzeiler ohne den letzten Ausführungsteil zur Probe laufen lassen!

Einzelne Schritte

Zunächst als "Fingerübung" die Grundlagen um aus bestimmten Definitionen, die "Sensor" im Namen haben, die Dateien zu kopieren.
  • der list Befehl erzeugt eine Tabelle aus Name und dem entsprechenden INTERNAL, von dieser wird nur die zweite Spalte benötigt,
    • für die log Datei wird mit sed alle Platzhalter %Y,%m usw. durch * ersetzt. Damit werden alle vorhandenen Log Dateien erfasst. 
    • für die gplot Datei wird mit sed noch $gDir vorangestellt und ".gplot" angehängt, 
  • der zweite sed Befehl ersetzt ./ durch den absoluten Pfad $rDir
    • Der Ausdruck ${rDir////\\/} erzeugt dabei aus dem normalen Pfadnamen einen "escaped" Pfadnamen: ersetzt also / durch \/.
  • Der tr Befehl am Ende macht aus der Zeilen- eine Leerzeichen getrennte Liste für den cp Befehl.

Die FileLog (log) Dateien

datei=$(bash fhemcl.sh $inst1 "list TYPE=FileLog:$devSpec logfile"|awk '{print $2}'|sed "s/%./*/g"|sed "s/.\//${rDir////\\/}/"|tr '\n' ' ')
bash fhemcl.sh 8083 "\"cp -n $datei ./log/\""

Die SVG (gplot) Dateien

datei=$(bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec GPLOTFILE"|awk '{print $2}'|sed "s/^/${gDir////\\/}/;s/$/.gplot/"|sed "s/.\//${rDir////\\/}/"|tr '\n' ' ')
bash fhemcl.sh 8083 "{qx(cp -n $datei ./www/gplot/)}"
Ich verwende bewusst zwei unterschiedliche Kopierbefehle. Die erste Variante blockiert das lokale FHEM nicht, allerdings hat man keine Rückmeldung wann der Kopiervorgang beendet ist. Die zweite Variante blockiert FHEM, was bei den kleinen gplot Dateien aber nicht stören sollte.

Definition

Jetzt brauchen nur noch die Definitionen auf das lokale Zielsystem kopiert werden.
bash fhemcl.sh $inst1 "list -r TYPE=FileLog:$devSpec"|bash fhemcl.sh 8083
bash fhemcl.sh $inst1 "list -r TYPE=SVG:$devSpec"|bash fhemcl.sh 8083

Verbundene Definitionen

Der list Befehl ist sehr vielseitig! Neben Gruppen von Definitionen kann man auch einzelne Geräte und deren komplette verbundenen Definitionen umziehen.
Achtung: "-R" funktioniert so richtig nur mit Device Namen ohne regExp, "-R" ist laut Doku auch "unvollkommen"!
So wird die SVG zusammen mit der zugehörigen FileLog Instanz übertragen.
bash fhemcl.sh $inst1 "list -R SVG_FileLog_SensorWG_1"|bash fhemcl.sh 8083

Abhängigkeit

Die SVG Definition und deren Funktion ist von der zugehörigen FileLog Definition abhängig. Also kann man von der SVG Definition alles notwendige ableiten. Die log Dateien anhand des SVG kopieren:
bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec LOGDEVICE"|sed 's/^.*\s/list /;s/$/ logfile/'|bash fhemcl.sh $inst1|awk '{print $2}'
Der Vorgang ist zweistufig, erst wird das LOGDEVICE aus der SVG Definition gelesen, daraus wieder ein list Befehl erzeugt und damit das log File direkt aus der Definition ausgelesen.
Nach dem obigen Schema kann man daraus die Dateiliste erstellen.
|sed "s/%./*/g"|sed "s/.\//${rDir////\\/}/"|tr '\n' ' '
Anstatt über eine Variable ($datei) kann man die Pipe auch fortführen.
|echo "\"cp -n $(cat -) ./log/\""|bash fhemcl.sh 8083
Der komplette Einzeiler ist wirklich lang.
bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec LOGDEVICE"|sed 's/^.*\s/list /;s/$/ logfile/'|bash fhemcl.sh $inst1|awk '{print $2}'|sed "s/%./*/g"|sed "s/.\//${rDir////\\/}/"|tr '\n' ' '|echo "\"cp -n $(cat -) ./log/\""|bash fhemcl.sh 8083

Um den list -R Befehl mit einer devSpec zu verwenden, muss man diese quasi erst auflösen.
Achtung: Wenn die devSpec nur eine Definition trifft, schlägt dieser einfache Einzeiler fehl!
bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec"|sed 's/^/list -R /'|bash fhemcl.sh $inst1|bash fhemcl.sh 8083

Finale

Damit hat man 3 Schritte ausgehend von einer devSpec:
  1. gplot Dateien kopieren
  2. log Dateien kopieren
  3. SVG Definitionen und die zugehörigen FileLog Definitionen übertragen.

bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec GPLOTFILE"|awk '{print $2}'|sed "s/^/${gDir////\\/}/;s/$/.gplot/"|sed "s/.\//${rDir////\\/}/"|tr '\n' ' '|echo "\"cp -n $(cat -) ./www/gplot/\""|bash fhemcl.sh 8083
bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec LOGDEVICE"|sed 's/^.*\s/list /;s/$/ logfile/'|bash fhemcl.sh $inst1|awk '{print $2}'|sed "s/%./*/g"|sed "s/.\//${rDir////\\/}/"|tr '\n' ' '|echo "\"cp -n $(cat -) ./log/\""|bash fhemcl.sh 8083
bash fhemcl.sh $inst1 "list TYPE=SVG:$devSpec"|sed 's/^/list -R /'|bash fhemcl.sh $inst1|bash fhemcl.sh 8083


Dienstag, 9. Juli 2019

Neues Linux System - Nacharbeit

Generell ist es relativ einfach eine bestehende FHEM Installation auf ein neues Betriebssystem/System zu setzen:
  1. Backup FHEM
  2. Kopie der aktuellen Backupdatei -> Server | USB Stick | per scp lokal
  3. sudo halt
  4. neue SD Card mit aktuellem Image (ich behalte immer die alte SD erstmal in Reserve)
  5. setupBasic, setupFhem, testen
  6. Backupdatei verfügbar machen -> Server | USB Stick mounten | per scp - lokal -> nach /home/pi
  7. stop fhem
  8. restore des Backups
  9. start fhem

Sicherung / Backup

Bei mir ist beim obigen Plan mindestens die Anbindung an Dateiserver und die ssh Verbindung auf der Strecke geblieben. Schritt 1 und 8 muss man dafür noch etwas ausbauen.

Sicherung ssh Umgebung

Die notwendigen Dateien befinden sich in diesen Verzeichnissen:
  • /etc/ssh für den ssh Host / Server
  • /opt/fhem/.ssh für den User fhem
  • /home/pi/.ssh für den User pi. 
Ich werde zwei zusätzliche Dateien in den Backup Ordner von FHEM erstellen. Dies könnte man auch von FHEM von Zeit zu Zeit machen lassen. Neben der Datei für eine Historie mit Zeitstempel erzeuge ich noch eine ohne Zeitstempel. Damit muss man bei der Wiederherstellung nicht lang suchen.
Man kann dieses als Script auch unter FHEM ausführen, da aber Systemdateien gesichert werden, muss tar als sudo ausgeführt werden. Damit das funktioniert, muss die zuständige Datei um /bin/tar ergänzt werden.
datei=/opt/fhem/backup/ssh$(date +%Y%m%d_%H%M%S).tar.gz
sudo tar -czf $datei /etc/ssh /opt/fhem/.ssh /home/pi/.ssh
sudo cp $datei /opt/fhem/backup/ssh.tar.gz
Falls der User fhem gar kein ssh verwendet kommt ein Fehler, dann muss man die Befehle abwandeln. Da die Dateien nicht voluminös sind, kann man auf zip verzichten. Damit kann das tar Archiv leichter mit der Option -rf ergänzt werden.
sudo tar -cf $datei /etc/ssh /home/pi/.ssh
cp $datei /opt/fhem/backup/ssh.tar
Der folgende Zweizeiler wirft zwar Fehler, sichert aber die ssh Dateien für alle existierenden User.
datei=ssh$(date +%Y%m%d_%H%M%S).tar
sudo tar rf $datei /etc/ssh $(awk -F: '$3>998{print $6"/.ssh"}' /etc/passwd)
Will man einfach in FHEM die ssh Key für user fhem sichern kann man das auch dort machen: 
"datei=./backup/sshFhem$(date +%Y%m%d_%H%M%S).tar;; tar -cf $datei ./.ssh"
Und auch später wiederherstellen:
"datei=./backup/sshFhem*.tar;; tar -xf $datei ./.ssh"

Sicherung Dateiserver Umgebung

datei=/opt/fhem/backup/fsconf$(date +%Y%m%d_%H%M%S).tar.gz
sudo tar -czf $datei /etc/fstab /usr/.*credentials /etc/davfs2/secrets
cp $datei /opt/fhem/backup/fsconf.tar.gz
Hat man Scripte o.ä. zusätzlich in anderen Pfaden, kann man die natürlich einfach in dem tar Befehl ergänzen und gleich mit sichern oder eine weitere Datei nach gleichem Schema erzeugen.

Finale Sicherung des alten Systems

Damit man den finalen Status im FHEM System behält, muss man FHEM beenden und dann ein backup durchführen!
systemctl stop fhem
datei=/opt/fhem/backup/FHEM-$(date +%Y%m%d_%H%M%S).tar.gz
tar -czf $datei -C /opt/fhem --exclude ./backup ./
Wenn das Backup zu Ende ist, startet man beispielsweise das Script /opt/fhem/backupFhem.sh und kopiert alle aktuellen Dateien damit auf den Sicherungsserver.
Am Ende des Artikels habe ich noch den Hinweis auf Alternativen zum Server.

Wiederherstellung

Nachdem das neue System grundlegend installiert und getestet ist (Ergänzung Schritt 5. optional: setupDavfs) wird die Sicherung zurückgespielt.
Die Verbindung zu meinem Windows Server geht relativ simpel. Ohne Angabe des Usernamens wird der momentane User (root) verwendet. Ohne Angabe des Passwortes wird es abgefragt:
sudo su
mount -t cifs //ServerName/Freigabe /mnt
mount -t cifs -o username=UserName //ServerName/Freigabe /mnt
mount -t cifs -o username=UserName,password=Passwort //ServerName/Freigabe /mnt
Ist das Laufwerk verbunden, kann auch ohne lokale Kopie sofort mit der Wiederherstellung begonnen werden.
datei=/mnt/fhem/$(hostname)/backup/FHEM-$(date +%Y%m%d_%H%M%S).tar.gz
tar -xzf $datei -C /opt/fhem
Querverweis: Falls die Module im alten System nicht gut dokumentiert waren, hilft eventuell dieser Beitrag.

Wiederherstellung ssh

Bei meinen Systemen wurde immer nur der ecsda Key verwendet. Ich möchte nicht die neue ssh Server Gesamtkonfiguration zerstören (da ändert sich immer mal was, ich habe nicht untersucht was genau) sondern nur das wiederherstellen was gebraucht wird. Unter der Annahme: es gibt nur eine aktuelle Datei im Verzeichnis, wird damit der ecdsa Key in /etc/ssh wieder hergestellt:
datei=/mnt/fhem/$(hostname)/backup/ssh.tar.gz
tar -xzf $datei -C / --wildcards etc/ssh/ssh_host_ecdsa_*
Um die User .ssh Verzeichnisse wieder herzustellen, sollte dieser Befehl gut sein
tar -xzf $datei -C / home/pi/.ssh/ opt/fhem/.ssh/
Damit steht die ssh Umgebung wieder.

Wiederherstellung Verbindungen zum Dateiserver

Die für die Verbindung verantwortlichen Scripte sollten im /opt/fhem Verzeichnis liegen und sind damit schon Teile der Wiederherstellung. Die Datei /etc/fstab und die jeweiligen Credential Dateien liegen aber unter Umständen "verstreut" im System und sind jetzt entweder nicht vorhanden oder neu gemacht.
Zunächst die credential Datei(en) in /usr/
datei=/mnt/fhem/$(hostname)/backup/fsconf.tar.gz
tar -xzf $datei -C / --wildcards usr/.*credentials
Die davfs2/secrets will ich nur ergänzen, davfs muss schon installiert sein!
tar -xzf $datei etc/davfs2/secrets -O|grep magentacloud >> /etc/davfs2/secrets
Ich möchte gern die fstab nicht überbügeln sondern die neue Datei einfach um meine Einträge ergänzen. Existieren z.B. mehrere Mountpoints in /media/ - kann das in einer Zeile erledigt werden:
tar -xzf $datei etc/fstab -O|grep /media/ >> /etc/fstab
Jetzt müssen noch die Mountpoint erzeugt werden:
mkdir $(tar -xzf $datei etc/fstab -O|grep -o '/media/[a-Z,0-9]*'|tr '\n' ' ')

Jetzt kann man das Sicherungslaufwerk wieder trennen.
umount /mnt
Die Verbindung zu davfs2 erfordert auch noch die Gruppenmitgliedschaft in der Gruppe davfs2
usermod -aG davfs2 fhem
Die Gruppenmitgliedschaft greift erst nach erneutem Login! Also mindestens FHEM neu starten!

Alternativen zum Sicherungsserver

USB Stick
Man könnte einen USB Stick verwenden, der Raspberry Pi 3 verkraftet das Stecken im laufenden Betrieb. Bei allen anderen Typen und "zur Vorsicht" ist aber vom Stecken eines USB Sticks im laufenden Betrieb abzuraten. Zuerst schauen welches Device der Stick geworden ist und dann einfach temporär mounten:
lsblk
mount /dev/sda1 /mnt

SCP - lokale Kopie
Mit scp ist es recht simpel sich eine Kopie der Dateien lokal zum holen und diese später zurück zu spielen. Geht auch unter Windows 10 ab 1809 einfach so!
scp pi@hostname:dateiname .
scp dateiname pi@hostname:
Die Restore Befehle muss man natürlich etwas abwandeln, entweder die Variable $datei wird einfach auf die lokale Datei Homedir von pi gesetzt, oder im tar Befehl die Datei direkt angeben.

Installation nodejs, pm2 und pip

Die Installation von nodejs hatte ich ja hier schon erläutert.
Zusätzlich brauche ich "global" noch den Nodejs Prozessmanager pm2.
sudo su
curl -sL https://deb.nodesource.com/setup_10.x | bash -
apt-get install nodejs
npm install pm2 -g
Trotz der Sicherung des gesamten Ordners installiere ich Landroid neu. Wie so oft gibt es mittlerweile neue Versionen.

Python Pakete werden mit Hilfe von pip installiert. Beispiel: die fhem API
apt-get install python-pip
pip install fhem


ToDo
Andere Applikationen unterhalb von /opt sichern

codebox

Mittwoch, 8. Mai 2019

Worx Landroid m500

Ich habe mir nach kurzer Überlegung und Klärung der Unterstützung in FHEM einen Mähroboter zugelegt. Nachdem ich die Installation und Einbindung in FHEM geschafft habe, will ich die mal hier festhalten. Als Grundlage gibt es einen kurzen Wikibeitrag. Dort sind zumindest mal die wichtigsten Links abgelegt. Ich habe die Installation etwas nach meinem Geschmack modifiziert, die Grundlage bildet aber diese Anleitung https://github.com/axelmohnen/fhem-landroid-s
Man sollte sich immer erst auf dieser Seite davon überzeugen, ob es Änderungen gibt!

Anmerkung: Im Herbst 2019 gab es mehrere Änderungen, sowohl im iobroker Modul als auch Modul LandroidSrv.js. Stand April 2020 habe ich die Beschreibung der Neuinstallation angepasst.

Die Voraussetzungen:
  • Cloudzugang eingerichtet und Mäher registriert. App funktioniert!
  • Installation Node.js, dieses Setup habe ich schon in einem extra Artikel beschrieben.

Setup Schritt für Schritt

Man sollte die Zeilen Schritt für Schritt ausführen und nicht einfach den Block als Script übernehmen.
sudo apt-get install libjson-pp-perl
sudo mkdir -p /opt/landroid/fhem-landroid-s
sudo chown -R pi /opt/landroid
sudo chmod -R 777 /opt/landroid
cd /opt/landroid/fhem-landroid-s
# Ich verzichte auf git und lade die beiden Datei einfach herunter
wget -O LandroidConf.json https://raw.githubusercontent.com/axelmohnen/fhem-landroid-s/master/LandroidConf.json
wget -O LandroidSrv.js https://raw.githubusercontent.com/axelmohnen/fhem-landroid-s/master/LandroidSrv.js
# bei der folgenden Installation gibt es ein paar Warnmeldungen, offenbar ohne Auswirkung
sudo npm install iobroker.worx
sudo npm install pm2 -g
# Nur in der alten Version: Ich korrigiere einen aktuellen kleinen Schönheitsfehler fürs Logging
# sed -i -e 's/self.mqtt)/self.mqtt_endpoint)/' node_modules/iobroker.landroid-s/lib/mqttCloud.js
# Jetzt den Cloudzugang eintragen und mit ctrl+o speichern und mit ctrl+x schließen
nano LandroidConf.json
Wenn man alles richtig gemacht hat, kann man jetzt schon mal auf zwei Arten testen:
node /opt/landroid/fhem-landroid-s/LandroidSrv.js mower1
# im Browser kann man sich den aktuellen Datensatz anzeigen lassen
http://ip-adresse-landroidsrv:8001/getMessage
Die Logausgaben sind selbsterklärend. Mit ctrl+c kann man den Testlauf abbrechen.

Automatischer Start

Auch mit der Node Prozess Steuerung kann man die Funktion testen
pm2 start /opt/landroid/fhem-landroid-s/LandroidSrv.js -f -- mower1
pm2 monit                           # zeigt Status
pm2 list                            # zeigt Prozesse
pm2 logs LandroidSrv [--lines 1000] # zeigt das Log
Sieht alles gut aus, kann man jetzt den Autostart aktivieren.
Diese Einrichtung war mir zunächst etwas schleierhaft. Aber - pm2 redet mit einem, und man muss genau lesen was er sagt.
Durch Eingabe von pm2 startup erhält man nach einer Analyse des Systems eine Zeile die man selbst noch einmal kopieren und in der Eingabezeile ausführen muss.
pm2 startup
[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u pi --hp /home/pi
Hat man die Zeile ausgeführt muss man die pm2 Konfiguration noch speichern.
pm2 save
Nach einem Neustart des Servers kann man sich davon überzeugen ob alles geklappt hat.
Achtung!
Ich habe mich mehrfach aus der AWS Cloud ausgesperrt! Offenbar ist es mir unbewusst gelungen, den LandroidSrv mehrfach zu starten und dies auch in pm2 zu verankern. Hast Du nicht gesehen, lief er 1x unter User pi und 2x unter User root. Wie ich das geschafft habe und was da genau abgeht weiß ich noch nicht. Mit ps
ps -aux|grep pm2
kann man schauen wie oft der Prozess läuft. Weiter unten habe ich geschrieben wie man alles wieder löschen kann.
pm2 legt für den angemeldeten User jeweils Prozesse an! Also nicht einmal als sudo und einmal als pi einrichten (hatte ich offenbar gemacht).

Setup FHEM

Zunächst brauchen wir das (derzeit nicht offizielle) Modul. Das laden wir direkt von GitHub an Ort und Stelle. Das notwendige Perl Modul haben wir oben als ersten Schritt schon installiert. Der Code ist entweder Zeilenweise für die FHEM Kommandozeile oder komplett für die Raw Definition.
"wget -nv -O FHEM/74_LANDROID.pm https://raw.githubusercontent.com/axelmohnen/fhem-landroid-s/master/74_LANDROID.pm"
reload 74_LANDROID.pm
define robbi LANDROID localhost
attr robbi event-on-change-reading .*
define FileLog_robbi FileLog ./log/robbi-%Y.log robbi
attr FileLog_robbi logtype text
Die ersten Reading sollten nach 180 sec erscheinen.

Noch Dies und Das

Zunächst die Installation aus der Readme vom GitHub in kurzer Zusammenfassung
sudo apt-get install git
cd /opt
sudo mkdir landroid
sudo chown pi landroid
sudo chmod 777 landroid
cd landroid
git clone https://github.com/axelmohnen/fhem-landroid-s.git
cd fhem-landroid-s
sudo npm install iobroker.worx
# bei der Installation gibt es ein paar Warnungen und eine
# npm WARN saveError ENOENT: no such file or directory, open '/opt/landroid/fhem-landroid-s/package.json'
# npm notice created a lockfile as package-lock.json. You should commit this file.
# Jetzt account eintragen
nano LandroidConf.json
#Teststart
node LandroidSrv.js mower1

Fehler beim Start

Wenn man sich im Argument vertippt hat (mover statt mower) kommt dieser Fehler
/opt/landroid/fhem-landroid-s/node_modules/iobroker.landroid-s/lib/mqttCloud.js:24
    this.email = adapter.config.email;
                                ^

TypeError: Cannot read property 'email' of undefined
    at new mqttCloud (/opt/landroid/fhem-landroid-s/node_modules/iobroker.landroid-s/lib/mqttCloud.js:24:33)
    at main (/opt/landroid/fhem-landroid-s/LandroidSrv.js:420:14)
    at Object. (/opt/landroid/fhem-landroid-s/LandroidSrv.js:458:3)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
    at startup (internal/bootstrap/node.js:283:19)
Ich hatte einen hartnäckigen Fehler, weil mich die Amazon Cloud offenbar gesperrt hatte (ich hatte durch einen Fehler eine Art Attacke gefahren). Abhilfe war: DSL neu verbinden.
undefined:1
<html>
^

SyntaxError: Unexpected token < in JSON at position 0
    at JSON.parse (<anonymous>)
    at IncomingMessage.<anonymous> (/opt/landroid/fhem-landroid-s/node_modules/iobroker.landroid-s/lib/mqttCloud.js:275:21)
    at IncomingMessage.emit (events.js:194:15)
    at endReadableNT (_stream_readable.js:1125:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)

Alles löschen

Der Abschnitt ist ein bisschen unsortiert. So einfach war ein Update bzw. Entfernen der Software nicht. Der iobroker.landroid-s / iobroker.worx wird unterhalb des aktuellen Pfades bei der Installation unter node_modules abgelegt.
Will man nur den Versuch mit pm2 beenden.
pm2 stop /opt/landroid/fhem-landroid-s/LandroidSrv.js
pm2 delete /opt/landroid/fhem-landroid-s/LandroidSrv.js
Wenn man alles wieder loswerden will:
pm2 unstartup
#alte Version
sudo npm uninstall iobroker.landroid-s
#neue Version
sudo npm uninstall iobroker.worx
#Nur Alte Version löschen
sudo rm -R node_modules/
sudo rm package-lock.json
#Alles löschen
cd /opt
sudo rm -R landroid/
#Auch nodejs löschen
sudo pm2 kill
sudo npm remove pm2 -g
sudo apt-get remove nodejs

Dienstag, 23. April 2019

Backup wenn der Server kommt

FHEM sichert ziemlich viel selbstständig, allerdings lokal innerhalb der Ordnerstruktur von FHEM. Geht beim Raspberry mal die SD Card kaputt, sollte man das Backup auf einem anderen System gesichert haben. Da bietet sich die NAS, der Windows Server oder was auch immer im lokalen Netzwerk an. Die Anbindung eines Windows/Samba Shares an den Raspberry hatte ich hier schon mal im Detail beschrieben.
Ich möchte zeigen, wie man bestimmte Ordner von FHEM auf ein temporäres Netzlaufwerk sichert und wie man diese Sicherung triggern kann.

Dienstag, 19. März 2019

Precence und OpenWrt - der 3. Versuch

Am Wochenende habe ich meinen OpenWrt Router WRT1900ACS mit neuer Firmware versorgt und dabei fiel dann relativ schnell auf, da war doch noch was...
Die beiden Varianten um festzustellen, ob bestimmte Geräte im Wlan angemeldet sind, die ich im Juni bzw. August 2018 schon mal getestet habe, haben einen ganz entscheidenden Nachteil: Sie überleben das Firmware Update nicht!
Ich hatte die Variante aus August zwar als Provisorium ohne Probleme laufen, aber so richtig zufrieden war ich damit sowieso nicht.
Der ssh Zugang zum OpenWrt Router überlebt das Firmware Update!
Deswegen eine neue Version. Ich beschreibe der Vollständigkeit halber an dieser Stelle auch noch einmal in Kurzfassung die Einrichtung des passwortlosen ssh Zuganges von fhem zum OpenWrt Router. Die Langfassung gab es ja schon in 2017.

Vorbemerkung

Das Tool ssh-copy-id kann nicht mit den speziellen Pfaden von dropbear (ssh OpenWrt) umgehen, deshalb kommt mein "Einzeiler" aus dem Artikel zum Einsatz.
Ich weiß, dass man prinzipiell die ssh Keys auch vom aktiven User auf den User fhem kopieren kann. Bei sicherheitsrelevanten Themen bin ich lieber für "ordentliches" und vielleicht etwas aufwendigeres Vorgehen.

ssh Zugang mit Public Key einrichten

Bitte diesen Artikel zur Einrichtung verwenden und dort in der Codebox den Abschnitt Ziel openwrt verwenden!

FHEM

Die Einrichtung in FHEM besteht aus einem Script und einem Presence Device.

Script

Meine Script liegen jetzt alle auch auf GitHub, wer das aktuelle Script einfach laden will kann das wie hier gezeigt tun, fhem braucht selbst nur Lese Rechte. Im Script muss normal nichts konfiguriert werden.
Dieser Befehl in der FHEM Kommandozeile lädt das Script ohne Umwege ins FHEM Verzeichnis:
"wget -O GetMacPresence.sh https://raw.githubusercontent.com/heinz-otto/scripts/master/Bash/GetMacPresence.sh"
Ich habe keinen anderen Benutzer in OpenWrt angelegt, sollte man aber vielleicht tun.
Das Script liefert lediglich, wie vom Modul gefordert, 0 oder 1 zurück, MAC Adresse und Routername werden als Parameter übergeben.
MAC=$1
host=$2
user="root"
ssh $user@$host '
    if (
      for m in $(
                 for w in $(iwinfo |grep -oE "wlan\d-\d|wlan\d")
                 do
                   iwinfo $w assoclist | grep -o -E "([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}"
                 done
                )
      do
        [ "$m" = '"$MAC"' ] && exit 0
      done
      exit 1
    )
  then
    echo 1
  else
    echo 0
  fi
'

Presence Device

Das Beispiel zeigt die einfachste Verwendung, neben der zu suchenden MAC Adresse wird noch der Name des OpenWrt Routers angegeben.
define WL_Dev1 PRESENCE shellscript "bash GetMacPresence.sh 11:22:33:AA:BB:CC wrt1900"

Erfahrung

Ein Problem tritt relativ schnell zu Tage: Man kann offenbar dieses Script nicht mehrfach starten, dann führt irgendeine Komponente zum Abbruch.

Dienstag, 5. März 2019

Eine weitere FHEM Instanz auf gleicher Hardware

Ein kleines HowTo für eine zweite FHEM Instanz.

1. Variante: Codeshare

Es gibt bei dieser Variante keine separaten Pfade - alles läuft in einem Pfad!
Lediglich eine neue fhem.cfg wird verwendet. Ein Update wirkt sich auf alle Instanzen aus!
Nur 3 Zeilen für die FHEM Kommandozeile - Achtung! Einzeln ausführen! FHEM startet über die Raw Definition mehrere Prozesse, da wäre die neue cfg noch nicht da wenn der sed Befehl losläuft:
  • Die erste Zeile holt eine Standard fhem.cfg vom SVN und speichert sie als fhem2.cfg. Die Original fhem.cfg wird nicht überschrieben!
  • Die zweite Zeile modifiziert diese fhem2.cfg:
    • Port 8093 statt 8083
    • ändert Namen für Logfile und Statefile sowie eventTypes, damit läuft diese Instanz zunächst unabhängig.
    • initialUsbCheck wird deaktiviert 
  • Die dritte Zeile startet diese neue Instanz, sie ist unter http://<hostname>:8093 erreichbar.
"wget -qO fhem2.cfg https://svn.fhem.de/fhem/trunk/fhem/fhem.cfg"
"sed -i -e 's/\/fhem/\/fhem2/;;s/\/eventTypes/\/eventTypes2/;;s/8083/8093/;;$aattr initialUsbCheck disable 1' fhem2.cfg"
"perl /opt/fhem/fhem.pl /opt/fhem/fhem2.cfg"

Achtung: Obwohl die Konfiguration separat ist, verwendet sie den gleichen Pfad wie die erste Instanz. Update, gleiche Namen innerhalb von Definitionen (Logdateien usw.) wirken sich auf beide Instanzen aus!

Ein shutdown der zweiten Instanz beendet diese, ein shutdown der ersten Instanz beendet beide Instanzen!

Mit einem notify in der ersten Instanz, kann man die zweite Instanz automatisch nach dem Start der ersten Instanz starten:
define n_StartFhem2 notify global:INITIALIZED "perl /opt/fhem/fhem.pl /opt/fhem/fhem2.cfg"

Damit läuft auch ein restart ohne Probleme: beide Instanzen werden nacheinander neu gestartet.
Beide Instanzen verwenden unterschiedliche Prozessorthreads/CPUs.

2. Variante: Rettungsweste

Wenn FHEM nicht mehr startet muss man Fehler suchen. Dazu ist es eigentlich nicht schlecht, mal eben "schnell" zu prüfen: Läuft es generell "ohne alles", was sagt das Log?
Der Ausgangszustand ist hier: FHEM läuft nicht! Wir bewegen uns im Terminal!

Erstmal prüfen, ob FHEM wirklich nicht läuft.
ps -aux|grep fhem
Prinzipiell passiert mit diesen Zeilen, das Gleiche wie in Variante 1, außer:
  • Nur der Name für den Statefile wird geändert, damit der Inhalt des originalen Statefile erhalten bleibt. 
  • Der Logfilename bleibt erhalten, man startet zwar ein  FHEM mit leerer cfg, hat aber den originalen Logfile im Zugriff.
n=3
cd /opt/fhem
sudo wget -qO fhem$n.cfg https://svn.fhem.de/fhem/trunk/fhem/fhem.cfg
sudo sed -i "s/\/fhem.save/\/fhem$n.save/;\$aattr initialUsbCheck disable 1" fhem$n.cfg
# oder auch das Port ändern
# sudo sed -i -e "s/8083/8093/" fhem$n.cfg
sudo chown fhem: fhem$n.cfg 
sudo perl fhem.pl fhem$n.cfg

Damit sieht/kann man:
  • ob FHEM startet (keine Problem mit dem Betriebssystem),
  • wie gewohnt das Logfile lesen. 
  • restore machen
  • diese Instanz ganz normal mit shutdown beenden

3. Variante: Völlig separat

In dieser Variante wird folgendes eingerichtet:
  • neues Port, 
  • der existierende /opt/fhem Pfad wird ohne die aktuellen Daten in einen neuen Pfad /opt/fhemN kopiert, 
  • neuer zweiter FHEM Service, 
  • neue leere fhem.cfg wird verwendet.
Ein installiertes FHEM  mit Systemd ist Voraussetzung!

Das Script / die Befehle alle im sudo Kontext (sudo su) ausführen!
#!/bin/bash
# Nummer und Port vergeben
n=4
fPort=8103

# aktiven Pfad kopieren, aktuelle Daten ausschließen
rsync -av --exclude={'log/*','restoreDir/*','cache','backup'} /opt/fhem/ /opt/fhem$n/

# fhem Service kopieren, anpassen und anlegen
cp /etc/systemd/system/fhem.service /etc/systemd/system/fhem$n.service
sed -i -e "s/\/fhem/\/fhem$n/" /etc/systemd/system/fhem$n.service
systemctl daemon-reload
systemctl enable fhem$n

# Leere cfg holen und anpassen
wget -qO /opt/fhem$n/fhem.cfg https://svn.fhem.de/fhem/trunk/fhem/fhem.cfg
sed -i -e "s/8083/$fPort/;\$aattr initialUsbCheck disable 1" /opt/fhem$n/fhem.cfg

#Besitzer setzen
chown -R fhem: /opt/fhem$n/

# Service starten
systemctl start fhem$n

Achtung, das kopiert und modifiziert die Version von fhem.cfg und fhem.service vom Entwicklungsstand heute! Wenn sich in Zukunft etwas ändert, muss dieser Code angepasst werden!

Will man alles wieder entfernen:
Bitte Vorsicht!
# zur Sicherheit nochmal n setzen
n=4

# Entferne den Dienst
systemctl stop fhem$n
systemctl disable fhem$n
systemctl daemon-reload
systemctl reset-failed
echo 'Mit y oder Y bestätigen'
rm -i /etc/systemd/system/fhem$n.service

#Entferne den Pfad
echo 'Mit y oder Y bestätigen'
rm -I -R /opt/fhem$n

Mittwoch, 20. Februar 2019

FHEM HTTP Client

Der eingebaute Client Modus in der fhem.pl funktioniert nur über die Telnet Schnittstelle von FHEM, diese wird aber per default nicht gar nicht mehr definiert.
Ich habe mal in 3 Varianten einen FHEM Client gebaut: als Bash-, Powershell- und Perlscript.

Der FHEM Client verfügt einheitlich über folgende Möglichkeiten:
  • Angabe komplette URL oder nur Portnummer (lokaler Zugriff) [http://<Username>:<Password>@<hostName>:]<portNummer>
    • Zugriff über Standard WEB sofort nach FHEM Installation möglich, csrf Token wird verwendet.
    • Angabe von Basic Auth in der URL möglich.
  • FHEM Befehle als:
    • Argument(e) (analog fhem.pl)
    • Dateiname der Befehlsdatei mit Codeschnipseln (z.B. ganz oder teilweise fhem.cfg).
    • Zeilen über die Pipeline (cat Befehlsdatei | fhemcl 8083)
    • Mehrzeilige Definitionen (Zeilenende mit "\")werden verarbeitet.
  • Ausgabe der FHEM Antwort, z.B. bei list Befehlen, analog zum Client Modus in fhem.pl
  • Kurze Hinweise zur Verwendung
Getestet habe ich die Bash und Perl Scripts unter Raspbian, das Powershell Script unter Windows 10. Mit Sicherheit ist es noch nicht völlig frei von bugs! Verwendung also auf eigene Gefahr!

Verwendung

Ich habe alle Scripte auf GitHub abgelegt und halte sie dort auch aktuell.
Am einfachsten kann man sich die Scripts per Download auf das System holen, das geht ziemlich einheitlich (auch unter Windows/Powershell) mit wget:
wget -OutFile fhemcl.ps1 https://raw.githubusercontent.com/heinz-otto/fhemcl/master/fhemcl.ps1
wget -O fhemcl.pl https://raw.githubusercontent.com/heinz-otto/fhemcl/master/fhemcl.pl
wget -O fhemcl.sh https://raw.githubusercontent.com/heinz-otto/fhemcl/master/fhemcl.sh
Anmerkung. -OutFile anstatt -O braucht man auf älteren Windows Systemen.

Allgemeiner Ablauf

  1. Es wird zunächst das erste Argument getestet, dies muss vorhanden sein. Es erfolgt ein Test ob nur die Portnummer angegeben wurde, diese wird um localhost erweitert ansonsten wird die url ohne weitere Prüfung übernommen. 
  2. Bei Powershell wird aus username:password noch ein extra "Credential" String gemacht, da Invoke-Webrequest die url nicht einfach so verarbeitet wie curl.
  3. Der csrfToken wird extrahiert und gespeichert.
  4. Dann wird ein Array mit den FHEM Befehlen gebildet, dazu wird 
    • die Pipeline getestet und gelesen
    • das zweite Argument gelesen und auf Dateiname getestet
    • entweder die Datei oder weitere Argumente eingelesen 
  5. Zum Schluss wird eine Schleife über das Befehlsarray abgearbeitet, die Befehle url-encoded  und an den FHEM Server übergeben und die Antwort ausgewertet.
  6. Wieder was dazu gelernt: Wenn man an den HTTP Aufruf &XHR=1 anhängt wird nur Text und kein HTML ausgeliefert. Von der FHEM Antwort wird der Inhalt zwischen <div id='content' > und </div> ausgefiltert und von überflüssigen <pre>/</pre> Zeilen und HTML Tags befreit. Die Ausgabe entspricht der des fhem.pl Clients bzw. der sichtbaren Ausgabe im Browser Fenster.

Die Scripts

Der folgende Code ist Stand der Veröffentlichung. Bitte unbedingt nach aktuellem Code im GitHub schauen!
Bash Variante
#!/bin/bash
# Heinz-Otto Klas 2019
# send commands to FHEM over HTTP
# if no Argument, show usage

if [ $# -eq 0 ]
then
     echo 'fhemcl Usage'
     echo 'fhemcl [http://<hostName>:]<portNummer> "FHEM command1" "FHEM command2"'
     echo 'fhemcl [http://<hostName>:]<portNummer> filename'
     echo 'echo -e "set Aktor01 toggle" | fhemcl [http://<hostName>:]<portNumber>'
     exit 1
fi

# split the first Argument
IFS=:
arr=($1)

# if only one then use as portNumber
# or use it as url
IFS=
if [ ${#arr[@]} -eq 1 ]
then
    if [[ `echo "$1" | grep -E ^[[:digit:]]+$` ]]
    then
        hosturl=http://localhost:$1
    else
        echo "$1 is not a Portnumber"
        exit 1
    fi
else
    hosturl=$1
fi

# get Token 
token=$(curl -s -D - "$hosturl/fhem?XHR=1" | awk '/X-FHEM-csrfToken/{print $2}')

# reading FHEM command, from Pipe, File or Arguments 
# Check to see if a pipe exists on stdin.
cmdarray=()
if [ -p /dev/stdin ]; then
        echo "Data was piped to this script!"
        # If we want to read the input line by line
        while IFS= read -r line; do
              cmdarray+=("${line}")
        done
else
        # Checking the 2 parameter: filename exist or simple commands
        if [ -f "$2" ]; then
            echo "Reading File: ${2}"
            readarray -t cmdarray < ${2}
        else
        echo "Reading further parameters"
        for ((a=2; a<=${#}; a++)); do
            echo "command specified: ${!a}"
            cmdarray+=("${!a}")
        done
        fi
fi

# loop over all lines stepping up. For stepping down (i=${#cmdarray[*]}; i>0; i--)
for ((i=0; i<${#cmdarray[*]}; i++));do 
    # concat def lines with ending \ to the next line
    cmd=${cmdarray[i]}
    while [ ${cmd:${#cmd}-2:1} = '\' ];do 
          ((i++))
          cmd=${cmd::-2}$'\n'${cmdarray[i]}
    done
    echo "proceeding Line $i : "${cmd}
    # urlencode loop over String
    cmdu=''
    for ((pos=0;pos<${#cmd};pos++)); do
        c=${cmd:$pos:1}
        [[ "$c" =~ [a-zA-Z0-9\.\~\_\-] ]] || printf -v c '%%%02X' "'$c"
        cmdu+="$c"
    done
    cmd=$cmdu
    # send command to FHEM and filter the output (tested with list...).
    # give only lines between, including the two Tags back, then remove all HTML Tags 
    curl -s --data "fwcsrf=$token" $hosturl/fhem?cmd=$cmd | sed -n '/<pre>/,/<\/pre>/p' |sed 's/<[^>]*>//g'
done

Perl Variante
#!/usr/bin/env perl
# Heinz-Otto Klas 2019
# send commands to FHEM over HTTP
# if no Argument, show usage

use strict;
use warnings;
use URI::Escape;
use LWP::UserAgent;

my $token;
my $hosturl;
my $fhemcmd;

 if ( not @ARGV ) {
     print 'fhemcl Usage',"\n";
     print 'fhemcl [http://<hostName>:]<portNummer> "FHEM command1" "FHEM command2"',"\n";
     print 'fhemcl [http://<hostName>:]<portNummer> filename',"\n";
     print 'echo -e "set Aktor01 toggle" | fhemcl [http://<hostName>:]<portNumber>',"\n";
     exit;
 }

if ($ARGV[0] !~ m/:/) {
   if ($ARGV[0] eq ($ARGV[0]+0)) { # isnumber?
       $hosturl = "http://localhost:$ARGV[0]";
   }
   else {
       print "$ARGV[0] is not a Portnumber";
       exit(1);
   }
}
else {
    $hosturl = $ARGV[0];
}

# get token 
my $ua = new LWP::UserAgent;
my $url = "$hosturl/fhem?XHR=1/";
my $resp = $ua->get($url);
   $token = $resp->header('X-FHEM-CsrfToken');

my @cmdarray ;

# test the pipe and read 
if (-p STDIN) {
   while(<STDIN>) {
       chomp($_);
       push(@cmdarray,$_);
   }
}
# second Argument is file or command?
if ($ARGV[1] and -e $ARGV[1]) {
    open(DATA, '<', $ARGV[1]);
    while(<DATA>) {
       s/\r[\n]*/\n/gm;      #remove any \r 
       chomp($_);
       push(@cmdarray,$_);
    }
    close(DATA);
}
else {
    for(my $i=1; $i < int(@ARGV); $i++) {
    push(@cmdarray, $ARGV[$i]);
    }
}
#execute commands and print response from FHEMWEB 
for(my $i = 0; $i < @cmdarray; $i++) {
    # concat def lines with ending \ to the next line
    my $cmd = $cmdarray[$i];
    while ($cmd =~ m/\\$/) {
        $i++;
        $cmd = substr($cmd,0, -1)."\n".$cmdarray[$i];
    };
    # url encode the cmd
    $fhemcmd = uri_escape($cmd);
    print "proceeding line $i : $fhemcmd\n";
    $url = "$hosturl/fhem?cmd=$fhemcmd&fwcsrf=$token";
    $resp = $ua->get($url)->content;
    # only between the lines <pre></pre> and remove any HTML Tag
    #funktioniert noch nicht sauber bei massenimport
    my @resparray = split("\n", $resp);
    foreach my $zeile(@resparray){
        if ($zeile !~ /<[^>]*>/ or $zeile =~ /pre>/ or $zeile =~ /NAME/) {
           $zeile =~ s/<[^>]*>//g;
           print "$zeile\n" ;
        }
    }
}

Powershell Variante
<#
.SYNOPSIS
    This Script is a FHEM Client for HTTP
.DESCRIPTION
    FHEM commands could given over the Pipe, Arguments or File.
.EXAMPLE
    fhemcl [http://<hostName>:]<portNummer> "FHEM command1" "FHEM command2"
    fhemcl [http://<hostName>:]<portNummer> filename
    echo "FHEM command"|fhemcl [http://<hostName>:]<portNummer>
.NOTES
    put every FHEM command line in ""
#>
#region Params
param(
    [Parameter(Mandatory=$true,Position=0,HelpMessage="-first 'Portnumber or URL'")]
    [String]$first,
    [Parameter(ValueFromPipeline=$true,ValueFromRemainingArguments=$true)]
    [String[]]$sec
)
#endregion 

# if only one element the use as portNumber
# or use as hosturl
$arr = $first -split ':'
if ($arr.Length -eq 1){
   if ($first -match '^\d+$') {$hosturl="http://localhost:$first"}
       else {
           write-output "is not a Portnumber"
           exit
            }
 } else {$hosturl=$first}
# url contains usernam@password?
if ($arr.Length -eq 4){
     $username = $($arr[1] -split'//')[1]
     $password = $($arr[2] -split '@')[0]
     $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
     $headers = @{
     Authorization=("Basic {0}" -f $base64AuthInfo)
     }

     # cut the account from hosturl 
     $hosturl=$arr[0] + "://"+$($arr[2] -split '@')[1] +":" + $arr[3]
}
# get Token
$token = Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri "$hosturl/fhem?XHR=1" | %{$_.Headers["X-FHEM-csrfToken"]}

# reading commands from Pipe, File or Arguments 
# clear cmdarray and save the Pipeline,
# $input contains all lines from pipeline, $sec contains the last line
$cmdarray=@()
foreach ($cmd2 in $input){$cmdarray += $cmd2}
if ($cmdarray.length -eq 0) {
     if((Test-Path $sec) -And ($sec.Length -eq 1)) {$cmdarray = Get-Content $sec} 
     else {foreach ($cmd2 in $sec){$cmdarray += $cmd2}}
}
# send all commands to FHEM
# there is still an error message with Basic Auth and commands like set Aktor01 ..  e.g. list is without any error.

for ($i=0; $i -lt $cmdarray.Length; $i++) {
   # concat def lines with ending \ to the next line
   $cmd = $cmdarray[$i]
   while($cmd.EndsWith('\')) {$cmd=$cmd.Remove($cmd.Length - 1,1) + "`n" + $cmdarray[$i+1];$i++}
   write-output "proceeding line $($i+1) : $cmd"
   # url encode
   $cmd=[System.Uri]::EscapeDataString($cmd)
   $web = Invoke-WebRequest -Uri "$hosturl/fhem?cmd=$cmd&fwcsrf=$token" -Headers $headers
   if ($web.content.IndexOf("<pre>") -ne -1) {$web.content.Substring($web.content.IndexOf("<pre>"),$web.content.IndexOf("</pre>")-$web.content.IndexOf("<pre>")) -replace '<[^>]+>',''}
}

Benchmark/Leistung

Ich habe mal Schleifen mit 10 gleichen Befehlen (set Aktor01 toggle) abgesetzt um zu sehen ob es große Unterschiede bei den realisierten Scripts gibt.

time for ((i=0; i<10; i++));do perl fhemcl.pl http://raspib:8083 "set Aktor01 toggle";done
time for ((i=0; i<10; i++));do bash fhemcl.sh http://raspib:8083 "set Aktor01 toggle";done
Measure-Command {for ($i=1; $i -le 10; $i++) {.\fhemcl.ps1 http://raspib:8083 "set Aktor01 toggle"}}
Die Bash und Powershell Variante läuft etwa gleich schnell ab und braucht für die 10 Durchläufe etwas über 3 sec. Die Perl Variante benötigt je nach Platform (Pi1/Pi2/Pi3) 2 bis 10 mal solange.

Die Telnet Schnittstelle ist wesentlich schneller! Nimmt man die obige Schleife und schickt sie remote an einen Pi3, dann braucht diese Schleife noch 0.5 Sekunden. Lokal auf einem PiB ausgeführt, sind die Unterschiede wieder minimal.
time for ((i=0; i<10; i++));do echo "set Aktor01 toggle"|nc raspib3plus 7072;done
Telnet ist schnell definiert, und wenn man es nicht mehr braucht wieder gelöscht.
define telnetPort telnet 7072 global