Zugriff auf FriztBox SmartHome Components mit microPython (pico2W)

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
SRCH
User
Beiträge: 7
Registriert: Montag 11. August 2025, 16:38

Man benötigt dazu eine SessionID, die nicht trivial zu bekommen ist. Hier ein Beispiel:

menencodeUTF19_LE.py:
# the Py3 code uses an edcoding which does not produce an error in microPython - but - gives a totally different and unuseful result
def enc0(char): # first character
return bytes(str(char), 'ascii')

def enc(char): # following characters
string="\x00"+char
return bytes(string, 'ascii')

def mencodeUTF16_LE(hexString: str) -> bytes:
encoded=bytes()
encoded+=enc0(hexString[0])
i=1
while i<len(hexString):
encoded+=enc(hexString)
i+=1 # why is there no i++?
encoded+=b'\x00' # termination
return encoded

def to_hex(b: bytes) -> str: # helper
ret=''.join('{:02x}'.format(x) for x in b)
return ret

fritzactors_sample.py:

# Controlling Fritz!Box smarthome components requires obtaining a sessonID according to protocol AVM TR-064.
# If done in Python3 there is a very elegant solution on Git: Tugzrida/fritzsid.py.
# Unfortunately this does not work in microPython for several reasons.
# This is a solution including a sample component access

from md5lib import md5
from mencodeUTF16_LE import * # code is below
import requests
import network
import time


# WIFI configuration
# your SSID
wlSsid = 'xxxx' # to be replaced
# your PW
wlPw = 'yyyy' # likewise


# WIFI connection

wlan = network.WLAN(network.STA_IF)
if not wlan.isconnected():
print('Connecting...')
wlan.active(True)
wlan.connect(wlSsid, wlPw)
for i in range(10):
if wlan.status() < 0 or wlan.status() >= 3:
break
print('->', wlan.status())
time.sleep(0.5)
if wlan.isconnected():
print('Connection OK')
else:
print('No connection')
print('WIFI-Status:', wlan.status())

def actors(act): # to address multiple actors/sensors
# we assume microPython sytem is in local network, otherwise see original post
response = requests.get("http://192.168.178.1/login_sid.lua")
response_content = response.content.decode('utf-8')
ia=response_content.index("nge>")
ie=response_content.index("</Cha")
challenge=response_content[ia+4:ie]
#print(challenge)
password="zzzz" # PW for user uuuu in line 51
tmp="{}-{}".format(challenge, password)
#print(tmp)
tmp2=mencodeUTF16_LE(tmp)
h = md5()
h.update(tmp2)
hash = (h.digest())
hash = to_hex(has)
username="uuuu" # FritzBox user for this specific application
respons = "{}-{}".format(challenge, hash)
response = requests.get("http://192.168.178.1/login_sid.lua?user ... {}".format(username, respons))
response_content = response.content.decode('utf-8')
ia=response_content.index("SID>")
SID=response_content[ia+4:ia+20]
#print(SID) - we've got it
if act==x: # example to get a power value from an actor with ain <ain>, e.g. a Fritz!SmartEnergy device
# for more details see AHA-HTTP-Interface document by AVM
resp=requests.get("http://192.168.178.1/webservices/homeau ... ormat(SID))
response_content = resp.content.decode('utf-8')
if resp.status_code==200: # means OK
resp1=requests.get("http://192.168.178.1/?sid={}&security:c ... SID,"dmy")) # better logout
return(float(response_content)/1000) # power in watts
else:
return -1
Benutzeravatar
DeaD_EyE
User
Beiträge: 1254
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Es würde helfen, wenn du den Code in Code-Tags packt, da ansonsten Tabulatoren und Leerzeichen entfernt werden, was in jedem Forum gleich ist.

[ code ] code [ /code ]
(ohne Leerzeichen zwischen den Klammern)

Bezüglich der SID könnte man regex nutzen, da Micropython keine xml-Bibliothek hat. Wäre auch viel zu groß.

Code: Alles auswählen

import re


RE_SID = re.compile(r"<SID>(\w+)</SID>")

reply = """<SessionInfo>
<SID>ff88e4d39354992f</SID>
<Challenge>ab7190d6</Challenge>
<BlockTime>128</BlockTime>
<Rights>
<Name>BoxAdmin</Name>
<Access>2</Access>
<Name>Phone</Name>
</Access>2</Access>
<Name>NAS></Name>
<Access>2</Access>
</Rights>
</SessionInfo>"""


def get_sid(response):
    if match := RE_SID.search(response):
        return match.group(1)
        
    return ""
Das sollte man sich auch durchlesen: https://fritz.com/fileadmin/user_upload ... ion_ID.pdf

Daher habe ich auch den Reply mit der SID. Ich habe keine FritzBox.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 18291
Registriert: Sonntag 21. Oktober 2012, 17:20

@SRCH: wenn Du utf-16le implementieren möchtest, dann ist es unsinnig, paarweise \x00\xab zu schreiben, und das erste und letzte Byte gesondert zu behandeln, weil für little-endian die Bytepaare einfach \xab\x00 sind. Statt einer while-Schleife benutzt man for.

Code: Alles auswählen

import struct

def mencodeUTF16_LE(string):
    return struct.pack(f"<{len(string)}H", *map(ord, string))
Konstanten schreibt man komplett groß und benutzt keine Abkürzungen, also WLAN_PASSWORD statt wlPw. Du hast die Variablen response, respon, resp und resp1, außer an der falschen Schreibweise kann man nicht erkennen, worin sie sich unterscheiden. Für verständlichen Code braucht man verständliche Namen.
`x` wird benutzt aber nirgends definiert.

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, und nicht mal 4 und mal 2. Deshalb ist die Einrückung bei Deinem `break` fehlerhaft.

format benutzt man heutzutage nur selten, statt dessen f-Strings.
Das könnte entsprechend dann so aussehen:

Code: Alles auswählen

LOGIN_URL = "http://192.168.178.1/login_sid.lua"

...

code = mencodeUTF16_LE(f"{challenge}-{password}")
hash = binascii.hexlify(md5(code).digest()).decode()
response = f"{challenge}-{hash}"
login_response = requests.get(f"{LOGIN_URL}?username={username}&response={response}")
response_content = login_response.text
Antworten