Communication with Fhem via websockets ?

Begonnen von AlxDmr, 03 November 2014, 19:58:17

Vorheriges Thema - Nächstes Thema

ntruchsess

im also in favor to implement a pure json-based protocol (in fact that is what the current implementation does response-wise). But as websocket-handshake is designed it's not limited to this - when connecting the client submits an URI-string and a list of supported protocols. It would be possible to support both: new pure json rpc protocol and existing 'newline-separated' fhemweb protocol by checking those on connection-establishment and choose the requested mode of operation.
while (!asleep()) {sheep++};

rudolfkoenig

My problem is not the newline vs. JSON protocol, but the fact, that the current fhemweb.js needs more than pure FHEM events. It relies on the FHEMWEB enriched data like status .png.

justme1968

why is this a problem?

the enriched data can be send by websockets and also 'pure' fhem events can contain images. see here for example: http://forum.fhem.de/index.php/topic,28866.msg216874.html#msg216874
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

ntruchsess

sure, I understand that. For that reason I'd like to have two protocols, one that is optimized for fhemweb.js and one that is going to access the 'raw' data. The second option is more efficient when we talk about high-cpu-power smartphones accessing a fritzbox ;-)

If you like to get all enriched data for fhemweb.js in json-format to, then everything may go into a single protocol (that has special commands for fhemweb.js). If you are talking about binary data (streaming e.g png through websockets) than this is should not be encoded as json but binary-messages. I have to check whether it's possible to enrich binary-messages with metadata.
while (!asleep()) {sheep++};

rudolfkoenig

@ntruchsess: I dont need binary data (FHEMWEB only sends the .png URL), but I need a hook to enrich/replace events with the corresponding image-url, SVG, etc. But I think that defining/deleting a second device and opening a new port for each FHEMWEB instance is too complicated, so I think I will just use your module as an example -> Don't bother with FHEMWEB or me :)

@justme1968: I am not convinced, that putting images in events is a good idea: for some users these images will land in a FileLog or DbLog.

justme1968

if there is a general way to add events/messages/data to the longpoll mechanism i will be happy to use it.

if a user is logging everything then a few images should be no problem.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

ntruchsess

images in events is good for nothing else than a specific frontend (and maybe a specific device and/or resolution) only. My assumption is that the main reason this got introduced is the lack of appropriate bidirectional remote-protocoll for web-browser in fhemweb that allows to receive both raw events and query for design-specific metadata through the very same (longpoll) channel. As websockets are both message-oriented and bidirectional by desing I'm pretty sure that they are a good basis to implement such.
while (!asleep()) {sheep++};

justme1968

readingsGroup is very fhemweb specific and the messages are device specific and only intended for the readingsGroup display. the same will be the case for some new widgets that can be used for heating control and shutter/blind control.

readings group even tries to detect if it is currently displayed and will not send any events if it is not visible in a browser window.

so yes. a fronted/device specific subscription method would be perfect for this.

would you suggest using a separate web sockets for each purpose? or a general one with a subscribe and dispatch mechanism similar to the current fhemweb 'widget' idea?

the last could allow for different channels (tags/message types) on the same socket and for example fhemweb would use a longpoll channel and the readingsGroup frontend would request a readingsGroup channel. the backend would just create both message types and send them to the same websocket. a message and device specific filter like the current inform filter could be integrated in the backend so unneeded messages would not be send. in addition for modules like readingsGroup a hasSubscribers method could query if there are subscribers for a specific device and stop generating the events altogether.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

ntruchsess

Best practice is to use a single websocket per browers-session, and use a subscribe-mechanism to dispatch the messages coming from server. One can easily implement multiple communication-channels through a single websocket using structured (json) data. All you need is some 'type' or 'channel'-attribute on the outermost level of the message-object.

Having multiple websockets per browser-session is not advisable as it may conflict with limits imposed by the client-device.

On the server-side Fhem currently doesn't have an appropriate subscribe-mechanism for its events as they are not type-save but string-data only (so you can 'subscribe' on device-level (NOTIFYDEV) and filter CHANGED by regexp and you may mark an event within it's content as e.g. 'widget-only' but as the event-data is unstructured this eventually will conflict with other events).

Therefore I suggest to leave the fhem-event-sytem unchanged and use it for what it is designed for (distributing reading-changes and lifecycle-events within fhem), filter the events a widget is interested in on the server side and send 'enriched' messages (enriched in respect to e.g. visual representation specific data) through the appropriate websocket-channel only if there's a subscrition for it on the client-side.

On the server-side there should be an api to allow for multiple frontendspecific modules to mediate fhem-events to widget-type specific messages.

(read 'suggest' and 'should' as: 'This is what i'm currently implementing...')

- Norbert
while (!asleep()) {sheep++};

ntruchsess

update: subscribe-mechanism for the client and for fhem-modules implemented:

98_websocket.pm
websocket.html

on the webbrowser-side:
use json as protocol when opening the connection:

var con = new WebSocket('ws://localhost:8080/fhem', ['json']);


if protocoll json is not specified all text-messages sent to the websocket are interpreted as fhem-commands, the messages sent back will contain the response of those commands.

if protocoll is 'json' than the following message-structure is used:


{"type":"...", "payload":{...}}


'payload' is message-'type' specific. Types defined so far are: 'command', 'commandreply' and 'event'

commands defined so far are 'subscribe', 'unsubscribe', 'list' and the other fhem-commands.

fhem-modules may subscribe to receive messages of (custom) types from websocket using websocket::subscribeMsgType() (->see below)

e.g.

{"type":"command","payload":{"command":"subscribe","type":"","name":"Licht_.*zimmer","changed":"on|off","arg":"widget1"}}

will register for fhem-events in json-format (equivalent to a call to websocket::subscribeEvent() (->see below))

The Messages of type 'event' that are send back to the browser have the following structure:

{"type":"event","payload":{"name":"Licht_Wohnzimmer","type":"OWSwitch","arg":"switch1","changed":{"state":"on"}}}



{"type":"command","payload":{"command":"list","arg":"room=raum"}}

does call jsonlist2 and wrapps the output into a json-response message type 'commandreply':

{"payload":{"command":"list","reply":{"Arg":"dummy","Results":[{"Internals":{"NAME":"dummy","NR":"15","TYPE":"dummy","STATE":"test: off"},"Readings":{"state":{"Time":"2014-11-12 22:27:23","Value":"test: off"}},"PossibleAttrs":"verbose:0,1,2,3,4,5 room group comment alias eventMap userReadings setList event-on-change-reading event-on-update-reading event-min-interval stateFormat devStateIcon devStateStyle icon sortby webCmd widgetOverride userattr","Attributes":{"room":"raum"},"Name":"dummy","PossibleSets":" "}],"totalResultsReturned":1}},"type":"commandreply"}


on the serverside:
an fhem-module may use the following methods to subscribe for lifecycle-events

websocket::subscribeOpen($hash,$fn,$arg)
websocket::unsubscribeOpen($hash,$arg)
websocket::subscribeClose($hash,$fn,$arg)
websocket::unsubscribeClose($hash,$arg)


$hash is the master websocket device, $fn is a code-reference, $arg is an (optional) argument that is both used as key (to unsubscribe) and passed as argument to the code-reference (equivalent to the argument of InternalTimer)

the signature of code-referece is:

sub <name>($cl,$arg)

$cl being the hash of the connected websocket-child-device, $arg is the argument that was passed to the subscribe-method

an fhem-module may subscribe to messaged being received by a connected websocket instance:

websocket::subscribeMsgType($cl,$type,$fn,$arg)
websocket::unsubscribeMsgType($cl,$type,$arg)

signature of $fn is:

sub <name>($cl,$payload,$arg);

$cl again is the hash of the connected websocket-child-device, $type is the type of json-message to subscribe to, $fn is the code-referece to be called and $arg the (optional) argument (that again can be used as key to unsubscribe), $payload is the payload value of the message received via websocket.

additionally an fhem-module may register subscriptions to events is such way that for each event that matches the regexp a message type 'event' is sent to the connected websocket:

websocket::subscribeEvent($cl,name => $name, type => $type, changed => $changed, arg => $arg)
websocket::unsubscribeEvent($cl,$arg)


$cl is the hash of the connected websocket-child-device, $name is a regular-expression matching the NAME of the device emmitting the event (optional), $type a regexp matching the TYPE (also optional), $changed a regexp to filter contents of the fhem-event, $arg is a value (string, array or hash) that is passed with the messages payload as value of key 'arg'. That can be used on the browser-side to identify a widget that should receive the event-message. The 'CHANGED'-array of the fhem-event is filtered by the regexp and all matching entries are send as value of key 'changed' in the payload of the message.

while (!asleep()) {sheep++};

AlxDmr

Halo,
I tried to connect to Fhem using websocket (after updating Fhem). But I get the following errors when doing:
con = new WebSocket('ws://192.168.1.14:8080/fhem', ['json']);
the error code is :
WebSocket connection to 'ws://192.168.1.14:8080/fhem' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

I also tried on port 8083, wich is the port used for HTTP server :
con = new WebSocket('ws://192.168.1.14:8083/fhem', ['json']);
the error code is :
WebSocket connection to 'ws://192.168.1.14:8083/fhem' failed: Error during WebSocket handshake: Unexpected response code: 200

I guess I did not updated code correctly or that I do not target the right port... how could I check?

   Alex.

ntruchsess

98_websocket.pm is (not yet) includet in update as the api is not settled yet. You have to install manually (use download-links you find a few posts above) or checkout from my fhem-mirror on github branch websocket.
If you have any suggestions about the javascript-api (see file fhem.js, your feedback is highly aprechiated.

regards,

Norbert
while (!asleep()) {sheep++};

AlxDmr

Halo,
thanks for the advice, for sure I will test that :)
However, I downloaded 98_websocket.pm in /opt/fhem/FHEM and I restarted everything (the raspberry itself). I still get the very same error messages.
Should I modify something else so that the module is taken into account?

ntruchsess

You have to define it first. ('define websocket websocket 8080 global' - same semantics as telnet-device) and you might have to install the dependencies...Protocol::WebSocket)
while (!asleep()) {sheep++};

AlxDmr

Thanks,
I added the command in fem.cfg and indeed it requires some dependencies.
I went to the website you indicated, downloaded and unpacked the file but I have no idea how I should install that, there are no information about that on the website. It's probably a Linux thing but I mainly use windows so it's not easy for me. I tried some make and make install but it did not worked...
I know it's a bit out of context for this forum but could you indicate me how to install the dependency once files are on the Raspberry please ?