📬 NTFY.sh: Push Nachrichten an iOS, Android, PC, Command-Line, E-Mail & Telefon

Begonnen von Torxgewinde, 09 Februar 2024, 15:35:12

Vorheriges Thema - Nächstes Thema

Torxgewinde

Ich hatte auch nochmal an meinem Snippet einen weiteren Watchdog zu dem einfachen Wiederverbinden eingefügt, doppelt hält besser... Das sollte damit recht hartnäckig die Verbindung halten und neu aufbauen, außer man gibt den stop-Befehl.

Weiterhin: Erste Wahl ist IMHO der Code von ByteRazor, falls das nicht klappt alternativ den Snippet von mir nehmen.

defmod NTFY_RECEIVE dummy
attr NTFY_RECEIVE userattr URL last_seen_max_age
attr NTFY_RECEIVE URL wss:ntfy.sh:443/FreundlichenGruesseAnAlleFHEMNutzer/ws
attr NTFY_RECEIVE alias NTFY_RECEIVE
attr NTFY_RECEIVE devStateIcon opened:general_ok@green:stop disconnected:rc_STOP@red:start
attr NTFY_RECEIVE eventMap /cmd connect:start/cmd disconnect:stop/
attr NTFY_RECEIVE group Experimente
attr NTFY_RECEIVE icon hue_filled_plug
attr NTFY_RECEIVE last_seen_max_age 600
attr NTFY_RECEIVE readingList cmd
attr NTFY_RECEIVE setList cmd
attr NTFY_RECEIVE userReadings connect:cmd:.connect {\
my $hash = $defs{$name};;\
my $devState = DevIo_IsOpen($hash);;\
return "Device already open" if (defined($devState));;\
\
$hash->{DeviceName} = AttrVal($name, "URL", "wss:ntfy.sh:443/FreundlichenGruesseAnAlleFHEMNutzer/ws");;\
$hash->{header}{'Host'} = 'ntfy.sh';;\
$hash->{header}{'User-Agent'} = 'FHEM';;\
\
$hash->{directReadFn} = sub () {\
my $hash = $defs{$name};;\
readingsBeginUpdate($hash);;\
my $buf = DevIo_SimpleRead($hash);;\
\
# track activity, emtpy buffer normally is from ping/pongs\
readingsBulkUpdate($hash, "last_seen", int(time()*1000));;\
RemoveInternalTimer($name.'Timeout');;\
my $timeoutFunction = sub() {\
my ($arg) = @_;;\
my $hash = $defs{$name};;\
my $myCmd = ReadingsVal($name, "cmd", "disconnect");;\
return if ($myCmd =~ /disconnect|stop/);;\
\
Log3($name, 3, "$name: Timeout occured, restarting websocket...");;\
DevIo_CloseDev($hash);;\
readingsBeginUpdate($hash);;\
readingsBulkUpdate($hash, "state", "disconnected");;\
readingsBulkUpdate($hash, "cmd", "connect", 1);;\
readingsEndUpdate($hash, 1);;\
};;\
InternalTimer(gettimeofday() + 120, $timeoutFunction, $name.'Timeout');;\
\
if(!defined($buf)) {\
DevIo_CloseDev($hash);;\
#readingsBulkUpdate($hash, "last_seen", 0);;\
$buf = "not_connected";;\
}\
\
# only update our reading if buffer is not empty and looks like it contains a message\
if ($buf ne "" && \
    $buf =~ /^{.*"event":"message".*}$/) { ## check if buffer looks like JSON with msg\
\
# delete all our readings that begin with "ntfy_"\
foreach my $reading (grep { $_ =~ /^ntfy_.*/ } keys %{$hash->{READINGS}}) {\
readingsDelete($hash, $reading);;\
}\
\
# parse as JSON, do not trust the input fully, thus sanitize buffer\
my %res = %{json2nameValue($buf)};; #(https://wiki.fhem.de/wiki/MQTT2_DEVICE_-_Schritt_f%C3%BCr_Schritt#json2nameValue.28.29)\
foreach my $k (sort keys %res) {\
# only keep ASCII and a German Characters like Umlaute, sharp-S...\
my $sanitizedValue = $res{$k} =~ s/[^[:ascii:]äöüÖÄÜß]/_/rg;; # 'r' flag prevents modifying the input string\
readingsBulkUpdate($hash, "ntfy_".makeReadingName($k), $sanitizedValue);;\
}\
}\
#readingsBulkUpdate($hash, "websocketData", "$buf") if ($buf ne "");;\
Log3($name, 3, "$name: Rx: >>>$buf<<<") if ($buf ne "");;\
\
readingsEndUpdate($hash, 1);;\
};;\
\
DevIo_OpenDev($hash,\
0,      ## reopen flag\
undef,  ## initFn, on success\
sub() { ## callbackFn, on verdict, req. to make it a non-blocking call\
my ($hash, $error) = @_;;\
if ($error) {\
Log(3, "$name: DevIo_OpenDev Callback: connection failed: $error");;\
\
my $timerFunction = sub() {\
my ($arg) = @_;;\
my $hash = $defs{$name};;\
my $devState = DevIo_IsOpen($hash);;\
readingsSingleUpdate($hash, "cmd", "connect", 1) if (!defined($devState));;\
};;\
\
RemoveInternalTimer($name.'Timer');;\
my $rwait = int(rand(20)) + 10;;\
InternalTimer(gettimeofday() + $rwait, $timerFunction, $name.'Timer');;\
readingsSingleUpdate($hash, "cmd", "reconnect attempt in $rwait seconds", 1);;\
}\
}\
);;\
\
readingsBulkUpdate($hash, "state", "connecting...");;\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
disconnect:cmd:.(disconnect|reconnect) {\
my $hash = $defs{$name};;\
my $myCmd = ReadingsVal($name, "cmd", "???");;\
\
RemoveInternalTimer($name.'Timer');;\
RemoveInternalTimer($name.'Timeout');;\
DevIo_CloseDev($hash);;\
readingsBulkUpdate($hash, "state", "disconnected") if (!defined(DevIo_IsOpen($hash)));;\
\
if ($myCmd =~ /reconnect/) {\
my $timerFunction = sub() {\
my $hash = $defs{$name};;\
readingsSingleUpdate($hash, "cmd", "connect", 1);;\
};;\
\
RemoveInternalTimer("${name}_${reading}_timer");;\
InternalTimer(gettimeofday()+1, $timerFunction, "${name}_${reading}_timer");;\
} else {\
RemoveInternalTimer("${name}_watchdog_timer");;\
}\
\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onDisconnect { ## check on each update if the connection is unintentionally broken...\
my $myState = ReadingsVal($name, "state", "???");;\
my $myData = ReadingsVal($name, "websocketData", "???");;\
my $myCmd = ReadingsVal($name, "cmd", "disconnect");;\
return if ($myState ne "disconnected" and $myData ne "not_connected");;\
return if ($myCmd =~ /disconnect|stop/);;\
\
my $timerFunction = sub() {\
my ($arg) = @_;;\
my $hash = $defs{$name};;\
my $devState = DevIo_IsOpen($hash);;\
readingsSingleUpdate($hash, "cmd", "connect", 1) if (!defined($devState));;\
};;\
\
RemoveInternalTimer($name.'Timer');;\
my $rwait = int(rand(20)) + 10;;\
InternalTimer(gettimeofday() + $rwait, $timerFunction, $name.'Timer');;\
readingsBulkUpdate($hash, "cmd", "reconnect attempt in $rwait seconds");;\
\
return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
watchdog:last_seen:.* {\
my $ls = ReadingsVal($name, "last_seen", 0);;\
\
my $timerFunction = sub() {\
##fhem("set FHEMMeldung.ntfy message $name $reading wurde ausgelöst (last_seen: $ls)");;\
readingsSingleUpdate($hash, "cmd", "reconnect", 1);;\
readingsSingleUpdate($hash, "last_seen", 0, 1);;\
};;\
\
RemoveInternalTimer("${name}_${reading}_timer");;\
InternalTimer(gettimeofday()+240, $timerFunction, "${name}_${reading}_timer");;\
\
return POSIX::strftime("%H:%M:%S", localtime(time()));;\
}
attr NTFY_RECEIVE verbose 1
attr NTFY_RECEIVE webCmd start:stop

Torxgewinde

Kleines Update, falls mit meinem Snippet auch Nutzername und Passwort (als Attribute "username" und "password") genutzt werden sollen:

defmod NTFY_RECEIVE dummy
attr NTFY_RECEIVE userattr URL last_seen_max_age password username
attr NTFY_RECEIVE URL wss:ntfy.sh:443/FreundlichenGruesseAnAlleFHEMNutzer/ws
attr NTFY_RECEIVE alias NTFY_RECEIVE
attr NTFY_RECEIVE devStateIcon opened:general_ok@green:stop disconnected:rc_STOP@red:start
attr NTFY_RECEIVE eventMap /cmd connect:start/cmd disconnect:stop/
attr NTFY_RECEIVE group Experimente
attr NTFY_RECEIVE icon hue_filled_plug

attr NTFY_RECEIVE password superGeheimesPasswort
attr NTFY_RECEIVE readingList cmd

attr NTFY_RECEIVE setList cmd
attr NTFY_RECEIVE userReadings connect:cmd:.connect {\
    my $hash = $defs{$name};;\
    my $devState = DevIo_IsOpen($hash);;\
    return "Device already open" if (defined($devState));;\
    \
    $hash->{DeviceName} = AttrVal($name, "URL", "wss:ntfy.sh:443/FreundlichenGruesseAnAlleFHEMNutzer/ws");;\
    $hash->{DeviceName} =~ m,^(ws:|wss:)?([^/:]+):([0-9]+)(.*?)$,;;\
    $hash->{header}{'Host'} = $2;;\
    $hash->{header}{'User-Agent'} = 'FHEM';;\
    \
    my $user = AttrVal($name, "username", "???");;\
    my $pwd  = AttrVal($name, "password", "???");;\
    if ($user ne "???" && $pwd ne "???") {\
        my $encoded_auth = MIME::Base64::encode_base64("$user:$pwd", "");;\
        $hash->{header}{'Authorization'} = "Basic $encoded_auth";;\
    }\
    \
    $hash->{directReadFn} = sub () {\
        my $hash = $defs{$name};;\
        readingsBeginUpdate($hash);;\
        my $buf = DevIo_SimpleRead($hash);;\
        \
        # track activity, emtpy buffer normally is from ping/pongs\
        readingsBulkUpdate($hash, "last_seen", int(time()*1000));;\
        RemoveInternalTimer($name.'Timeout');;\
        my $timeoutFunction = sub() {\
            my ($arg) = @_;;\
            my $hash = $defs{$name};;\
            my $myCmd = ReadingsVal($name, "cmd", "disconnect");;\
            return if ($myCmd =~ /disconnect|stop/);;\
            \
            Log3($name, 3, "$name: Timeout occured, restarting websocket...");;\
            DevIo_CloseDev($hash);;\
            readingsBeginUpdate($hash);;\
            readingsBulkUpdate($hash, "state", "disconnected");;\
            readingsBulkUpdate($hash, "cmd", "connect", 1);;\
            readingsEndUpdate($hash, 1);;\
        };;\
        InternalTimer(gettimeofday() + 120, $timeoutFunction, $name.'Timeout');;\
        \
        if(!defined($buf)) {\
            DevIo_CloseDev($hash);;\
            #readingsBulkUpdate($hash, "last_seen", 0);;\
            $buf = "not_connected";;\
        }\
        \
        # only update our reading if buffer is not empty and looks like it contains a message\
        if ($buf ne "" && \
            $buf =~ /^{.*"event":"message".*}$/) { ## check if buffer looks like JSON with msg\
            \
            # delete all our readings that begin with "ntfy_"\
            foreach my $reading (grep { $_ =~ /^ntfy_.*/ } keys %{$hash->{READINGS}}) {\
                readingsDelete($hash, $reading);;\
            }\
            \
            # parse as JSON, do not trust the input fully, thus sanitize buffer\
            my %res = %{json2nameValue($buf)};; #(https://wiki.fhem.de/wiki/MQTT2_DEVICE_-_Schritt_f%C3%BCr_Schritt#json2nameValue.28.29)\
            foreach my $k (sort keys %res) {\
                # only keep ASCII and a German Characters like Umlaute, sharp-S...\
                my $sanitizedValue = $res{$k} =~ s/[^[:ascii:]äöüÖÄÜß]/_/rg;; # 'r' flag prevents modifying the input string\
                readingsBulkUpdate($hash, "ntfy_".makeReadingName($k), $sanitizedValue);;\
            }\
        }\
        #readingsBulkUpdate($hash, "websocketData", "$buf") if ($buf ne "");;\
        Log3($name, 3, "$name: Rx: >>>$buf<<<") if ($buf ne "");;\
        \
        readingsEndUpdate($hash, 1);;\
    };;\
    \
    DevIo_OpenDev($hash,\
        0,      ## reopen flag\
        undef,  ## initFn, on success\
        sub() { ## callbackFn, on verdict, req. to make it a non-blocking call\
            my ($hash, $error) = @_;;\
            if ($error) {\
                Log(3, "$name: DevIo_OpenDev Callback: connection failed: $error");;\
                \
                my $timerFunction = sub() {\
                    my ($arg) = @_;;\
                    my $hash = $defs{$name};;\
                    my $devState = DevIo_IsOpen($hash);;\
                    readingsSingleUpdate($hash, "cmd", "connect", 1) if (!defined($devState));;\
                };;\
                \
                RemoveInternalTimer($name.'Timer');;\
                my $rwait = int(rand(20)) + 10;;\
                InternalTimer(gettimeofday() + $rwait, $timerFunction, $name.'Timer');;\
                readingsSingleUpdate($hash, "cmd", "reconnect attempt in $rwait seconds", 1);;\
            }\
        }\
    );;\
    \
    readingsBulkUpdate($hash, "state", "connecting...");;\
    return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
disconnect:cmd:.(disconnect|reconnect) {\
    my $hash = $defs{$name};;\
    my $myCmd = ReadingsVal($name, "cmd", "???");;\
    \
    RemoveInternalTimer($name.'Timer');;\
    RemoveInternalTimer($name.'Timeout');;\
    DevIo_CloseDev($hash);;\
    readingsBulkUpdate($hash, "state", "disconnected") if (!defined(DevIo_IsOpen($hash)));;\
    \
    if ($myCmd =~ /reconnect/) {\
        my $timerFunction = sub() {\
            my $hash = $defs{$name};;\
            readingsSingleUpdate($hash, "cmd", "connect", 1);;\
        };;\
    \
        RemoveInternalTimer("${name}_${reading}_timer");;\
        InternalTimer(gettimeofday()+1, $timerFunction, "${name}_${reading}_timer");;\
    } else {\
        RemoveInternalTimer("${name}_watchdog_timer");;\
    }\
    \
    return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
onDisconnect { ## check on each update if the connection is unintentionally broken...\
    my $myState = ReadingsVal($name, "state", "???");;\
    my $myData = ReadingsVal($name, "websocketData", "???");;\
    my $myCmd = ReadingsVal($name, "cmd", "disconnect");;\
    return if ($myState ne "disconnected" and $myData ne "not_connected");;\
    return if ($myCmd =~ /disconnect|stop/);;\
    \
    my $timerFunction = sub() {\
        my ($arg) = @_;;\
        my $hash = $defs{$name};;\
        my $devState = DevIo_IsOpen($hash);;\
        readingsSingleUpdate($hash, "cmd", "connect", 1) if (!defined($devState));;\
    };;\
    \
    RemoveInternalTimer($name.'Timer');;\
    my $rwait = int(rand(20)) + 10;;\
    InternalTimer(gettimeofday() + $rwait, $timerFunction, $name.'Timer');;\
    readingsBulkUpdate($hash, "cmd", "reconnect attempt in $rwait seconds");;\
    \
    return POSIX::strftime("%H:%M:%S",localtime(time()));;\
},\
watchdog:last_seen:.* {\
    my $ls = ReadingsVal($name, "last_seen", 0);;\
        \
    my $timerFunction = sub() {\
        ##fhem("set FHEMMeldung.ntfy message $name $reading wurde ausgelöst (last_seen: $ls)");;\
        readingsSingleUpdate($hash, "cmd", "reconnect", 1);;\
        readingsSingleUpdate($hash, "last_seen", 0, 1);;\
    };;\
    \
    RemoveInternalTimer("${name}_${reading}_timer");;\
    InternalTimer(gettimeofday()+240, $timerFunction, "${name}_${reading}_timer");;\
    \
    return POSIX::strftime("%H:%M:%S", localtime(time()));;\
}
attr NTFY_RECEIVE username Torxgewinde
attr NTFY_RECEIVE verbose 1
attr NTFY_RECEIVE webCmd start:stop