fhem.js - websocket connection to fhem via node.js proxy

Begonnen von Werner Schäffer, 13 Februar 2015, 21:53:55

Vorheriges Thema - Nächstes Thema

Werner Schäffer

(Dokumentation und Download auf Github: https://github.com/winne27/fhem.js)

Beschreibung

Bei dem hier vorgestellten Proxy handelt es sich um einen node.js Server, der zum einen mit einem klassischen FHEM-Server kommuniziert und zum anderen Clients wie Internet Browsern oder Apps unter Android oder iOs ermöglicht sich mit FHEM über Websockets zu verbinden. 

Technik:

  • dies ist ein node.js Server (ab jetzt genannt fhem.js)
  • ein klassischer fhem Server ist Voraussetzung (ab jetzt genannt fhem.pl)
  • fhem.js kommuniziert über den telnet Port 7072 mit fhem.pl. Auf dem Server muss deshalb auch telnet installiert sein und fhem.pl muss so konfiguriert sein, dass über den Port 7072 von localhost eine Verbindung ohne Passwort möglich ist.
  • fhem.js puffert alle Messwerte des fhem.pl Servers und schickt bei Änderung eines Messwertes oder eines Stati, und nur dann, mit Hilfe des Websocket-Protokolls eine Nachricht an alle angeschlossenen Clients die diesen Wert adoptiert haben

Warum diese Lösung?

  • Beobachtung: die websocket Integration in fhem stockt scheinbar (keine neuen Beiträge, tote Links)
  • Vorlieben: ich kenne Perl, aber ich kann Javascript
  • Feststellung: node.js wurde konzipiert um websockets abzuwickeln - Perl, aber auch PHP und Andere, integrieren dieses Konzept irgendwie
  • Erfahrung: sowohl in Browsern als auch in Apps lassen sich problemlos Verbindungen zu node.js Servern aufbauen

Warum überhaupt websockets

  • Sarkastisch: um den websockets Propheten endlich mal ein Beispiel abseits der "Chatroom-Beispiele" zu liefern.
  • Euphorisch: um aktuelle Messwerte, egal ob die Zimmertemperatur in der Eckkammer oder die Stärke des Sonnenwindes, auf einer Website oder in einer App aktuell darzustellen, sind websockets eine wahre ... lassen wir die Kirche im Dorf und sagen maßgeschneidert.

Nachtrag vom 18.12.2015:

Installation node.js

Eine zwingende Voraussetzung für diese Anwendung ist eine aktuelle Version von node.js. In den letzten zwei Jahren gab es bezüglich node.js ein ziemliches Versions Chaos. Zum einen hatte sich ein Fork genannt io.js gebildet und zum anderen wurden die beiden Stränge dann wieder zusammengeführt. node.js machte dann auch einen Versionssprung von 0.12 auf 4.0. Viele Linux Distributionen installieren über ihren Package-Manager (z.B. apt-get) aber häufig immer noch die Version 0.12 oder noch älter. Mit node -v kann man übrigens die installierte Version herausfinden. Ergibt sich dabei ein Fehler oder eine Version <= 0.12 sollte erst einmal mal node.js komplett deinstalliert werden mit

apt-get remove nodejs
apt-get remove npm
apt-get remove node


Am Besten alle drei Befehle ausführen damit es ganz sicher weg ist.

Jetzt node.js neu installieren mit:

# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs


# Using Debian, as root
curl -sL https://deb.nodesource.com/setup_8.x | bash -
apt-get install -y nodejs

Ausführliche Infos zur Installation von node.js siehe https://github.com/nodesource/distributions

Installation von fhem.js

Die Installation von fhem.js einschließlich aller Submodule geht nun ganz einfach:

sudo npm install --unsafe-perm -g fhem.js

Wer Optionen wie unsafe-perm nicht traut, kann diese Option auch weglassen. Allerdings muss dann anschließend die Postinstallation manuell gestartet werden mit:

sudo /usr/lib/node_modules/fhem.js/bin/postinstall.sh

Die zweite Methode bietet die Möglichkeit das Postinstallscript usr/(local/)lib/node_modules/fhem.js/bin/postinstall.sh zu prüfen, bevor es mit Root-Rechten ausgeführt wird.

Egal welche Methode man wählt, wird während des Post-Installation Prozesses ein User abgefragt mit dem fhem.js ausgeführt werden soll. Als Default wird fhem vorgeschlagen.

Konfiguration

Nach der Installation kann in /etc/fhem.js/params.js der Server konfiguriert werden. Dort kann z.B. ein Verbindungs-Passwort definiert werden, SSL eingeschaltet werden, usw. Die Konfigurationsparameter sind in dieser Datei ausführlich kommentiert. Eine weiterführende Dokumentation zu der Konfiguration und zu den Möglichkeiten von fhem.js findet sich im Link des nächsten Absatzes.

Software:

Die Software von fhem.ja ist Open Source und ist hier zu finden:

https://github.com/winne27/fhem.js

Beispiel FHEMswitch

FHEMswitch ist ein werbefreies Android Widget für benutzerdefinierte FHEM-Shortcuts auf dem Android-Startscreen und ist im Google Store kostenlos erhältlich.  Diese App benötigt den hier beschriebenen fhem.js Server. Auch zu dieser App gibt es hier im Forum einen Thread:

http://forum.fhem.de/index.php/topic,36824.90.html

Phill

Hi,
ja websockets sind eine super Sache. Ich wusste gar nicht das es schon versuche gab das in fhem zu integrieren.

Ich habe bei mir eine kleine node.js Zentrale geschrieben, mit einem recht ähnlichem konzept. Ich finde deinen Ansatz sehr gut, leider kann ich es nicht ohne weiteres bei mir einsetzen.

Bei der Telnet-Verbindung mit "inform on" werden nur die geänderten state's mitgeteilt oder? Das ist irgendwie ein manko da ich auch Readings an den Client schicke.
Das ist bei mir etwas umstandlich gelöst indem fhem eine Telnet-Verbindung mit dem node.js Server aufbaut und die Daten rüber schiebt.

Es hätte natürlich einige Vorteile mit der kompletten Datenstruktur von fhem in fhem.js, macht es aber auch wesentlich komplizierter. und ob es unbedingt notwendig ist weiß ich auch noch nicht. Muss mal noch etwas darüber nachdenken, ist schon spät.

Gruß.
Homebrew 1-Wire / HomeMatic Mix - Cubietruck mit FHEM als Server - Raspberry PI 3 als Informationsanzeige im MagicMirror Stil - Raspberry Pi 1 als Klingelanlage - VDR

Mein Modul: Talk2Fhem - Mein Tipp: https://forum.fhem.de/index.php/topic,82442.0.html

peterk_de

Nach einem ersten sehr  kurzen Blick und um ein bisschen in deinem Stil zu bleiben:


  • Sehr geil. Websockets gehören eigentlich als Standard-Modul in FHEM genau wie der telnet-Port :-)
  • Socket.io scheint hierfür ja ganz fluffig zu sein.
  • Aaaaaaber es gibt ja da noch JSON-RPC - z.B. per https://www.npmjs.com/package/node-json-rpc ... was hältst du n davon? Das erscheint mir so n bissel besser erweiterbar und Standardisierter (benannte Parameter, Batch Calls, ...) und sogar das was du mit getValuePerm gemacht hast ist da sozusagen standardisiert (Notifications ohne Antwort in beide Richtungen) ...  oder liege ich da falsch und socket.io - das ich nicht kenne - kann das alles genauso "sauber"?

Auf jeden Fall schön zu sehen dass hier auch noch andere websocket-Fans sind ;)
FHEM auf Ubuntu-VM / 2xNUC Proxmox Cluster
UI: HomeKit, TabletUI, Grafana
IOdevs: 2xHueBridge, RaspiMatic-CCU, CUL868, 2xHarmonyHub, 6xRaspi-Roomnode mit CO2, VOC und lepresenced
Devices: 107xHomematic(IP), 96xPhilips Hue, 17xTECHEM, 12xBTLE, 8xSONOS, 2xHomeConnect, 1xShelly 3em, 1xNanoleaf ...

marvin78

Wenn ihr WebSocket Fans seid, schaut euch mal Fronthem an (im Frontends Bereich). Das ist eine Websocket Schnittstelle an FHEM um beliebige Frontends mit FHEM verbinden zu können.

Werner Schäffer

Zitat von: peterk_de am 19 Februar 2015, 23:57:38

...

  • Aaaaaaber es gibt ja da noch JSON-RPC - z.B. per https://www.npmjs.com/package/node-json-rpc ... was hältst du n davon? Das erscheint mir so n bissel besser erweiterbar und Standardisierter (benannte Parameter, Batch Calls, ...) und sogar das was du mit getValuePerm gemacht hast ist da sozusagen standardisiert (Notifications ohne Antwort in beide Richtungen) ...  oder liege ich da falsch und socket.io - das ich nicht kenne - kann das alles genauso "sauber"?
...

Ich kenne JSON-RPC nicht. Habe eben mal kurz einen Blick darauf geworfen und mir ist nicht ganz klar wie da die Kommunikation stattfindet, wird da mit Polling gearbeitet oder mit Websockets?

Egal, ich arbeite mit socket.io weil ich damit gute Erfahrungen gemacht habe. socket.io ist die gängiste Implementierung von websockets in node.js. Außerdem gibt es Client Pakete für Javascript (Browser) und Java (Android). Mit Beiden habe ich schon erfolgreich, zugegebenermaßen nach hartem Programmierkampf,  Websites,  Apps und Android Widgets mit Realtime-Daten versorgt.






Werner Schäffer

Zitat von: Phill am 19 Februar 2015, 23:17:51
...
Bei der Telnet-Verbindung mit "inform on" werden nur die geänderten state's mitgeteilt oder? Das ist irgendwie ein manko da ich auch Readings an den Client schicke.
Das ist bei mir etwas umstandlich gelöst indem fhem eine Telnet-Verbindung mit dem node.js Server aufbaut und die Daten rüber schiebt.

Es hätte natürlich einige Vorteile mit der kompletten Datenstruktur von fhem in fhem.js, macht es aber auch wesentlich komplizierter. und ob es unbedingt notwendig ist weiß ich auch noch nicht. Muss mal noch etwas darüber nachdenken, ist schon spät.
...

Ich versuch mal die Abläufe in fhem.js etwas besser zu erläutern:

Verbindung zum Server fhem.pl

  • beim Start des node.js Servers fhem.js wird eine telnet Verbindung zu fhem.pl aufgebaut die permant erhalten bleibt (bisher ohne Problem bei mir auf einem Banana Pi)
  • es werden alle Status-Daten abgefragt und im fhem.js gespeichert (mit fhem-Befehl list)
  • mit dem fhem-Befehl "inform on" werden Statusänderungen vom fhem.pl Server angefordert
  • sendet fhem.pl einen neuen Status so wird der in den Puffer geschrieben und es wird geprüft ob es Clients gibt die diesen Wert aboniert haben, falls ja wird er automatisch ausgeliefert.
  • vom Client empfangene Befehle werden an fhem.pl weitergeleitet und, je nach client-seitiger Anforderung, wird die Antwort von fhem.pl zurückgegeben.

Verbindung vom Clinet zum fhem.js Server

  • der Client kann eine websocket-Verbindung zu fhem.js mittels  des Paketes socket.io (Javascript, Java, ...) aufbauen
  • der Client kann fhem-Values anfordern: sie werden einmalig geliefert
  • der Client kann fhem_Values abonieren: der aktuelle Wert wird geliefert und jegliche Änderung des Wertes wird über Websockets geliefert
  • der Client kann fhem-Commands absetzen und auf eine Antwort warten (socket.io.ack) oder auch nicht

Warum node.js Server?

Dieses ganze Konstrukt ist natürlich nur eine Hilfslösung. Das Ziel muss sein websockets-Verbindungen direkt zum fhem.pl Server zu ermöglichen. Ich habe bisher dazu keine funktionierente Lösung gefunden und deshalb diesen node.js websocket-proxy zu fhem.pl erstellt.


HansDampfHH

Könntest du mir vielleicht noch einmal die genaue Anwendung auf der Clientseite darstellen?
Ich befasse mich erst kurz mit node.js. Bisher läuft das aber mit einem einfachen websocket Tutorial.

Nun wollte ich Dein Script nutzen.
In der params.js habe ich erst einmal ssl und passwort auf false gestellt.

// port on which node.js service is reachable
exports.nodePort = 8091;

// telnet port of FHEM server
exports.fhemPort = 7072;

// path for Webfiles (html,css,js, ...) !! no php !!
// change to path of web directory only if you want to deliver
// web files by this server
// set to false else
exports.pathHTML = false;
//exports.pathHTML = '/var/www/test';

// default html page
//exports.indexHTML = 'index.html';

// use SSL for conversation (true/false)
exports.useSSL = false;

// use connection password (true/false)
// it is recommended to use this only if useSSL is also true
// else the password is send as plain text
exports.useClientPassword = false;

// sha-256 hashed password
// create it on Linux shell with
// echo -n "mein Passwort" | sha256sum | cut -d' ' -f1
exports.connectionPassword = 'addb0f5e7826c857d7376d1bd9bc33c0c544790a2eac96144a8af22b1298c940';

// location of SSL and client-auth certificats
// only used then useSSL and/or useClientAuth set to true
exports.sslcert =
{
   key:    '/etc/ssl/private/bundle/ssl.key',
   cert:   '/etc/ssl/private/bundle/allcert.pem',
}
exports.cipher = 'HIGH:!aNULL:!MD5';


Die Client.html sieht so aus:


<html>
    <head>
    </head>
    <body>
        <time></time>
        <div id="container">empty</div>
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
        <script>
        // creating a new websocket
        var socket = io.connect('http://192.168.148.32:8091');
   
        socket.on('getAllValues', function(data){
for (unit in data){
var value = data[unit];
$('#container').html(value);
}
});
        </script>
    </body>
</html>


Ziel wäre es am Ende einfach die veränderten Werte aus FHEM zu übermitteln.
FHEM Docker, CUL868, Zigbee, CCU2, Jeelink

Werner Schäffer

Entschuldigung dass ich jetzt erst antworte, aber ich hatte die Benachrichtigung nicht eingeschaltet.

Auf Client-Seite muss man das auf andere Weise angehen. Auf githup habe ich das aber auch nicht gut dokumentiert. Da muss ich nochmals ran.




        <script>
        // creating a new websocket
        var socket = io.connect('http://192.168.148.32:8091');
   
        socket.emit('getAllValues', function(data){
for (unit in data){
var value = data[unit];
$('#container').html(value);
}
});
        </script>



Also socket.emit statt socket.on!

HansDampfHH

Danke für Deine Antwort.
Nachdem ich Dein aktuelles GIT Repo noch einmal geladen habe funktioniert das Beispiel mit getAllValues.
Schon mal super :-)

Könntest du mir vielleicht noch mit der Option getValueOnChange auf die Sprünge helfen?
Das wäre die Option mit der ich gerne arbeiten würde.

Wenn ich das nun so nutze passiert da leider nichts nach einer Aktion in FHEM:
socket.emit('getValueOnChange', function(data){
FHEM Docker, CUL868, Zigbee, CCU2, Jeelink

Werner Schäffer

schicke dem

socket.emit('getValueOnChange')

ein

socket.on('value', function(data){ ..

hinterher.

Das Erste ist die Beauftragung (emit) eines Dienstes,
das Zweite ist ein Listener (on)


Das fehlt noch in der Doku:

Es gibt synchrone (z.B. getAllValues) und asychrone (z.B. getValueOnChange) Anfragen bei einem node.js Server:

Synchron:



socket.emit('anfrage',function(response)
{
     // response auswerten
})



Asynchron:



socket.emit('anfrage');

socket.on('antwort',function(response)
{
     // response auswerten
})





HansDampfHH

Vielen Dank für Deine Unterstützung.
Soweit habe ich verstanden was es mit emit und on auf sich hat.

Wenn ich das ganze nun so umbaue tut sich allerdings nichts.
Zumindest kann ich im Browser auf Netzwerkebene keine Aktivität erkennen.
Da kommt scheinbar nichts zurück!?

socket.emit('getValueOnChange');
socket.on('value', function(data){
for (unit in data){
   var value = data[unit];
}
FHEM Docker, CUL868, Zigbee, CCU2, Jeelink

Werner Schäffer

Meine Antwort gestern war leider nicht ganz korrekt. Habe ich auch nach dem Sportschaubier geschrieben.  :o

Asynchron:



socket.emit('anfrage','fhem-device-name');

socket.on('antwort',function(response)
{
     // response auswerten
})



Schau doch auch auf https://github.com/winne27/fhem.js. Dort habe ich die Doku angepasst. Vor allem die Unterschiede zwischen asyn und sync Request wurden deutlicher dargestellt.

HansDampfHH

Vielen Dank für Deine Mühe !
Die Option getValueOnChange fände ich ganz hilfreich wenn Sie Änderungen an irgendeinem Device meldet.
Wenn ich dich richtig verstehe muss bzw. kann ich aber nur ein getValueOnChange an EIN Device hängen, oder?

Ich würde gerne Deine Lösung auf dem Server laufen lassen. Bei irgendeiner Änderungen eines Lichtschalters oder eines Rolladen sollte node diese Änderungen per Socket an den Client pushen damit ich dort mit diesem Value weiter arbeiten kann, z.B.:
{'Wohnzimmer_Licht', ON}

Bekomme ich so etwas mit Deinem Ansatz umgesetzt?
Ansonsten bleibt mir wohl nichts anderes übrig in einem Intervall (z.B. alle 5 Sekunden) mit getAllValues zu arbeiten.
FHEM Docker, CUL868, Zigbee, CCU2, Jeelink

Werner Schäffer

Bisher war es so wie du feststellst:
ein getValueOnChange liefert nur den Wert einer Device. Man kann diesen Befehl natürlich auch mehrfach für verschiedene Devices absetzen. So habe ich das in einer Android Anwendung gemacht. Dort werden nicht alle Werte angezeigt sondern nur ausgewählte und auch nur die werden vom node.js-Server abonniert um die Menge übertragener Daten möglichst klein zu halten. Bewegt man sich nur im Hausnetz ist diese Knausrigkeit nicht unbedingt notwendig, hat man aber die Anwendung auf einem Smartphone oder einem Tablet und ist über Mobilfunk im Netz, kann eine permanente Übertragung von Daten, wen es auch immer nur kleine Häppchen sind, schon am verfügbaren Download-Volumen nagen.

Auf https://github.com/winne27/fhem.js habe ich soeben eine neue Version hochgeladen. Dort gibt es jetzt die Option

getAllValuesOnChange

Damit wird jede Änderung an beliebigen Devices vom Server gepusht.

HansDampfHH

Super, dass du Deine Lösung so supportest !
Leider bekomme ich mit der neuen Variante einen Fehler auf der Kommandozeile nach Absetzen von node server.js:
Cannot find module mysql

Muss ich für node.js noch ein weiteres Modul laden und eine Verbindung zu Mysql herstellen?
FHEM Docker, CUL868, Zigbee, CCU2, Jeelink