FHEM Forum

FHEM => Codeschnipsel => Thema gestartet von: PSI69 am 04 September 2026, 09:44:36

Titel: Abfrage / Einbindung TP-Link AP für Anwesenheitserkennung
Beitrag von: PSI69 am 04 September 2026, 09:44:36
Moin Zusammen!

Ich betreibe seit geraumer Zeit 6 dieser AP (EAP235-Wall/EAP725-Wall) ohne Controller. Sie kommen mit VLANs klar, spannen mehrere WLANs auf, etc. - es gab halt diverse Gründe die AVM Technik rauszuwerfen. Nur ein 'fertiges' Modul diese APs abzufragen und für eine Anwesenheitserkennung zu nutzen, habe ich nicht finden können...

Nach mehreren Versuchen einer Abfrage über ssh, bin ich dann bei SNMP gelandet (hier einer der 6 als Muster; Community & ipAddress anpassen):
defmod AP_EXT_CARPORT CustomReadings
attr AP_EXT_CARPORT userattr ipAddress Community
attr AP_EXT_CARPORT Community xxx
attr AP_EXT_CARPORT alias TP-Link WLAN AP Carport
attr AP_EXT_CARPORT event-on-change-reading .*
attr AP_EXT_CARPORT group Access Point
attr AP_EXT_CARPORT icon it_router
attr AP_EXT_CARPORT interval 60
attr AP_EXT_CARPORT ipAddress 192.168.x.x
attr AP_EXT_CARPORT readingDefinitions SystemName:{ my $ip=AttrVal($name,"ipAddress","");;;; my $com=AttrVal($name,"Community","public");;;; qx(snmpget -Oqv -v 2c -c '$com' $ip 1.3.6.1.2.1.1.5.0) },SystemLocation:{ my $ip=AttrVal($name,"ipAddress","");;;; my $com=AttrVal($name,"Community","public");;;; qx(snmpget -Oqv -v 2c -c '$com' $ip .1.3.6.1.2.1.1.6.0) },SystemDescription:{ my $ip=AttrVal($name,"ipAddress","");;;; my $com=AttrVal($name,"Community","public");;;; qx(snmpget -Oqv -v 2c -c '$com' $ip .1.3.6.1.2.1.1.1.0) },Uptime:{ my $ip=AttrVal($name,"ipAddress","");;;; my $com=AttrVal($name,"Community","public");;;; my $raw=qx(snmpget -Oqv -v 2c -c '$com' $ip .1.3.6.1.2.1.1.3.0);;;; $raw =~ s/(\d+):(\d+):(\d+):(\d+)\.\d+/$1 Tage, $2 h $3 min/;;;; $raw =~ s/^(\d+):(\d+):(\d+)\.\d+/0 Tage, $1 h $2 min/;;;; $raw },Clients:{ my $ip=AttrVal($name,"ipAddress","");;;; my $com=AttrVal($name,"Community","public");;;; qx(/opt/fhem/FHEM/get_ap_clients.sh $ip '$com') },ClientCount:{ my $cl = ReadingsVal($name,"Clients","NONE");;;; $cl eq "NONE" ? 0 : scalar(split(",", $cl)) }
attr AP_EXT_CARPORT room Infrastruktur->Netzwerk
attr AP_EXT_CARPORT stateFormat { "Clients: " . ReadingsVal($name,"ClientCount",0) . " | Uptime: " . ReadingsVal($name,"Uptime","unknown") }

setstate AP_EXT_CARPORT Clients: 1 | Uptime: 9 Tage, 19 h 55 min
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 ClientCount 1
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 Clients aabbccddeeff
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 SystemDescription "Linux EAP235-Wall 3.10.108 #1 SMP Wed Dec 3 15:57:51 CST 2025 mips"
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 SystemLocation "EXT-CARPORT"
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 SystemName "AP-EXT-CARPORT"
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 Uptime 9 Tage, 19 h 55 min
setstate AP_EXT_CARPORT 2026-09-04 09:19:07 state OK

Hier dann ein Dummy für die Anzeige der Summe aller WLAN Clients:
defmod ALL_WLAN_Clients dummy
attr ALL_WLAN_Clients alias Summe aller WLAN Clients
attr ALL_WLAN_Clients group Access Point
attr ALL_WLAN_Clients icon wifi_online
attr ALL_WLAN_Clients room Infrastruktur->Netzwerk
attr ALL_WLAN_Clients stateFormat ClientCount

... das notify zum Schreiben des Dummy:
defmod nt_AP_ClientCount notify AP_.*:ClientCount:.* {\
  my @aps = ::devspec2array("AP_.*");;\
  my $sum = 0;;\
  \
  foreach my $ap (@aps) {\
    $sum += ReadingsNum($ap, "ClientCount", 0);;\
  }\
  \
  fhem("setreading ALL_WLAN_Clients ClientCount $sum");;\
}
attr nt_AP_ClientCount alias Benachrichtigung ClientCount der TP-Link APs
attr nt_AP_ClientCount devStateIcon {ReadingsVal($name, "state", "inactive") eq "active" ? ".*:ios-on-blue:inactive" : ".*:ios-off:active"}
attr nt_AP_ClientCount group Benachrichtigung
attr nt_AP_ClientCount icon ring
attr nt_AP_ClientCount room Infrastruktur->Netzwerk

Die Namen der AP beginnen alle mit 'AP_', meine Roommate's mit 'rr_'. Letztere benötigen ein userattr 'MAC' mit entsprechendem Inhalt.

Jetzt noch auf die Änderungen der Clients Readings der AP reagieren:
defmod di_p_RR_PRESENT DOIF subs {\
  sub $SELF_DebugMessage {\
     my ($Message)=@_;;\
     my $MODUL="$SELF";;\
     my $senddebugmessages = AttrVal("$SELF","senddebugmessages","");;\
     \
    if ($senddebugmessages eq "1") {\
      Log 3, $MODUL.": "."Message: '$Message'";;\
    }\
    if ($senddebugmessages eq "2") {\
      fhem("msg push @[Messages.Type:mt_Bewohner] '$MODUL': Message: '$Message'");;\
      Log 3, $MODUL.": "."Message: '$Message'";;\
    }\
  }\
\
  # Funktion für Timer-Ablauf\
  sub $SELF_SetAbsent($) {\
    my ($person) = @_;;\
    $SELF_DebugMessage("Timer abgelaufen: Setze $person auf abwesend");;\
    fhem_set("$person state absent");;\
  }\
}\
\
\
## WebCmd\
{ if (["^$SELF$:^Ein$"]) {\
    set_State("on");;\
  }\
}\
{ if (["^$SELF$:^Aus$"]) {\
    set_State("off");;\
  }\
}\
\
\
  ## DOIF enabled\
{ if (!([$SELF:state] eq "off")\
  ## Event Trigger Reading Clients aller AP\
  and ["^AP_:^Clients:"] \
) {\
##    $SELF_DebugMessage("Trigger erhalten, starte Prüfung...");;\
    \
    my @all_aps = ::devspec2array("AP_.*");;\
    my @all_bewohner = ::devspec2array("rr_.*");;\
    \
##    $SELF_DebugMessage("Gefundene APs: " . join(", ", @all_aps));;\
##    $SELF_DebugMessage("Gefundene Bewohner: " . join(", ", @all_bewohner));;\
    \
    ## Bewohner\
    foreach my $person (@all_bewohner) {\
      my $mac = AttrVal($person, "MAC", "");;\
      next if ($mac eq "");;\
      \
      ## MAC-Adresse für den Vergleich vorbereiten\
      $mac = lc($mac);;\
      $mac =~ tr/://d;;\
      \
      my $found_at_any_ap = 0;;\
      my $matching_ap_alias = "";;\
      \
      ## alle APs für Bewohner prüfen\
      foreach my $current_ap (@all_aps) {\
        my $client_list = ReadingsVal($current_ap, "Clients", "NONE");;\
        next if ($client_list eq "NONE");;\
        \
        if ($client_list =~ /$mac/) {\
          $found_at_any_ap = 1;;\
          $matching_ap_alias = AttrVal($current_ap, "alias", $current_ap);;\
          \
          # Treffer erzielt, AP-Schleife für diese Person abbrechen\
          last;;\
        }\
      }\
      \
      ## Aktuelle Werte des Bewohners auslesen\
      my $curr_presence = ReadingsVal($person, "presence", "unknown");;\
      my $curr_location = ReadingsVal($person, "location", "unknown");;\
      my $alias_person = AttrVal($person, "alias", $person);;\
      \
      if ($found_at_any_ap == 1) {\
        ## FALL 1: Bewohner ist an mindestens einem AP eingeloggt\
        $SELF_DebugMessage("MATCH -> $alias_person ist am AP '$matching_ap_alias' angemeldet.");;\
        \
        ## ggf Timer Löschen\
        del_Exec("timer_$person");;\
        \
        if ($curr_presence ne "present") {\
          $SELF_DebugMessage("Setze $person auf present");;\
          fhem_set("$person state home");;\
        }\
        \
      } else {\
        ## FALL 2: Bewohner wurde an KEINEM AP gefunden\
        $SELF_DebugMessage("NO MATCH -> $alias_person wurde an keinem AP gefunden.");;\
        \
        ## Timer starten, wenn Person aktuell "present"\
        if ($curr_presence eq "present") {\
          \
          ## Check ob der Timer läuft\
          if (!get_Exec("timer_$person")) {\
            $SELF_DebugMessage("Starte Abwesenheits-Timer (5 Min) für $alias_person");;\
            \
            ## Timer Starten\
            set_Exec("timer_$person", 300, '$SELF_SetAbsent("' . $person . '")');;\
          }\
        }\
      }\
    }\
  }\
}
attr di_p_RR_PRESENT userattr senddebugmessages
attr di_p_RR_PRESENT alias Benachrichtigung Bewohner Anwesend
attr di_p_RR_PRESENT devStateIcon on:ios-on-blue:Aus : off:ios-off:Ein H.*:control_arrow_up R.*:control_arrow_down .*:noIcon
attr di_p_RR_PRESENT event-on-change-reading .*
attr di_p_RR_PRESENT group Benachrichtigung
attr di_p_RR_PRESENT icon helper_doif
attr di_p_RR_PRESENT room Infrastruktur->Bewohner,Infrastruktur->Netzwerk
attr di_p_RR_PRESENT senddebugmessages 1
attr di_p_RR_PRESENT webCmd Ein:Aus

Hier noch der Dummy für die Konfiguration der Empfänger - reduziert auf den mt_Bewohner (bei senddebugmessages 2 senden meine DOIFs Nachrichten per Messenger):
defmod Messages.Type dummy
attr Messages.Type userattr mt_Bewohner
attr Messages.Type alias Konfiguration Empfänger für Benachrichtigungen
attr Messages.Type event-on-change-reading .*
attr Messages.Type group Konfiguration Benachrichtigungen
attr Messages.Type icon edit_settings
attr Messages.Type mt_Bewohner rr_Peter
attr Messages.Type room Infrastruktur->Messenger

... fertig. Ich hoffe, ich habe nichts vergessen?!?
Peter
Titel: Aw: Abfrage / Einbindung TP-Link AP für Anwesenheitserkennung
Beitrag von: PSI69 am 04 September 2026, 09:56:33
... und klar fehlte noch etwas:

/opt/fhem/FHEM/get_ap_clients.sh

#!/bin/bash

# Prüfen, ob beide Argumente übergeben wurden
if [ -z "$1" ] || [ -z "$2" ]; then
    echo "Fehler: Parameter fehlen."
    echo "Nutzung: $0 <IP-Adresse> <Community>"
    exit 1
fi

# VAR aus Argumenten
TARGET="$1"
COMMUNITY="$2"

# snmpwalk:
# - Daten holen (snmpwalk | tr | tr ) als eine Zeile
# - nach Zeilen trennen (| sed 's/\.1\.3\.6/\n&/g' )
# - grep nach HEX-String und Leerzeichen entfernen (| grep "Hex-STRING:" | awk -F "Hex-STRING: " '{print $2}' | tr -d ' ')
# - Zeichenketten in MAC umwandeln (| while read -r hex; do echo "$hex" | xxd -r -p; echo ""; done)
# - MACs Komma separiert in eine Zeile (| tr -d '-' | tr -d '\000' | tr '[:upper:]' '[:lower:]' | tr '\n' ',' | sed 's/,$//')

# Abfrage ausführen und Ergebnis in Variable
RESULT=$(snmpwalk -On -v 2c -c "$COMMUNITY" "$TARGET" 1.3.6.1.4.1.11863.10.1.1.2.1.2 | tr -d '\n' | tr -d '\r' | sed 's/\.1\.3\.6/\n&/g' | grep 'Hex-STRING:' | awk -F 'Hex-STRING: ' '{print $2}' | tr -d ' ' | while read -r hex; do echo "$hex" | xxd -r -p; echo ""; done | tr -d '-' | tr -d '\000' | tr '[:upper:]' '[:lower:]' | tr '\n' ',' | sed 's/,$//')

# Prüfen, ob das Ergebnis leer ist
if [ -z "$RESULT" ]; then
    echo "NONE"
else
    echo "$RESULT"
fi