Speedport Hybrid Router auslesen, Script für IP-Symcon vorhanden

Begonnen von Kiki99, 24 Januar 2017, 15:18:50

Vorheriges Thema - Nächstes Thema

Kiki99

Hallo Forum,
der Speedport Hybrid Router der Telekom hat eine sehr eigensinnige Oberfläche und der Login ist irgendwie sehr umständlich. Ich habe schon viel gefunden aber nichts funktioniert, um die Anrufliste ohne manuelle Eingaben herunter zu laden.

Nun habe ich ein Script gefunden, dass vielversprechend aussieht aber es ist für IP-Symcon und ich verstehe nicht genug davon, um es auf meinem Ubuntu Rechner zum laufen zu bringen. Die Sciptsammlung findet sich unter https://github.com/florianprobst/ips-speedport/blob/master/README.md

Bin für jede Hilfe dankbar, mit der ich die Anrufliste in FHEM rein bekomme  ;D

andies

FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

Also das ist jetzt nicht die Lösung Deines Problems, aber ich schreibe mal hin, was ich habe - denn das funktioniert. Zuerst lese ich mit python auf der Kommandozeile die Anrufliste aus. Dabei beschränke ich mich auf die ersten sechs Anrufe, das reicht mir (kann man leicht ändern, vorab Path und Kennwort anpassen!):


#!/usr/bin/python3

import requests
import hashlib
import re
import sys
import json


#formatting and printing single call
def print_call_data(str1, str2):
    # when do we need newlines when printing?
    nwl = (str1 == "takencalls_duration") or (str1 == "dialedcalls_duration") or (str1 == "missedcalls_who")
    # write duration using minutes and seconds
    if (str1 == "takencalls_duration") or (str1 == "dialedcalls_duration"):
         str2_min = int(str2) // 60
         str2_sec = int(str2) % 60
         str2 = str(str2_min) + "' " + str(str2_sec) + "''"
    # remove useless entries (do this after newlines and recalculation!)
    clutter = ["takencalls_", "missedcalls_", "dialedcalls_", "date", "time", "who"]
    for s in clutter:
        str1 = re.sub(s, '', str1)
    str1 = re.sub('duration', 'Dauer', str1)
    str1 = re.sub('id', 'Nr', str1)
    # finally print
    if nwl:
         print(str1, str2)
    else:
        print(str1, str2, end=" ")
    return
   

#printing set of calls
def print_calls(i, str, denom, counter, no):
    if (i["varid"] == str):
        counter += 1
        if (counter == 1):
            print (denom)
        if (counter < no+1):
            for j in i["varvalue"]:
                print_call_data(j["varid"],j["varvalue"])
    return counter
   
def main():
    url_router = 'http://192.168.2.1'
    passwd_router = 'XXXXXXXX'
   
    no_printed_calls = 6 #how many calls shall be printed?
    counter_taken = 0 #three counters
    counter_missed = 0
    counter_dialed = 0
       
    with requests.Session() as s:
        page = s.post(url_router + '/data/Login.json', data={'password': hashlib.md5(bytes(passwd_router, 'utf-8')).hexdigest(), 'showpw': '0', 'httoken': ''})
        httoken = re.findall('_httoken = (\d*);', s.get(url_router + '/html/content/overview/index.html?lang=de').text)[0]
       
        #Telefonate
        page = s.get(url_router + '/data/PhoneCalls.json', params={"_tn": httoken}, headers={'Referer': url_router + '/html/content/overview/index.html?lang=de'})
        page_decoded = json.loads(page.content.decode('utf-8'))
       
        for i in page_decoded:
             counter_taken = print_calls(i, "addtakencalls", "--angenommen", counter_taken, no_printed_calls)         
             counter_missed = print_calls(i, "adddialedcalls", "--angerufen", counter_missed, no_printed_calls)         
             counter_dialed = print_calls(i, "addmissedcalls", "--verpasst", counter_dialed, no_printed_calls)         

if __name__ == '__main__':
    main()
   


Diese Datei muss auf der Kommandozeile fehlerfrei ausführbar sein ("python3 speedport.py", speedport.py muss dabei bei mir in /opt/fhem). Damit hat man erst einmal die Daten. Damit die dann in FHEM angezeigt werden, habe ich ein reading für mein dummy-device "Telefon" angelegt. Das sieht dann so aus:

defmod Telefon dummy
attr Telefon icon it_telephone
attr Telefon stateFormat &nbsp;;
attr Telefon userReadings anrufliste
attr Telefon webCmd einlesen


Der Button "einlesen" löst nun ein notify aus, mit dem der eigentliche Lesevorgang erfolgt:

defmod TelefonEinlesen notify Telefon.einlesen {my $returnCode = qx(python3 speedport.py);;fhem("setreading Telefon anrufliste $returnCode");;}


Klickt man auf "Einlesen", wird dann das setreading neu gesetzt und enthält jetzt die Anrufliste. Mir genügt das, weil ich die Daten selbst dann mit TelegramBot auslese und dann reicht es, wenn sie im setreading bleiben.

Ich habe mir Python selbst "beigebracht" und nutze FHEM seit zwei Wochen, also der Code ist sicherlich voller No-Go's, gern höre ich mir hilfreiche Hinweise an. Aber wie gesagt: ich bin selbst dummy.
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

Kiki99

Hi andies,
um ehrlich zu sein klingt das nach der perfekten Lösung  ;D

Leider bekomme ich aber folgende Antwort wenn ich es mit python3 ausführen lasse:
python3 '/home/kiki/Downloads/speedport.py'
Traceback (most recent call last):
  File "/home/kiki/Downloads/speedport.py", line 66, in <module>
    main()   
  File "/home/kiki/Downloads/speedport.py", line 46, in main
    httoken = re.findall('_httoken = (\d*);', s.get(url_router).text)[0]
IndexError: list index out of range

Das Kennwort habe ich natürlich geändert, die IP ist bei mir die gleiche.
Hast Du eine Idee, woran das liegen kann?

Danke im voraus, bin jetzt echt nervös, könnte ja doch noch klappen, bin kurz vorm aufgeben  >:(

lg Kiki

andies

Hmm, ich bin natürlich auch Anfänger. Es ist diese Zeile
httoken = re.findall('_httoken = (\d*);', s.get(url_router).text)[0]
und soweit ich das kapiert habe (ich denke mal laut), wird dabei zuerst die Webseite aufgerufen ("s.get"), dadurch wird der token generiert und dann wird mit re.findall (steht anscheinend für regexpression und die entsprechende library) gesucht, wo _httoken steht und daraus der token ausgelesen. Wenn nun "index out of range" ist und man dabei alle entsprechenden Einträge gesucht hat, gibt es wahrscheinlich keinen einzigen. Also zu deutsch: Anscheinend wird die Einstiegsseite des Routers nicht oder nicht richtig aufgerufen. Er findet schlichtweg den token nicht.

Also ich würde dann mit dem debugen beginnen, das kostet halt Zeit. Ich habe in solchen Fällen immer print(und_dann_das,was_mich_interessierte) hineingeschrieben), wobei die Fehler danach uU zum Abbruch des Programmes führen. Das muss man dann auskommentieren, Fehler erkennen, wieder einkommentieren usw usf.
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

Das ist der allererste Aufruf der Seite, da wird das Passwort nicht benutzt (das kommt erst in dem Post-Aufruf danach). Also muss es an der Adresse (192... usw.) liegen.
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

mahowi

Ich habe Dein Skript auch mal ausprobiert, aber ich bekomme dieselbe Fehlermeldung wie Kiki99:

mahowi@T60:~/bin$ python3 -d speedport.py
Traceback (most recent call last):
  File "speedport.py", line 66, in <module>
    main()
  File "speedport.py", line 46, in main
    httoken = re.findall('_httoken = (\d*);', s.get(url_router).text)[0]
IndexError: list index out of range


IP und Paßwort sind korrekt, mit denselben Daten komme ich mit dem Browser auf den Speedport (Hybrid). Kann es sein, daß Du einen Speedport W724V hast und das Skript nur damit funktioniert?
CUBe (MAX): HT, FK | CUBe (SlowRF): ESA2000WZ
JeeLink: LaCrosse | nanoCUL433: Smartwares SHS-51001-EU, EM1000GZ
ZME_UZB1: GreenWave PowerNode, Popp Thermostat | SIGNALDuino: HE877, X10 MS14A, Revolt NC-5462,  IT Steckdosen + PIR
tado° | Milight | HUE, Lightify | SmarterCoffee

andies

Ja, ich habe einen 724V, sorry! Aber wieso sollte es nur damit funktionieren?

Habt Ihr mal die Seiten genau angeschaut, die da aufgerufen werden? Vielleicht sind die Links etwas anders?
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

Ich habe ja diese js auch nur "reverse engineered verstanden" (und letzteres Wort müsste in doppelte Anführung). Also es geht ungefähr so. Am Anfang ruft man die Webseite http://192.168.2.1 auf.
        s.get(url_router)

Auf dieser Seite ist der token versteckt, der die ganze Zeit mitgeschleppt werden muss. Also extrahieren wir den token
        httoken = re.findall('_httoken = (\d*);', s.get(url_router).text)[0]


Wenn schon das nicht geht - könnte es sein, dass bei Euch der token erst nach der Eingabe angelegt wird? Dann müsste der entsprechende Befehl erst dann erscheinen, wenn man das Passwort eingegeben und abgeschickt hat, also nach
        page = s.post(url_router + '/data/Login.json', data={'password': hashlib.md5(bytes(passwd_router, 'utf-8')).hexdigest(), 'showpw': '0', 'httoken': ''})

FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

Kiki99

Hi,
wir scheinen der Sache doch näher zu kommen, im Quelltext der Loginseite finde ich jedoch nur
  var csrf_token = "nulltoken";

Kann es sein, dass dann also die Suchzeile angepasst werden müssze zu
httoken = re.findall('csrf_token = (\d*);', s.get(url_router).text)[0]
Das \d* kann bleiben? (Das sagt mir nichts  ???)

Und zusätzlich müsste es hinter den Login verschoben werden, weil es wohl noch nicht gesetzt ist dachte ich zuerst aber selbst nach dem Login hat die Zeile den gleichen Inhalt also nulltoken?!  :-\

Hat jemand eine Idee dazu? :)

andies

Zitat von: Kiki99 am 22 März 2017, 17:16:07
Kann es sein, dass dann also die Suchzeile angepasst werden müssze zu
httoken = re.findall('csrf_token = (\d*);', s.get(url_router).text)[0]
Das \d* kann bleiben? (Das sagt mir nichts  ???)
Also \d* weist nur die Suche an, wie zu suchen ist. Man sucht zum einen das Wort mit dem token, das Gleichheitszeichen und danach Zahlen - und (\d*) heißt zum einen ein separates Wort, getrennt durch Leerzeichen (das sind die Klammern), dann Zahlen (das ist durch das Symbol \d beschrieben) und davon mindestens eine, dann aber beliebig viele (der Stern *). Ersetze doch mal das durch csrf_token, denn das kommt ja auch in dem anderen Text, den ich oben geschickt habe, vor:
def get_reboot_csrf():
html = open_site(speedport_url + problem_handling, None)
start = html.find("csrf_token")

Im Zweifel nehmt Ihr einfach diese Code (https://github.com/dordnung/Speedport-Hybrid-Rebooter/blob/master/speedport-rebooter.py) und verändert den?
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

Und auch hier (https://github.com/Stricted/speedport-hybrid-php-api/blob/master/lib/trait/Login.class.php) steht csrf_token:
$path = 'data/Login.json';
$this->hash = hash('sha256', $this->challenge.':'.$password);
$fields = array('csrf_token' => 'nulltoken', 'showpw' => 0, 'password' => $this->hash);
$data = $this->sendRequest($path, $fields);
$json = $this->getValues($data['body']);
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

Ich habe inzwischen weiter gemacht und mir die Telefondaten auf mein dashboard geholt. Dazu habe ich in 99_myUtils.pm folgende Funktion definiert:

our %myUtils_telefonbuch = (
"012345678" , "Name1",
"087654321" , "Name2" #usw
);

sub FormatiereTelefonliste($) {
my ($liste) = $_[$0];
$liste =~ s/Nr \d  //g; #remove Nr 0 etc. at the beginning
$liste =~ s/\*\*6\d/-intern-/g; # cannot be handled in Telefonbuch because it is a nested regex
for my $nummer (keys %myUtils_telefonbuch) { #replace known numbers
$liste =~ s/$nummer/$myUtils_telefonbuch{$nummer}/g;
}
$liste =~ s/\n/<br>/g; # add newlines
return $liste
}

Dann gibt es eine ReadingsGroup der Form
defmod TelefonListe readingsGroup Telefon:anrufliste
attr TelefonListe group Telefon
attr TelefonListe valueFormat { FormatiereTelefonliste($VALUE);; }

und die Anzeige sieht dann so aus:
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

Kiki99

Wow,
das klingt richtig gut.  ;D

Kannst Du das gesamte Script posten? Vermutlich hast Du es geschafft noch irgend etwas zu korrigieren, bei mir kommen noch immer Fehlermeldungen  :(

Kiki99

Hi andies,
sorry, habe es jetzt kapiert, dass das ganze auf der  speedport-hybrid-php-api aufbaut und habe diese auch ans endlich ans laufen bekommen, jippie  ;D

Was ich an Deinem Script noch nicht blicke ist, wo eist dort eigentlich der Aufruf der  speedport-hybrid-php-api?!  :o

Ich möchte eigentlich nur die missed_calls  8)

Danke im voraus, falls Du mir da noch mal auf die Sprünge helfen kannst  ::)

Bis ich die php Syntax so richtig verstehe brauche ich wohl noch etwas. Scheint ja sehr effektiv zu sein aber so weit kann mein Hirn irgendwie noch nicht um die Ecke denken  :-[

lg
Kiki

andies

Geht mir mit Perl ganz genau so: Blindflug.

Ich habe das umständlich programmiert. Es geht in drei Schritten (und ginge vermutlich einfacher):

defmod Telefon dummy
attr Telefon stateFormat &nbsp;;
attr Telefon userReadings anrufliste
attr Telefon webCmd einlesen


ist der Ausgangspunkt. Dort gibt es ein webcmd, mit dem der Einlesevorgang gestartet wird. Zudem existiert eine userReading, die das Ergebnis des Einlesevorganges speichern wird. Klickt man nun auf "einlesen", dann wird ausgelöst:

defmod TelefonEinlesen notify Telefon.einlesen {my $returnCode = qx(python3 speedport.py);;fhem("setreading Telefon anrufliste $returnCode");;}

und exakt hier wird der Python-Code angesprochen (bzw abgerufen). Das Ergebnis wird dabei plain von php in FHEM und zwar genauer in Telefon-dummy übernommen. Damit es dann aber schon formatiert wird, existiert

defmod TelefonListe readingsGroup Telefon:anrufliste
attr TelefonListe mapping {'anrufliste' => 'Liste'}
attr TelefonListe noheading 1
attr TelefonListe nolinks 1
attr TelefonListe valueFormat { HTMLFormatiereTelefonliste($VALUE);; }


und hier wird schön formatiert. Die Funktion selbst ist in myUtils.pm definiert, sie lautet wie folgt

our %myUtils_telefonbuch = (
"01234567" , "Eins",
"987654321" , "Zwei",
"0000011111", "Drei"
);

sub TelegramFormatiereTelefonliste($) {
my ($liste) = $_[$0];
$liste =~ s/Nr \d  //g; #remove Nr 0 etc. at the beginning (kommt so aus speedport heraus)
$liste =~ s/\*\*6\d/-intern-/g; # (**61 etc sind interne Nummern) cannot be handled in Telefonbuch because it is a nested regex
for my $nummer (keys %myUtils_telefonbuch) { #replace known numbers
$liste =~ s/$nummer/$myUtils_telefonbuch{$nummer}/g;
}
return $liste
}

sub HTMLFormatiereTelefonliste($) {
my ($liste) = TelegramFormatiereTelefonliste( $_[$0] ); # use above routine
$liste =~ s/\n/<br>/g; # does not work in Telegram, necessary in FHEMWEB
return $liste
}


Ich habe zwei Befehle, weil ich noch in Telegram was anzeigen lasse.
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

andies

Ich gebe auf. Ich hatte heute zum vierten Mal in einem Monat eine Situation, in der der Speedport die Portfreigaben von sich aus verstellt hat?! Ich hatte bisher immer diese Telekom-Gurke behalten, weil ich "WLAN-to-Go" behalten wollte. Das wiederum brauchte ich wirklich, weil man Handy für eine Woche ausgefallen war - aber statt sich problemlos einzuloggen, gab es da ständig Ärger. Lange Rede, kurzer Sinn: Ich habe heute eine Fritzbox ersteiget und verkaufe den Speedport. Diese Software ist einfach schrottig und da lohnt sich keine Arbeit zu investieren.
FHEM 6.1 auf RaspPi3 (Raspbian:  6.1.21-v8+; Perl: v5.32.1)
SIGNALduino (433 MHz) und HM-UART (868 MHz), Sonoff, Blitzwolf, Somfy RTS, CAME-Gartentor, Volkszähler, Keyence-Sensor, Homematic-Sensoren und -thermostat, Ferraris-Zähler für Wasseruhr, Openlink-Nachbau Viessmann

Rothammel