Seite 1 von 1
json Body ist leer
Verfasst: Freitag 22. November 2024, 17:27
von kiaralle
Hi,
kurze Frage zu meinem leeren json Body
ich habe einen Wert, abgefragt aus einer Fritzbox, den ich nicht in meinen json wieder finde. json ist leer.
Code: Alles auswählen
json_body = {
"measurement": device_device_name,
"fields": device_hkr_is
}
print(device_hkr_is)
client.write_points(json_body)
print(device_hkr_is) zeigt mir 19 an. Es existiert also.
Ein print(json_body) zeigt einen leeren Inhalt.
Das speichern in eine Influxdb erzeut mir den Fehler
AttributeError: 'float' object has no attribute 'keys'
Gruß Ralf
Re: json Body ist leer
Verfasst: Freitag 22. November 2024, 17:32
von kiaralle
Kurzer Nachtrag
Code: Alles auswählen
json_body = [
{
"measurement": device_device_name,
"fields": device_hkr_is
}
]
print(json_body)
client.write_points(json_body)
zeigt mir doch etwas an.
[{'measurement': 'Heizkörper groß', 'fields': 20.0}]
Damit erhalte ich den Fehler: AttributeError: 'float' object has no attribute 'keys'
Re: json Body ist leer
Verfasst: Freitag 22. November 2024, 18:03
von sparrow
Und wir sollen jetzt auf magische Weise hellsehen, was genau "client" und "write_points" ist?
Re: json Body ist leer
Verfasst: Freitag 22. November 2024, 18:13
von kiaralle
sparrow hat geschrieben: Freitag 22. November 2024, 18:03
Und wir sollen jetzt auf magische Weise hellsehen, was genau "client" und "write_points" ist?
Deshalb schrieb ich ja "Das speichern in eine Influxdb"
Das schreiben in einem anderen Programm funktioniert ja. Deshalb fand ich es jetzt nicht als erwähnenswert.
Re: json Body ist leer
Verfasst: Freitag 22. November 2024, 18:22
von kiaralle
Gelöst:
Code: Alles auswählen
json_body = [
{
"measurement": device_device_name,
"fields": {"temp": device_hkr_is}
}
]
print(json_body)
Re: json Body ist leer
Verfasst: Samstag 23. November 2024, 09:15
von noisefloor
Hallo,
schön, das es funktioniert. Nur ist das alles null nachvollziehbar, weil deinerseits so ziemlich alles an Infos fehlt, was relevant ist. Das ist für Aussenstehende, die hätten helfen wollen, null Komma null nachvollziehbar, wo was herkommt und wo was hin soll.
Gruß, noisefloor
Re: json Body ist leer
Verfasst: Samstag 23. November 2024, 11:34
von kiaralle
Ok, ich bin auch deiner Meinung.
Hier meine zusammengeraubtes und umgeändertes Script.
Ich rufe die Fritzbox und damit meine DECT-Heizungs-Thermostate ab.
Durch den Umstieg auf eine Wärmepumpe bin ich gerade am Abgeleich meiner Heizkörper und möchte so meine Daten der Wärmepumpe, ein extra shely-Script, den der Zimmertemperaturen gegenüber stellen.
Alles was ich nicht benötige möchte ich später noch aus dem Skript entfernen.
Hier der Code, eventuell benötigt ihn ja jemand.
Code: Alles auswählen
#!/usr/bin/env python3
import time
import sys
from requests.auth import HTTPBasicAuth
import json
import requests
import hashlib
import re
from influxdb import InfluxDBClient
client = InfluxDBClient(host='192.168.178.xx', port=8086,
username='xxxx', password='xxxx', database='shelly_wp')
# URL der Fritzbox
fritzbox_url = "http://192.168.178.xx"
# Benutzername
Username = "xx"
# Passwort
Password = "xxx"
# Anmeldemethode
# version=1 ist MD5
# version=2 ist PBKDF2
version = 2
# Linie ziehen
print("------------------------------------------------------------------------")
# Anmeldemethode mit PBKDF2
# ==========================================================================================================================
if version == 2:
#Payload erzeugen
request_payload = {'version': '2'}
# Anfrage starten
request_result = requests.get(fritzbox_url + "/login_sid.lua", params=request_payload)
# Zum Debuggen, alles Ausgeben
#print (request_result.text)
# Daten herausfiltern
SID = str((request_result.text).split("<SID>")[1].split("</SID>")[0])
BlockTime = int((request_result.text).split("<BlockTime>")[1].split("</BlockTime>")[0])
Challenge = str((request_result.text).split("<Challenge>")[1].split("</Challenge>")[0])
if SID == '0000000000000000':
# SID ist 0, daher Response Erzeugen und neu anfordern: Berechnung mit PBKDF2
Challenge_split = Challenge.split("$")
#notused = int(Challenge_split[0])
iter1 = int(Challenge_split[1])
salt1 = bytes.fromhex(Challenge_split[2])
iter2 = int(Challenge_split[3])
salt2 = bytes.fromhex(Challenge_split[4])
hash1 = hashlib.pbkdf2_hmac("sha256", Password.encode(), salt1, iter1)
hash2 = hashlib.pbkdf2_hmac("sha256", hash1, salt2, iter2)
Response = Challenge_split[4] + "$" + hash2.hex()
# Daten erstellen für POST
request_data = {"username": Username, "response": Response}
# Header erstellen
request_headers = {"Content-Type": "application/x-www-form-urlencoded"}
# Und Daten anfordern
request_result = requests.post(fritzbox_url + "/login_sid.lua", data=request_data, headers=request_headers)
# Zum Debuggen, alles Ausgeben
#print (request_result.text)
SID = str((request_result.text).split("<SID>")[1].split("</SID>")[0])
BlockTime = int((request_result.text).split("<BlockTime>")[1].split("</BlockTime>")[0])
Name = str((request_result.text).split("<Name>")[1].split("</Name>")[0])
Access = int((request_result.text).split("<Access>")[1].split("</Access>")[0])
# Ausgabe der einzelnen Variablen
print ("Challenge: ",Challenge)
print ("Response: ",Response)
print ("SID: ",SID)
print ("BlockTime: ",BlockTime)
print ("Name: ",Name)
print ("Access: ",Access)
else:
# Ausgabe der einzelnen Variablen
print ("SID: ",SID)
print ("BlockTime: ",BlockTime)
# Anmeldemethode mit MD5
# ==========================================================================================================================
if version == 1:
#Payload erzeugen
request_payload = {'version': '1'}
# Anfrage starten
request_result = requests.get(fritzbox_url + "/login_sid.lua", params=request_payload)
# Zum Debuggen, alles Ausgeben
#print (request_result.text)
# Daten herausfiltern
SID = str((request_result.text).split("<SID>")[1].split("</SID>")[0])
BlockTime = int((request_result.text).split("<BlockTime>")[1].split("</BlockTime>")[0])
Challenge = str((request_result.text).split("<Challenge>")[1].split("</Challenge>")[0])
if SID == '0000000000000000':
# SID ist 0, daher Response Erzeugen und neu anfordern: Berechnung mit MD5
MD5 = hashlib.md5((Challenge + "-" + Password).encode("utf_16_le")).hexdigest()
Response = Challenge + "-" + MD5
# Daten erstellen für POST
request_data = {"username": Username, "response": Response}
# Header erstellen
request_headers = {"Content-Type": "application/x-www-form-urlencoded"}
# Und Daten anfordern
request_result = requests.post(fritzbox_url + "/login_sid.lua", data=request_data, headers=request_headers)
# Zum Debuggen, alles Ausgeben
#print (request_result.text)
SID = str((request_result.text).split("<SID>")[1].split("</SID>")[0])
BlockTime = int((request_result.text).split("<BlockTime>")[1].split("</BlockTime>")[0])
Name = str((request_result.text).split("<Name>")[1].split("</Name>")[0])
Access = int((request_result.text).split("<Access>")[1].split("</Access>")[0])
# Ausgabe der einzelnen Variablen
print ("Challenge: ",Challenge)
print ("MD5: ",MD5)
print ("Response: ",Response)
print ("SID: ",SID)
print ("BlockTime: ",BlockTime)
print ("Name: ",Name)
print ("Access: ",Access)
else:
# Ausgabe der einzelnen Variablen
print ("SID: ",SID)
print ("BlockTime: ",BlockTime)
# Getdevicelistinfos
# ==========================================================================================================================
# Linie ziehen
print ("------------------------------------------------------------------------")
# Payload erzeugen
request_payload = {'switchcmd': 'getdevicelistinfos', 'sid': SID}
# Und Daten anfordern
request_result = requests.get(fritzbox_url + "/webservices/homeautoswitch.lua", params=request_payload)
# Zum Debuggen, alles Ausgeben
#print (request_result.text)
# Teile die devices in einzelne Abschnitte
request_result_split_array = re.findall("<device (.*?)</device>", request_result.text)
for i, request_result_split in enumerate(request_result_split_array):
# Ausgabe der einzelnen Variablen
#print (request_result_split)
device_ain = str((request_result_split).split('identifier="')[1].split('" id=')[0])
device_id = int((request_result_split).split('id="')[1].split('" functionbitmask=')[0])
device_function_bitmask = int((request_result_split).split('functionbitmask="')[1].split('" fwversion=')[0])
device_firmware = str((request_result_split).split('" fwversion="')[1].split('" manufacturer=')[0])
device_manufacturer = str((request_result_split).split('manufacturer="')[1].split('" productname=')[0])
device_product_name = str((request_result_split).split('productname="')[1].split('"><present>')[0])
device_device_name = str((request_result_split).split("<name>")[1].split("</name>")[0])
device_present = str((request_result_split).split("<present>")[1].split("</present>")[0])
device_busy = str((request_result_split).split("<txbusy>")[1].split("</txbusy>")[0])
# ab hier unvollstaendig: nur Heizkoerperregler, Schaltaktor, Taster. Andere Geraete ergaenzen...
if "<hkr>" in request_result_split:
device_battery = int((request_result_split).split("<battery>")[1].split("</battery>")[0])
device_batterylow = str((request_result_split).split("<batterylow>")[1].split("</batterylow>")[0])
device_temperature_celsius = float((request_result_split).split("<celsius>")[1].split("</celsius>")[0])/10
device_temperature_offset = float((request_result_split).split("<offset>")[1].split("</offset>")[0])/10
#Temperatur-Wert in 0,5 °C, Wertebereich: 16 – 56 -> 8 bis 28°C, z.B.: 16 <= 8°C, 17 = 8,5°C...... 56 >= 28°C, 254 = ON , 253 = OFF
device_hkr_set = float((request_result_split).split("<tsoll>")[1].split("</tsoll>")[0])/2
device_hkr_is = float((request_result_split).split("<tist>")[1].split("</tist>")[0])/2
device_hkr_absenk = float((request_result_split).split("<absenk>")[1].split("</absenk>")[0])/2
device_hkr_komfort = float((request_result_split).split("<komfort>")[1].split("</komfort>")[0])/2
device_hkr_lock = int((request_result_split).split("<lock>")[1].split("</lock>")[0])
device_hkr_devicelock = int((request_result_split).split("<devicelock>")[1].split("</devicelock>")[0])
device_hkr_errorcode = int((request_result_split).split("<errorcode>")[1].split("</errorcode>")[0])
device_hkr_windowopenactiv = int((request_result_split).split("<windowopenactiv>")[1].split("</windowopenactiv>")[0])
device_hkr_windowopenactiveendtime = int((request_result_split).split("<windowopenactiveendtime>")[1].split("</windowopenactiveendtime>")[0])
device_hkr_boostactive = int((request_result_split).split("<boostactive>")[1].split("</boostactive>")[0])
device_hkr_boostactiveendtime = int((request_result_split).split("<boostactiveendtime>")[1].split("</boostactiveendtime>")[0])
device_hkr_endperiod = int((request_result_split).split("<endperiod>")[1].split("</endperiod>")[0])
device_hkr_tchange = int((request_result_split).split("<tchange>")[1].split("</tchange>")[0])
device_hkr_summeractive = int((request_result_split).split("<summeractive>")[1].split("</summeractive>")[0])
device_hkr_adaptiveHeatingActive = int((request_result_split).split("<adaptiveHeatingActive>")[1].split("</adaptiveHeatingActive>")[0])
device_hkr_adaptiveHeatingRunning = int((request_result_split).split(
"<adaptiveHeatingRunning>")[1].split("</adaptiveHeatingRunning>")[0])
json_body = [
{
"measurement": device_device_name,
"fields": {"temp": device_hkr_is,
"temp_set": device_hkr_set
}
}
]
print(json_body)
client.write_points(json_body)
# Linie ziehen
print("------------------------------------------------------------------------")
Re: json Body ist leer
Verfasst: Samstag 23. November 2024, 11:51
von noisefloor
Hallo,
also vom Code her sieht es stark so aus, als wäre der Response XML. Zum Parsen solltest du dann auch einen XML-Parser verwenden, Python hat einen an Bord ([url]
https://docs.python.org/3/library/xml.e ... e.html[url]). Ansonsten ist soweit ich weiß lxml Stand der Dinge bei externen Modulen. XML wie einen String zu behandeln ist zu fehleranfällig.
Gruß, noisefloor
Re: json Body ist leer
Verfasst: Samstag 23. November 2024, 13:19
von kiaralle
Danke für deine Link.
Den schaue ich mir an.
Gerade habe ich gelesen, das die Abfrage der Fritzbox auch mit Json gehen soll.
Wäre ja noch besser.
Re: json Body ist leer
Verfasst: Samstag 23. November 2024, 15:20
von Sirius3
Eingerückt wird immer mit 4 Leerzeichen pro Ebene. Variablennamen schreibt man komplett klein, Konstanten dagegen komplett GROSS. Ein Programm strukturiert man mit Funktionen, nicht mit Kommentaren, die keinen Inhalt haben, sondern nur aus =-Zeichen bestehen.
XML ist kein einfacher String, sondern ist ein strukturiertes Format, das man mit einem entsprechenden Parser liest.
Wenn man mit request eine Seite liest, sollte man prüfen, dass das Ergebnis auch fehlerfrei ist.
Der Code für Login-Version 1 und Version 2 ist fast identisch, da wurde also viel kopiert.
Kommentare sollten einen Mehrwert bieten, der Kommentar "Linie ziehen" gefolgt von einem print mit vielen Minuszeichen ist da extrem.
Zudem ist da viel Code, der gar nicht gebraucht wird.
Alles in allem bleibt ungefähr das übrig:
Code: Alles auswählen
#!/usr/bin/env python3
import requests
import hashlib
from influxdb import InfluxDBClient
import xml.etree.ElementTree as et
FRITZBOX_URL = "http://192.168.178.xx"
USERNAME = "xx"
PASSWORD = "xxx"
def login(fritzbox_url, username, password, version=2):
""" Anmeldemethode mit PBKDF2 """
login_response = requests.get(f"{fritzbox_url}/login_sid.lua", params={'version': version})
login_response.raise_for_status()
session_info = et.fromstring(response.content)
sid = session_info.findtext("SID")
block_time = session_info.findtext("BlockTime")
challenge = session_info.findtext("Challenge")
if sid == "0000000000000000":
# SID ist 0, daher Response Erzeugen und neu anfordern
if version == 2:
# Berechnung mit PBKDF2
_, iterations1, salt1, iterations2, salt2 = challenge.split("$")
hash1 = hashlib.pbkdf2_hmac("sha256", password.encode(), bytes.fromhex(salt1), int(iterations1))
hash2 = hashlib.pbkdf2_hmac("sha256", hash1, bytes.fromhex(salt2), int(iterations2))
response = f"{salt2}${hash2.hex()}"
else:
# Berechnung mit MD5
md5 = hashlib.md5(f"{challenge}-{password}".encode("utf_16_le")).hexdigest()
response = f"{challenge}-{md5}"
request_data = {"username": username, "response": response}
login_response = requests.post(f"{fritzbox_url}/login_sid.lua", data=request_data)
login_response.raise_for_status()
session_info = et.fromstring(response.content)
sid = session_info.findtext("SID")
block_time = session_info.findtext("BlockTime")
name = session_info.findtext("Name")
access = session_info.findtext("Access")
print ("Challenge: ", challenge)
print ("Response: ", response)
print ("SID: ", sid)
print ("BlockTime: ", block_time)
print ("Name: ", name)
print ("Access: ", access)
else:
print ("SID: ", sid)
print ("BlockTime: ", block_time)
return sid
def get_device_list_infos(sid):
payload = {'switchcmd': 'getdevicelistinfos', 'sid': sid}
response = requests.get(f"{fritzbox_url}/webservices/homeautoswitch.lua", params=payload)
response.raise_for_status()
device_infos = et.fromstring(response.content)
result = []
for device in device_infos.findall("device"):
device_device_name = device.findtext("name")
if int(device.attrib["functionbitmask"]) & 32:
# Heizkörper
device_hkr_is = float(device.findtext("hkr/tist"))
device_hkr_set = float(device.findtext("hkr/tsoll"))
result.append({
"measurement": device_device_name,
"fields": {
"temp": device_hkr_is,
"temp_set": device_hkr_set
}
})
return result
def main():
client = InfluxDBClient(
host='192.168.178.xx', port=8086,
username='xxxx', password='xxxx', database='shelly_wp'
)
print("------------------------------------------------------------------------")
sid = login(FRITZBOX_URL, USERNAME, PASSWORD)
print("------------------------------------------------------------------------")
device_list = get_device_list_infos(sid)
print(device_list)
client.write_points(device_list)
print("------------------------------------------------------------------------")
if __name__ == "__main__":
main()
Re: json Body ist leer
Verfasst: Samstag 23. November 2024, 15:50
von __blackjack__
Ich würde übrigens immer mit der Version 2 anfangen und schauen wie die Challenge aussieht. Wenn die mit "2$" anfängt, dann ist es PBKDF2, wenn nicht dann MD5. Und je nach dem wie alte Fritz OS Versionen man noch unterstützen möchte/muss, könnte man sich den MD5-Teil auch sparen und sagen mit so alten Systemen möchte man nicht mehr reden.
Edit: Die MD5-Variante ist auch nicht ganz vollständig: Zeichen im Passwort die einen Unicode-Codepoint > 255 haben, müssen durch einen Punkt "." ersetzt werden. Kann ja durchaus sein, dass jemand beispielsweise das €-Zeichen im Passwort verwendet, das ist auf Tastaturen mit DE-Layout ja kein Problem.