Da mir die tägliche Info-Mail, die man sich von einer Fritz!Box zusenden lassen kann, zu unpraktisch war, hatte ich erst mit dem Gedanken gespielt, diese von einem Server auswerten und umschreiben zu lassen, bin dann aber auf die TR-064-Schnittstelle gestoßen, die neuere Modelle bieten (welche nicht mehr via Telnet ansprechbar sind).

Im Folgenden ein Ansatz, per PHP auf Daten der Fritz!Box zugreifen zu können.
Ein anderer Ansatz, sich die Daten des Web-UIs abzugreifen, ist in diesem Artikel zu finden. Die beiden Ansätze ergänzen sich teilwese, weil ich über diese TR-064 leider nicht an alle gewünschte Daten rankomme.

Auf der Seite von AVM https://avm.de/service/schnittstellen/ werden alle vorhandenen Schnittstellen aufgelistet. Wie man aber genau darauf zugrift (Protokoll, Anmeldung, etc), musste ich erst etwas nachforschen.

Ich habe mir darauf folgenden PHP-Klasse zusammengebastelt, um einerseits kompakt die Services aufzulisten, welche auf auf der Service-Website von AVM in mehreren PDFs verlinkt sind, als auch mit einem Aufruf auf diese Services zugreifen zu können.

<?php

// v1.2 31.12.2023
// - getDeflections()    Alle eingerichteten Rufumleitungen auflisten
// - toggleDeflection()  Rufumleitung aktivieren/deaktivieren
//
// v1.1 19.2.2021
// - getCallList gibt nun auch den Unix-Timestamp eines Anrufs zurück
//   (die Zeitangabe der FritzBox mit 2stelliger Jahreszahl ist scheiße!)

class fritzbox {
   
   protected $fb_ip   = '';
   protected $fb_user = '';
   protected $fb_pass = '';
   
   protected $client   = null;
   protected $connected = null;
   
   public function __construct( $fb_ip = null, $fb_pass = null, $fb_user = null ) {
      if( $fb_ip === null || $fb_pass == null ) return false;
      if( $fb_user === null ) $this->fb_user = "fritz-api";
      $this->fb_ip = $fb_ip;
      $this->fb_pass = $fb_pass;
      $this->setCon( true );
      }
   
   public function client($controlURL, $uri) {
      if( $controlURL === null || $controlURL === "" ) return;
      if( $uri === null || $uri === "" ) return;
      try {
         $this->client = new SoapClient(
               null,
               array(
                     'location'   => "http://" . $this->fb_ip . ":49000" . $controlURL,
                     'uri'        => $uri,
                     'noroot'     => True,
                     'login'      => $this->fb_user,
                     'password'   => $this->fb_pass,
                     'trace'      => True,
                     'exceptions' => false
               )
         );
      } catch ( SoapFault $e ) {
//         echo $e->getMessage();
         echo $e->faultstring;
      }
   }
   
   public function setCon( $state = null ) {
      if( $state === null ) return false;
      $this->connected = $state;
      }
   
   public function getCon() {
      return $this->connected;
      }
   
   // Services auflisten
   public function getServices() {
        $url = "http://" . $this->fb_ip . ":49000/tr64desc.xml";
        $result = @simplexml_load_file( $url );
        $services = array();
      foreach ($result->device->serviceList->service as $s) {
         $services[] = array("typ"                   => "service",
                        "serviceType"           => (string) $s->serviceType,
                        "serviceId"           => (string) $s->serviceId,
                        "controlURL_eventSubURL" => (string) $s->controlURL,
                        "SCPDURL"              => (string) $s->SCPDURL,
                        "methods"              => $this->getActions((string) $s->SCPDURL)
                        );
         }
      foreach ($result->device->deviceList->device as $device)
         foreach ($device->serviceList->service as $s) {
            $services[] = array("typ"                => "device ".( (string) $device->deviceType),
                           "serviceType"          => (string) $s->serviceType,
                           "serviceId"             => (string) $s->serviceId,
                           "controlURL_eventSubURL" => (string) $s->controlURL,
                           "SCPDURL"              => (string) $s->SCPDURL,
                           "methods"              => $this->getActions((string) $s->SCPDURL)
                           );
            }
      return $services;
      }

   public function getActions($service) {
        $url = "http://" . $this->fb_ip . ":49000/$service";
        $result = @simplexml_load_file( $url );
        $actions = array();
        //print_r($result);
      foreach ($result->actionList->action as $a) {
         $arguments = $out_vars = array();
         if (is_object($a->argumentList->argument)) foreach ($a->argumentList->argument as $arg)
            if ((string) $arg->direction == "in")
               $arguments[] = (string) $arg->name;
            else
               $out_vars[] = (string) $arg->name;
         $actions[] = array("name" => (string) $a->name, "arguments" => $arguments, "out_vars" => $out_vars);
         }
        return $actions;
      }  
   
   public function callService($controlURL, $serviceType, $action, $args=array()) {
      $this->client($controlURL, $serviceType);
      $data = $this->client->__soapCall($action, $args);
      return $data;
      }
   
   public function getActiveWLANClients() {
      $hosts = array();
      foreach (array(1 => "2.4 GHz", 2 => "5 GHz", 3 => "2.4 GHz (Gast)", 4 => "5 GHz (Gast)") as $wlan_no => $wlan_typ) {
         $anz = $this->callService("/upnp/control/wlanconfig$wlan_no", "urn:dslforum-org:service:WLANConfiguration:$wlan_no", "GetTotalAssociations");
         if (is_object($anz) && get_class($anz) == "SoapFault") continue; // Gast-WLAN ggf nicht an
         for ($i = 0; $i < $anz; $i++) {
            $info1 = $this->callService("/upnp/control/wlanconfig$wlan_no", "urn:dslforum-org:service:WLANConfiguration:$wlan_no", "GetGenericAssociatedDeviceInfo", array(new SoapParam($i,'NewAssociatedDeviceIndex')));
            $info2 = $this->callService("/upnp/control/hosts", "urn:dslforum-org:service:Hosts:1", "GetSpecificHostEntry", array(new SoapParam($info1['NewAssociatedDeviceMACAddress'],'NewMACAddress')));
            $hosts[] = array("IP"         => $info1['NewAssociatedDeviceIPAddress'],
                         "name"         => (!is_object($info2) || get_class($info2) != "SoapFault") ? $info2['NewHostName'] : "k/A",
                         "MAC"         => $info1['NewAssociatedDeviceMACAddress'],
                         "MB_S"       => $info1['NewX_AVM-DE_Speed'],
                         "signal"      => $info1['NewX_AVM-DE_SignalStrength'],
                         "interface"   => (!is_object($info2) || get_class($info2) != "SoapFault") ? $info2['NewInterfaceType'] : "k/A",
                         "band"         => $wlan_typ
                         );
            }
         }
      return $hosts;
      }

   public function getAllClients($activeOnly=true) {
      $hosts = array();
      $anz = $this->callService("/upnp/control/hosts", "urn:dslforum-org:service:Hosts:1", "GetHostNumberOfEntries");
      for ($x=0; $x<$anz;$x++) {
         $host = $this->callService("/upnp/control/hosts", "urn:dslforum-org:service:Hosts:1", "GetGenericHostEntry", array(new SoapParam($x,'NewIndex')));
         if (is_soap_fault($host) || $activeOnly && $host['NewActive'] != 1) continue;
         $hosts[] = array("IP"         => $host['NewIPAddress'],
                      "name"         => $host['NewHostName'],
                      "MAC"         => $host['NewMACAddress'],
                      "interface"   => $host['NewInterfaceType'],
                      "aktiv"      => $host['NewActive']
                      );
         }
      return $hosts;
      }

   public function getCallList($days=0) {
      $url = $this->callService("/upnp/control/x_contact", "urn:dslforum-org:service:X_AVM-DE_OnTel:1", "GetCallList");
      $xml = @simplexml_load_file( $url."&days=$days");
      $list = array();
      foreach($xml->Call as $call) {
         $name = (string) $call->Device;
         if (preg_match("/FRITZ!App Fon \((.*?)\)/", $name, $m)) $name = $m[1];
         $zeit = preg_replace("/(\d+)\.(\d+)\.(\d+) (\d+):(\d+)/", "20$3-$2-$1 $4:$5:00", $call->Date);
         $list[] = array("datum"       => date("d.m.Y H:i", strtotime($zeit)),
                         "timestamp"   => strtotime($zeit),
                         "typ"         => (int) $call->Type, // 1+2 Ankommend, 3: Ausgehend
                         "extern"      => ((int) $call->Type == 3) ? (string) $call->Called : (string) $call->Caller,
                         "extern_name" => (string) $call->Name,
                         "intern"      => ((int) $call->Type == 3) ? (string) $call->CallerNumber : (string) $call->CalledNumber,
                         "intern_name" => $name,
                         "dauer"       => (string) $call->Duration
                     );
         }
      return $list;
      }

  public function getDeflections() {
    $deflections = $this->callService("/upnp/control/x_contact", "urn:dslforum-org:service:X_AVM-DE_OnTel:1", "GetDeflections");
    $result = @simplexml_load_string($deflections);
    $defs = array();
    foreach ($result->Item as $i) $defs[] = array("id" => (int) $i->DeflectionId, "enabled" => (int) $i->Enable, "from" => (string) $i->Number, "to" => (string) $i->DeflectionToNumber, "mode" => (string) $i->Mode);
    return $defs;
    }

  public function toggleDeflection($id, $on) {
    $result = $this->callService("/upnp/control/x_contact", "urn:dslforum-org:service:X_AVM-DE_OnTel:1", "SetDeflectionEnable", array(new SoapParam($id, "NewDeflectionId"), new SoapParam($on ? 1 : 0, "NewEnable")));
    print_r($result);
    }
   }
?>

Zwei Beispiels-Methoden, um alle verbundenen WLAN-Clients oder alle Clients (egal ob WLAN oder LAN) aufzulisten, sind in die Klasse integriert (getActiveWLANClients und getAllClients).

Mit der Funktion getServices() werden alle verfügbaren Services der Fritz!Box aufgelistet.

include 'fritz_klassen_datei.php';
$fritz = new fritzbox("192.168.178.1", "User-Kennwort", "FritzBox-User");
// Login nur mit Kennwort funktioniert ab Fritz!OS 7.28 nicht mehr
// Ab Fritz!OS 7.28 muss unbedingt auch noch der Fritz!Box-Benutzer
// mitgegeben werden.
// $fritz = new fritzbox("192.168.178.1", "UI-Kennwort");
print_r($fritz->getServices());

Um Infos aus der Fritz!Box zu extrahieren oder Einstellungen zu ändern, sind die controlURL/eventSubURL und serviceType relevant.
Außerdem, ob bei den einzelnen Methoden Argumente mitgegeben werden müssen oder nicht.

Hier ein paar Beispiele:

// Allgemeine Infos über die Fritz!Box ausgeben:
print_r($fritz->callService("/upnp/control/deviceinfo", "urn:dslforum-org:service:DeviceInfo:1", "GetInfo"));

// Ereignis-Log anzeigen.
// Hier wird der Methode GetDeviceLog noch ein Attribut mitgegeben.
// (Allerdings scheinen mir hier nur ein Bruchteil der Ereignisse angezeigt
// zu werden, die ansonsten im Fritz!Box-Webinterface unter System > Ereignisse
// angezeigt werden...doch dazu später mehr...)
print_r($fritz->callService("/upnp/control/deviceinfo", "urn:dslforum-org:service:DeviceInfo:1", "GetDeviceLog", array("NewDeviceLog")));

// Weitere Informationen zu einem speziellen bekannten Netzwerkgerät anzeigen
// Hier wird der Methode GetSpecificHostEntry noch ein Parameter
// (die MAC-Adresse des Gerätes, für welches man die Infos möchte) mitgegeben.
print_r($fritz->callService("/upnp/control/hosts", "urn:dslforum-org:service:Hosts:1", "GetSpecificHostEntry", array(new SoapParam('00:xx:22:FB:8B:YY','NewMACAddress'))));

Leider bin ich nicht dahinter gekommen, wieso das Ereignis-Log über diese API im Gegensatz zum Ereignis-Log der Fritz!Box-UI beschnitten ist und nur einen Bruchteil der Ereignisse anzeigt.
Auch die Werte, wie viele Daten an einem Tag gesendet und empfangen wurde, kommen mir spanisch vor.

Einen anderen Ansatz zum Anzapfen der Fritz!Box, die sicherlich einen Blick wert ist, bietet noch ein Entwickler auf seiner Homepage:
http://www.mengelke.de/Projekte/FritzBox-Tools
https://mengelke.de/.dg

Das hieraus extrahierte Ereignis-Log ist vollständig und führt auch Informationen über an-/abgemeldete Netzwerkgeräte, VPN-Verbindungen, FTP-Zugriffe auf, die mir bei der TR-064-Schnittstelle seltsamerweise fehlen.