🔌 Websocket mit DevIo, Owntone Push-Notifications erhalten

Begonnen von Torxgewinde, 17 November 2023, 17:01:11

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Owntone (Musik-Player, vormals Forked-Daapd) stellt Benachrichtigungen via Websocket bereit. Verändert man die Lautstärke, wird zum Beispiel die Nachricht "{ "notify": [ "volume" ] }" verschickt. DevIo kann man nutzen um diese Websocket zu lesen. Die anderen API Aufrufe für Owntone lassen sich mit HTTPMOD erledigen: https://forum.fhem.de/index.php?topic=135666.0

Besonderheit:
  • Owntone möchte gerne zusätzliche Header in der Anfrage sehen. Ohne diese Header wird die Verbindung aufgebaut, aber nie etwas an FHEM gesendet.
  • wert connect muss man teilweise zweimal aufrufen damit die Websocket aufgebaut wird. Da muss man nochmal schauen...

Einfaches Testen:
Zum Testen von Websockets eignet sich uwsc. So konnte ich schnell herausfinden welcher der Header denn unbedingt noch benötigt wird: uwsc -x "Host: localhost:3688" -x "Origin: http://localhost:3688" -x "Sec-WebSocket-Protocol: notify" http://owntone.server:3688. Das Tool ist in Debian mit der Paketverwaltung installierbar.

defmod WS dummy
attr WS userattr websocketURL
attr WS readingList wert
attr WS setList wert
attr WS userReadings connect:wert:.connect {\
my $hash = $defs{$name};;\
$hash->{DeviceName} = AttrVal($name, "websocketURL", "ws:echo.websocket.org:443");;\
\
# special headers needed for Owntone\
# https://owntone.github.io/owntone-server/json-api/#push-notifications\
$hash->{header}{'Sec-WebSocket-Protocol'} = 'notify';;\
$hash->{header}{'Host'} = 'localhost:3688';;\
$hash->{header}{'Origin'} = 'http://localhost:3688';;\
\
# callback function when "select" signals data for us\
# websocket Ping/Pongs are treated in DevIo but still call this function\
$hash->{directReadFn} = sub () {\
my $hash = $defs{$name};;\
readingsBeginUpdate($hash);;\
\
# we can read without closing the DevIo, because select signalled data\
my $buf = DevIo_SimpleRead($hash);;\
\
if(!defined($buf)) {\
DevIo_CloseDev($hash);;\
$buf = "not connected";;\
}\
\
# only update our reading if buffer is not empty\
readingsBulkUpdate($hash, "websocketData", "$buf") if ($buf ne "");;\
readingsEndUpdate($hash, 1);;\
};;\
\
# open DevIo websocket\
DevIo_OpenDev($hash, 0, undef, sub(){\
my ($hash, $error) = @_;;\
return "$error" if ($error);;\
\
#immediately send Owntone what we would like to be notified for (here we selected everything)\
DevIo_SimpleWrite($hash, '{"notify":["update","database","player","options","outputs","volume","queue","spotify","lastfm","pairing"]}', 2);;\
});;\
\
return "ok";;\
}
attr WS webCmd wert connect
attr WS websocketURL ws:owntone.host:3688

setstate WS opened
setstate WS 2023-11-16 22:40:38 connect ok
setstate WS 2023-11-16 22:40:38 state opened
setstate WS 2023-11-16 22:45:00 websocketData { "notify": [ "player" ] }
setstate WS 2023-11-16 22:40:38 wert connect

Torxgewinde

Nachdem der Socket nun gut läuft, hier einmal das Listing womit ich dann das HTTPMOD Owntone.device aufrufe. Dadurch kann man das Pollingintervall deutlich reduzieren, zB auf einmal pro Minute oder sogar noch seltener. Die IP 192.168.123.123 ist ein Beispiel und wird durch den Owntone-Server ersetzt.

defmod WS dummy
attr WS userattr websocketURL
attr WS alias Owntone Websocket
attr WS devStateIcon opened:general_ok@green:stop disconnected:rc_STOP@red:start
attr WS eventMap /wert connect:start/wert disconnect:stop/
attr WS group Musik
attr WS icon hue_filled_plug
attr WS readingList wert
attr WS room Musik
attr WS setList wert
attr WS userReadings connect:wert:.connect {\
my $hash = $defs{$name};;\
my $devState = DevIo_IsOpen($hash);;\
return "Device already open" if (defined($devState));;\
\
$hash->{DeviceName} = AttrVal($name, "websocketURL", "ws:echo.websocket.org:443");;\
#$hash->{nextOpenDelay} = 10;;\
\
# special headers needed for Owntone\
# https://owntone.github.io/owntone-server/json-api/#push-notifications\
$hash->{header}{'Sec-WebSocket-Protocol'} = 'notify';;\
$hash->{header}{'Host'} = 'localhost:3688';;\
$hash->{header}{'Origin'} = 'http://localhost:3688';;\
\
# callback function when "select" signals data for us\
# websocket Ping/Pongs are treated in DevIo but still call this function\
$hash->{directReadFn} = sub () {\
my $hash = $defs{$name};;\
readingsBeginUpdate($hash);;\
\
# we can read without closing the DevIo, because select signalled data\
my $buf = DevIo_SimpleRead($hash);;\
\
if(!defined($buf)) {\
DevIo_CloseDev($hash);;\
$buf = "not connected";;\
}\
\
# only update our reading if buffer is not empty\
readingsBulkUpdate($hash, "websocketData", "$buf") if ($buf ne "");;\
readingsEndUpdate($hash, 1);;\
};;\
\
#my $timerFunction = sub() {\
# my ($hash) = @_;;\
# my $devState = DevIo_IsOpen($hash);;\
# readingsSingleUpdate($hash, "wert", "connect", 1) if (!defined($devState));;\
#};;\
#RemoveInternalTimer($hash, $timerFunction);;\
#InternalTimer(gettimeofday() + 10, $timerFunction, $hash);;\
\
# open DevIo websocket\
DevIo_OpenDev($hash, 0, undef, sub(){\
my ($hash, $error) = @_;;\
return "$error" if ($error);;\
\
#immediately send Owntone what we would like to be notified for (here we selected everything)\
DevIo_SimpleWrite($hash, '{"notify":["update","database","player","options","outputs","volume","queue","spotify","lastfm","pairing"]}', 2);;\
});;\
\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
disconnect:wert:.disconnect {\
my $hash = $defs{$name};;\
RemoveInternalTimer($hash);;\
DevIo_SimpleRead($hash);;\
DevIo_CloseDev($hash);;\
\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onDisconnect {\
my $myState = ReadingsVal($name, "state", "???");;\
return if ($myState ne "disconnected");;\
\
my $timerFunction = sub() {\
my ($hash) = @_;;\
my $devState = DevIo_IsOpen($hash);;\
readingsSingleUpdate($hash, "wert", "connect", 1) if (!defined($devState));;\
};;\
my $hash = $defs{$name};;\
RemoveInternalTimer($hash, $timerFunction);;\
InternalTimer(gettimeofday() + 10, $timerFunction, $hash);;\
\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onPlayer:websocketData:.*player.* {\
fhem("set Owntone.device reread");;\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onOutputs:websocketData:.*outputs.* {\
fhem("get Owntone.device outputs");;\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onVolume:websocketData:.*volume.* {\
fhem("get Owntone.device volume");;\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onQueue:websocketData:.*queue.* {\
fhem("get Owntone.device queue");;\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
}
attr WS websocketURL ws:192.168.123.123:3688

Du darfst diesen Dateianhang nicht ansehen.

Torxgewinde

Ich habe die Websocket auch als Beispiel in das Wiki gepackt. Auch ist mir aufgefallen, dass ich die Funktion "RemoveInternalTimer()" nicht korrekt verwendet hatte.