Internetstatus in FHEM und Behandlung im Fehlerfall

Begonnen von Benni, 25 Dezember 2021, 23:27:17

Vorheriges Thema - Nächstes Thema

Benni

Ich habe (mir) mal wieder was gebastelt....

Neulich hatte ich nach dem Re-Connect nach der allnächtlichen Zwangstrennung der Internetverbindung eine nicht korrekt funktionierende solche.
Sprich, die Verbindung war da und auch mein Check in FHEM auf vorhandenes Internet per Ping auf 8.8.8.8 (der Google DNS) bekam Antwort.
Allerdings hat anscheinend das DNS nicht funktioniert und so hat sich FHEM letztendlich im Laufe der Nacht erst mal festgefahren und am nächsten Morgen hat kein Automatismus mehr funktioniert, obwohl FHEM noch lief.
Habe dann lediglich die Internetverbindung einmal getrennt und wieder neu aufgebaut und .... magic: alles hat sich von alleine aufgelöst!

Sowas konnte ich bisher nicht abfangen, ich dachte, den eigenen Router als DNS-Server in global zu setzen genügt. War halt nicht so. Also habe ich nach einer Lösung gesucht mit der ich das einfangen kann und dann im Fehler-Fall alle Devices, die Internet-Verbindung benötigen zu deaktivieren bis das Internet wieder voll funktionsfähig zur Verfügung steht.

Zunächst wird die Internetverbindung nicht mehr von FHEM selbst geprüft, sondern in einem Shell-Skript, welches per Cron-Job minütlich ausgeführt wird und die entsprechende Information an FHEM übermittelt.
Das Skript prüft den Ping auf 8.8.8.8 (Check ob internet "da" ist), den Ping auf google.com (check ob DNS funktioniert) und dann noch den Datenabruf von google.com (Prüfung ob http funktioniert). Alle 3 Zustände werden an FHEM gemeldet und zusätzlich ein Gesamtzustand (anyfailed), das einfach die Ergebnisse der Einzeltests aufsummiert (1 je fail).

Dazu gibt es bei mir in FHEM ein notify, welches zum einen, analog zu einem Dummy-Device, die o.g. Ergebnisse entgegennimmt und zum Anderen im Fehler-/Regenerationsfall die entsprechende Routine, die sich in einem separaten Modul (99_InetUtils.pm) befindet, aufruft.

Hier zunächst mal der Raw-Code des notify:


defmod nyExternalInternetStatus notify nyExternalInternetStatus:anyfailed.* { \
  if ($EVTPART1 > 0) {\
    Log3 'global',1,"$NAME: Internet failure: ".ReadingsVal($SELF,'last_fail','unknown');;\
    InetUtils::DevStateFromInternetState('fail');;\
  } else {\
    Log3 'global',1,"$NAME: Internet returned to normal operation!";;\
    InetUtils::DevStateFromInternetState('ok');;\
  }\
}

attr nyExternalInternetStatus userattr destPing destDns destHttp
attr nyExternalInternetStatus DbLogExclude .*
attr nyExternalInternetStatus alias Internet Status (extern)
attr nyExternalInternetStatus destDns google.com
attr nyExternalInternetStatus destHttp https://8.8.8.8
attr nyExternalInternetStatus destPing 8.8.8.8
attr nyExternalInternetStatus devStateIcon inactive:ios-off:active active:ios-on-blue:inactive .*ok:15px-green .*fail:15px-red ANY.0:15px-green ANY.*:15px-red
attr nyExternalInternetStatus event-on-change-reading .*
attr nyExternalInternetStatus group System
attr nyExternalInternetStatus icon it_internet
attr nyExternalInternetStatus room Allgemein->Presence,Allgemein->Umwelt,Uebersicht
attr nyExternalInternetStatus stateFormat ANY:anyfailed\
 ;\
[\
PING:ping\
DNS:dns\
HTTP:http\
]\
 ;\
 ;\
state\



Im Anhang finden sich noch das Modul (99_InetUtils.pm) und das Shell-Skript (inetchk-fhem.sh), das bei mir direkt unter /opt/fhem liegt.

Der Aufruf des Shell-Skriptes erfolgt als Cron-Job von root (sudo crontab -e). Da das Skirpt minütlich ausgeführt wird und es u.U. dazu kommen kann, dass das Skript länger läuft, erfolgt der Aufruf in der crontab mittels flock:


* * * * * /usr/bin/flock -w 60 /var/run/inetchk-fhem.lock /opt/fhem/inetchk-fhem.sh &>/dev/null


Wer andere Ziele als den Google-DNS ( 8.8.8.8 ) und google.com verwenden möchte, muss im Shell-Skript die entsprechenden Variablen (Zeilen 9-11) anpassen. kann diese per Aufrufparameter anpassen:

Zitat
inetchk-fhem.sh [-f <host>] [-t <port>] [-w <port>] [-d <device>]

   -f = fhemHost:   specify the fhem host name or ip address
   -t = telnetPort: port number of fhem telnet device
   -w = webPort:    port number of fhem's web ui
   -d = device:     name of fhem device (e.g. dummy) to report
                    results as readings get settings from

Der Code für crontab könnte dann auch so aussehen:


* * * * * /usr/bin/flock -w 60 /var/run/inetchk-fhem.lock /opt/fhem/inetchk-fhem.sh -f localhost -t 7072 -w 8083 -d nyExternalInternetStatus &>/dev/null


Wenn das Skript auf einem anderen Server ausgeführt wird, muss der Host-Name und ggf. der telnet-Port des FHEM-Ziel-Hosts angepasst werden (Zeilen 6+7) Die Anpassung erfolgt idealerweise dann per Aufrufparameter

Wer das Notify anders benennen möchte, als oben angegeben, muss zusätzlich im Shell-Skript zusätzlich in der Zeile 4 die Variable dummy anpassen. kann dies über den Aufrufparameter -d anpassen.

Aktuell geht das Skript davon aus, dass der FHEM-telnet ohne Username und Passwort aufgerufen werden kann. Bei Verwendung mit Username und Passwort muss der telnet-Aufruf im Skript entsprechend ergänzt werden.

Das zugehörige Modul (99_InetUtils.pm) gehört liegt bei mir in /opt/fhem/FHEM

Um Devices als relevante Devices, die bei Internet-Ausfall entsprechend deaktiviert werden sollen zu kennzeichnen, benötigt es noch folgende 'userattr'


inetFailDisableCmd inetFailEnableCmd inetFailCheckDisabledCmd inetFailDevice:1


Man kann die userattr entweder an jedem Device, das berücksichtigt werden soll, einzeln hinzufügen, oder einmal für alle Devices am global-Device.

!!! WICHTIG: Unbedingt darauf achten, das die bestehende userattr-Liste ergänzt (!!!) und nicht ersetzt wird. Das kann besonders am global-Device sonst für ernsthafte Schwierigkeiten sorgen

Anschließend können Geräte einfach durch setzen des Attributs inetFailDevice auf 1 als Gerät für die eventuelle Deaktivierung festgelegt werden.

Geräte, die eine Aktivierung/Deaktivierung per Attribut disable oder per Set acitve/inactive unterstützen werden automatisch erkannt. Dafür ist keine weitere Konfiguration am Device nötig.

Für Devices, die andere Methoden nutzen, muss das entsprechend definiert werden. Zum einen muss ein FHEM-Befehl für die Deaktivierung (inetFailDisableCmd) festgelegt werden, zum anderen ein entsprechende Gegenstück für die Reaktivierung (inetFailEnableCmd). Außerdem braucht es noch Code um den aktuellen Aktivierungszustand feststellen zu können (inetFailCheckDisabledCmd), dessen Ergebnis true oder false  (1/0) liefern muss.

Der Name des jeweiligen devices kann/muss dabei als Quasi-Variable $NAME in den Attributen angegeben werden

Beispielsweise können Calendar-Devices über das Attribut update=none deaktiviert werden. Dementsprechend sähen die o.g. Attribute bei einem Calendar-Device wie folgt aus:


inetFailCheckDisabledCmd {AttrVal('$NAME','update','') eq 'none'}
inetFailDevice 1
inetFailDisableCmd attr $NAME update none
inetFailEnableCmd deleteattr $NAME update


Wahrscheinlich schieße ich hier mit Kanonen auf Spatzen und auch die Funktionsweise wird nicht zu 100% gewährleistet sein. Wenn FHEM schon hängt und das notify im Problemfall schon gar nicht mehr verarbeitet, ist's halt Pech, aber bei einer 60 Sekunden-Taktung ist die Wahrscheinlichkeit für ein rechtzeitiges Abfangen recht hoch.
Sollt mal google.com oder der google-DNS nicht erreichbar sein, obwohl das Internet und DNS und http in Wirklichkeit gut funktionieren, wird halt alles unnötig deaktiviert, bis google wieder tut. Das könnte man ggf. durch Prüfung mehrerer Quellen im Shell-Skript verbessern.

Nichts desto trotz hatte ich Spaß bei der Umsetzung und wenn's jemand brauchen kann, um so besser!

Zu guter letzt noch ein Hinweis: Die im devStateIcon des o.g. notify verwendeten Icons habe ich zusätzlich mal in den Icon-Thread hier im Forum hochgeladen:

Viel Spaß damit!

gb#

Edit:

  • 26.12.2021
     

       
    • Anpassung im Shell-Skript: Die anyfail-Berechnung hat schätzungsweise nicht 100%ig funktioniert
    • Anpassung im notify, um auch das anyfailed-Reading im devStateIcon darzustellen
  • 27.12.2021
     

       
    • Anpassung im Shell-Skript: Zusätzlich wird nun noch der HTTP-Return-Code http_code an FHEM übermittelt
    • Anpassung im notify: setList verworfen, da die Readings-Werte aus dem Shellscript jetzt per setreading übermittelt werden
  • 29.12.2021
     

       
    • Anpassung im notify: Der Zeitpunkt des letzten Ausfalls wird im Reading lastFail gespeichert
  • 16.01.2022
     

       
    • Anpassung im notifyy: userAttr eigefügt um die Ziele für Ping, DNS und http über Attribute am Device konfigurieren zu können
       
    • Anpassungen im Shell-Skript:
         

               
      • Namen geändert in inetchk-fhem.sh
      • Die Ziele für Ping, DNS und http werden, sofern gesetzt, aus den jeweiligen Attributen übernommen. Defaults bleiben dabei die ursprünglich fest im Skript eingetragenen Werte
      • Der http-Request über curl hat einen verlängerten timeout (5 Sekunden) und führt ggf. 3 insgesamt Wiederholungen durch, falls ein Fehler auftritt
      • Das Skript kann nun auch per Aufrufparameter konfiguriert werden. So können der FHEM-Hostname (-f), der telnet-Port (-t) der Port des FHEM-Web-UI (-w) und das device (-d) in das die Ergebnisse geschrieben werden sollen
      • Neue Readings mit Textinformationen zum letzten Ergebnis (last_result) und zum letzten Ausfall (last_fail). Weiterhin wird der Pfadname des Skriptes (check-skript) gemeldet und schließlich werden noch die verwendeten Prüfziele mitgeteilt (dest-ping, dest-dns und dest-http)
       


Jamo

#1
Hallo Benni,
das Problem mit dem check auf Internet vs DNS (Internet ist da, aber der DNS funktioniert nicht), hatte ich auch, immer dann wenn mein AdGuard ausgefallen ist (DNS ist in der Fritzbox auf den Adguard eingestellt).
Ich habe das über ein HTTPMOD gelöst. Wenn DNS ok, liefert das HTTPMOD reading header 'HTTP' zurück, sonst nichts, also ''. Zuerst kommt aber immer ein'', sieht man im eventmonitor, deswegen das named sleep im notify. Wenn man also ein '' bekommt, und danach kein 'HTTP' mehr kommt, ist der DNS ausgefallen. Das kann man dann mit einem Notify weiterverarbeiten.
defmod InternetDNS HTTPMOD https://www.google.de/ 60
attr InternetDNS alias INTERNET / DNS
attr InternetDNS alignTime 00:00:00
attr InternetDNS event-on-change-reading header
attr InternetDNS group SERVER
attr InternetDNS reading01MaxAge 10
attr InternetDNS reading01Name header
attr InternetDNS reading01Regex (?s)(.*?)/

defmod InternetDNS_n notify InternetDNS:header:. sleep 15 sleep_iDNS quiet;;{myInternetDNS()}
Bullseye auf iNUC, Homematic + HMIP(UART/HMUSB), Debmatic, HUEBridge, Zigbee/ConbeeII, FB, Alexa (fhem-lazy), Livetracking, LaCrosse JeeLink, LoRaWan / TTN / Chirpstack

Benni

Ach ja! Falls das jemand nicht auf Anhieb durchschaut:  :P Die hier vorgestellte Lösung besteht genau genommen aus 2 Teilen, die natürlich auch völlig unabhängig voneinander, bzw. auch nur einzeln eingesetzt werden können.

Das ist zum einen der Teil mit der Ermittlung des Internet-Status und der Weitergabe dieser Information(en) an FHEM. Das ist das was im shell-Skript passiert.

Zum anderen die Kennzeichnung von beliebigen Internet-Devices und die Möglichkeit zum gemeinsamen setzen des Aktivierungszustandes. Das ist das was mit dem 99er-Modul gemacht wird.

Einzige Gemeinsamkeit ist im vorgestellten Fall das notify, das quasi die Aufgaben eines dummy-Device (1. Fall) und die Auswertung der Statusänderung, als Event (per notify in Fall 2) übernimmt.

Die Readings, die aus dem Shellskript kommen, kann ich aber im Prinzip in jedes beliebige Device "injizieren" lassen, da das Setzen im Skript ja per setreading geschieht.

gb#

booster

Möchte hier auch kurz meine Version vorstellen die ich für mich verfeinert habe.

defmod InternetDNS HTTPMOD https://www.google.de/ 60
attr InternetDNS alias INTERNET / DNS
attr InternetDNS alignTime 00:00:00
attr InternetDNS event-on-change-reading header
attr InternetDNS reading01MaxAge 10
attr InternetDNS reading01Name header
attr InternetDNS reading01Regex (?s)(.*?)/
attr InternetDNS room Gateways
attr InternetDNS stateFormat {if (ReadingsVal($name,"header","") eq "HTTP") { ReadingsVal($name,"state","online")}\
else {ReadingsVal($name,"state","offline")}}


Der Teil ist nicht wirklich notwendig...

defmod psInetDNS1 PRESENCE function {ReadingsVal("InternetDNS","header","") eq "HTTP" ? 1:0}
attr psInetDNS1 alias Internet / DNS
attr psInetDNS1 devStateIcon present:10px-kreis-gruen absent:10px-kreis-rot
attr psInetDNS1 event-on-change-reading state
attr psInetDNS1 icon it_internet
attr psInetDNS1 room _automation

Benni

Zitat von: booster am 02 Januar 2022, 18:58:54
Möchte hier auch kurz meine Version vorstellen die ich für mich verfeinert habe.

Der erste Teil ist ja letztendlich auch nur die Lösung, die Jamo bereits für die DNS-Prüfung vorgestellt hat.

Die Prüfung mittels HTTPMOD sollte, wenn ich es richtig verstanden habe ja auch grundsätzlich ohne Blockieren funktionieren, dafür MUSS aber am global device dann auch ein immer erreichbarer DNS eingetragen sein.

Der zweite Teil ist übrigens nicht nur "nicht wirklich erforderlich", sondern auch unnötig kompliziert. Man könnte auch ein readingsProxy dafür verwenden. Unnötig bleibt es dennoch.

Ich habe bei mir übrigens bewußt auf eine Prüfung der unterschiedlichen Internet-Status außerhalb von FHEM gesetzt!

gb#

booster

Das stimmt, ich habe nichts eigenes erfunden, nur für mich angepasst.

alanblack

Hallo Benni!

Besten Dank für dieses "Schnipsel"! Es ersetzt zwei PRESENCE-Devices, die bisher die Überwachung der Internet-Verbindung darstellten, aber deutlich ressourcenfressender waren.

Grüße
FHEM 6.0 auf raspi3&ODROID XU4 mit HMLAN und HM-MOD-RPI-PCB, LaCrosse via JeeLink, COC868 und CUL433, Xiaomi Aqara+div. Zigbee via deCONZ, Dooya via SIGNALDuino, ZWave mit Danalock
Jeder Witz kann ein Einzeiler sein mit genügend Semikolons

Benni

#7
Ich habe das Shell-Skript etwas erweitert und generalisiert.

- So kann es nun teilweise über Aufrufparameter und teilweise über FHEM-Attribute konfiguriert werden.
- Es werden zusätzliche Daten als Readings übertragen.
- Und nicht zuletzt hat sich der Name geändert

Auch das notify hat sich etwas geändert. Die Loginformationen sind nun etwas komprimierter und die Attribute für die Skript-Konfiguration sind als userAttr hinzugekommen
Ich habe den Beschreibungstext im ersten Post entsprechend angepasst. Und das neue Skript dort angehängt.

gb#