Ntfy

Aus FHEMWiki
Version vom 15. Februar 2025, 07:10 Uhr von Torxgewinde (Diskussion | Beiträge) (Erste Version, Seite in Arbeit)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

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

NTFY.sh bietet Push-Dienste auf iOS, Android, Console, E-Mail, ja sogar als Anruf auf ein Telefon an. Es ist damit ähnlich zu dem was man mit PushOver, PushBullet oder auch Gotify anstellen kann, wobei NTFY auch Kriterien wie "Self-hosted" und echte "iOS-Pushnachrichten" erfüllt. Man kann damit von FHEM aus alle diese Kanäle mit einer Push-Nachricht erreichen. Die Nutzung ist extrem einfach, man setzt einen HTTP-Aufruf ab um eine Push-Nachricht zu versenden. NTFY.sh betreibt sogar einen Gratis-Server, den man nutzen darf. Man kann den Server auch selber hosten wenn man will.

Öffnet testweise diesen Link, dort tauchen die Nachrichten auch in der PWA (Webapp) auf: https://ntfy.sh/FreundlichenGruesseAnAlleFHEMNutzer

Anbindung per externem Modul

Um NTFY.sh einzurichten:

Code Auswählen update add https://rm.byterazor.de/upd-fhem-ntfy/controls_byterazor-fhem-ntfy.txtupdateshutdown restartEin Device anlegen mit:


Code Auswählen defmod NTFY0 NTFY_CLIENT https://ntfy.shattr NTFY0 defaultPriority defaultattr NTFY0 defaultTopic FreundlichenGruesseAnAlleFHEMNutzerattr NTFY0 room ExperimenteSenden einer Nachricht mit einem curl-Befehl:


Code Auswählen curl -d "Your message content here" \     -H "Title: Bla" \     https://ntfy.sh/FreundlichenGruesseAnAlleFHEMNutzerDas Gleiche nur mit WGET:


Code Auswählen wget --method=POST \     --body-data="Your message content here" \     --header="Title: Bla" \     https://ntfy.sh/FreundlichenGruesseAnAlleFHEMNutzer -q -O -Oder, wenn es ein reines HTTP-GET als Webhook sein soll:

https://ntfy.sh/FreundlichenGruesseAnAlleFHEMNutzer/publish?message=Your%20message%20content%20here&title=Bla Oder, aus FHEM an den Server eine Nachricht senden mit:


Code Auswählen set NTFY0 publish @FreundlichenGruesseAnAlleFHEMNutzer Testnachricht!Im Event-Log taucht dann folgendes auf, darauf kann man mit notify oder DOIF reagieren:


Code Auswählen 2024-10-29 19:01:16.714 NTFY_TOPIC NTFY0_FreundlichenGruesseAnAlleFHEMNutzer nrReceivedMessages: 72024-10-29 19:01:16.715 NTFY_CLIENT NTFY0 subscriptions: FreundlichenGruesseAnAlleFHEMNutzer2024-10-29 19:01:16.715 NTFY_CLIENT NTFY0 nrReceivedMessages: 72024-10-29 19:01:16.715 NTFY_CLIENT NTFY0 lastReceivedTitle: Bla2024-10-29 19:01:16.715 NTFY_CLIENT NTFY0 lastReceivedData: Your message content here2024-10-29 19:01:16.715 NTFY_CLIENT NTFY0 lastReceivedRawMessage: {"id":"oU5iqrdSFoeY","time":1730224876,"expires":1730268076,"event":"message","topic":"FreundlichenGruesseAnAlleFHEMNutzer","title":"Bla","message":"Your message content here"}Testen kannst man hier:

https://ntfy.sh/FreundlichenGruesseAnAlleFHEMNutzer https://demo-fhem.cooltux.net/fhem?detail=NTFY0 <-- Begrenzt gültiger Link https://ntfy.sh/FreundlichenGruesseAnAlleFHEMNutzer/publish?message=Blubb&title=Bla

Anbindung per HTTPMOD und Websocket

Senden mit HTTPMOD

  • set NTFY message Title="mein Titel aus dem Set heraus" Meine Nachricht
  • set NTFY message Title="" Meine Nachricht ohne Titel
  • set NTFY message Title="✅ Emojis 🚀" 📝 Meine Nachricht mit Emojis 👾
  • oder man hat nur den Titel im User-Attribut "Title" falls das Muster Title="irgendwas" nicht im Parameter der set Nachricht auftaucht: set NTFY message Meine kurze Nachricht, Titel aus UserAttr Title genutzt

Empfangen mir Websocket

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