json Body ist leer

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

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
Zuletzt geändert von kiaralle am Freitag 22. November 2024, 17:36, insgesamt 1-mal geändert.
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

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'
Benutzeravatar
sparrow
User
Beiträge: 4501
Registriert: Freitag 17. April 2009, 10:28

Und wir sollen jetzt auf magische Weise hellsehen, was genau "client" und "write_points" ist?
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

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.
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Gelöst:

Code: Alles auswählen


                json_body = [
                    {
                        "measurement": device_device_name,
                        "fields": {"temp": device_hkr_is}

                    }
                ]

                print(json_body)
Benutzeravatar
noisefloor
User
Beiträge: 4149
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

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
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

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("------------------------------------------------------------------------")



Benutzeravatar
noisefloor
User
Beiträge: 4149
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

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
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

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.
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

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()
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

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.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten