'NoneType' object has no attribute 'items'

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Bra!NPa!N
User
Beiträge: 4
Registriert: Dienstag 8. November 2022, 10:34

Guten Tag Zusammen,

ich bin Neuling in Python und habe mich an das Auslesens unseres Wechselrichters mittels modbus gemacht. Dank der Hifler diverser Beiträge habe ich gelingt mir das Auslesen selbst auch ohne Probleme allerdings soll das ganze auch via MQTT übertragen werden wozu ich den folgenden Artikel gefunden habe https://sequr.be/blog/2021/08/reading-g ... over-mqtt/ (Vielen Dank an den Autor an dieser Stelle!). Den dort gefundenen Code:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Read modbus data from Ginlong Solis inverter
# and send over MQTT
#
# Based on https://github.com/rogersia/Solis-4G
# Includes fixes, small modifcations and refactoring. Migrated to Python3.
# See https://sequr.be/blog/2021/08/reading-ginlong-solis-inverter-over-serial-and-importing-in-home-assistant-over-mqtt/
#
import logging
import minimalmodbus
import paho.mqtt.client as mqtt
import serial
import socket
import sys
import time

broker = "192.168.xxx.yyy"      # Set to MQTT broker IP (likely your HA IP)
port = 1883                     # Set to MQTT broker port
mqttuser = "YOUR_MQTT_USER"     # Set to your MQTT user username
mqttpass = "YOUR_MQTT_PASSWORD" # Set to your MQTT user password
client_id = "solis_com"

logging.basicConfig(stream=sys.stderr, level=logging.INFO)

def mqtt_connect():
  # callback for mqtt
  def on_connect(client, userdata, flags, rc):
    logging.debug("MQTT connected with result code {}".format(rc))

  client = mqtt.Client(client_id)
  client.username_pw_set(mqttuser, mqttpass)
  client.on_connect = on_connect
  client.connect_async(broker, port, 60)
  return client

def mqtt_subscribe(client):
  def on_message(client, userdata, msg):
    logging.debug("[ {} ({})] {}".format(msg.topic, msg.qos, msg.payload))

  client.subscribe("meters/solis_com/command", 2)
  client.on_message = on_message

def mqtt_publish(client, data):
  def on_publish(client, userdata, mid):
    logging.debug("[{}] published ({})".format(mid, userdata))

  def send(client, topic, payload="", qos=2, retain=False):
    res = client.publish(topic, payload, qos, retain)
    res.wait_for_publish()
    logging.debug("[{}] status: {} - {}".format(res.mid, res.rc, "Published" if res.is_published() else "Failed"))
    time.sleep(0.5)

  client.on_publish = on_publish
  time.sleep(2)
  for k, v in data.items():
    send(client, "meters/solis_com/{}".format(k), v)

def modbus_connect():
  instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1) # Set to inverter's address
  instrument.serial.baudrate = 9600
  instrument.serial.bytesize = 8
  instrument.serial.parity   = serial.PARITY_NONE
  instrument.serial.stopbits = 1
  instrument.serial.timeout  = 3
  #instrument.debug = True
  return instrument

def modbus_read(instrument):
  timestamp = time.time()
  # get data from solis
  Realtime_ACW = instrument.read_long(3004, functioncode=4, signed=False) # Read AC Watts as Unsigned 32-Bit
  logging.info("{:<23s}{:10.2f} W".format("AC Watts", Realtime_ACW))
  Realtime_DCV = instrument.read_register(3021, number_of_decimals=2, functioncode=4, signed=False) # Read DC Volts as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} V".format("DC Volt", Realtime_DCV))
  Realtime_DCI = instrument.read_register(3022, number_of_decimals=0, functioncode=4, signed=False) # Read DC Current as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} A".format("DC Current", Realtime_DCI))
  Realtime_ACV = instrument.read_register(3035, number_of_decimals=1, functioncode=4, signed=False) # Read AC Volts as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} V".format("AC Volt", Realtime_ACV))
  Realtime_ACI = instrument.read_register(3038, number_of_decimals=0, functioncode=4, signed=False) # Read AC Current as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} A".format("AC Current", Realtime_ACI))
  Realtime_ACF = instrument.read_register(3042, number_of_decimals=2, functioncode=4, signed=False) # Read AC Frequency as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} Hz".format("AC Frequency", Realtime_ACF))
  Inverter_C = instrument.read_register(3041, number_of_decimals=1, functioncode=4, signed=True) # Read Inverter Temperature as Signed 16-Bit
  logging.info("{:<23s}{:10.2f} °C".format("Inverter Temperature", Inverter_C))
  AlltimeEnergy_KW = instrument.read_long(3008, functioncode=4, signed=False) # Read All Time Energy (KWH Total) as Unsigned 32-Bit
  logging.info("{:<23s}{:10.2f} kWh".format("Generated (All time)", AlltimeEnergy_KW))
  Today_KW = instrument.read_register(3014, number_of_decimals=1, functioncode=4, signed=False) # Read Today Energy (KWH Total) as 16-Bit
  logging.info("{:<23s}{:10.2f} kWh".format("Generated (Today)", Today_KW))

  data = {
    'online': timestamp,
    'acw': Realtime_ACW,
    'dcv': Realtime_DCV,
    'dci': Realtime_DCI,
    'acv': Realtime_ACV,
    'aci': Realtime_ACI,
    'acf': Realtime_ACF,
    'inc': Inverter_C
  }

  # Fix for 0-values during inverter powerup
  if AlltimeEnergy_KW > 0: data["gat"] = AlltimeEnergy_KW
  if Today_KW > 0: data["gto"] = Today_KW

def main():
  try:
    mqttc = mqtt_connect()
    mqtt_subscribe(mqttc)
    mqttc.loop_start()
    modc = modbus_connect()
    data = modbus_read(modc)
    mqtt_publish(mqttc, data)

  except TypeError as err:
    logging.error("TypeError:\n{}".format(err))

  except ValueError as err:
    logging.error("ValueError:\n{}".format(err))

  except minimalmodbus.NoResponseError as err:
    logging.error("Modbus no response:\n{}".format(err))

  except serial.SerialException as err:
    logging.error("SerialException:\n{}".format(err))

  except Exception as err:
    logging.error("Exception:\n{}".format(err))

if __name__ == "__main__":
  main()
"spuckt" plausible Werte im Terminal aus überträgt sie allerdings nicht via mqtt und spuckt Folgendes im Debug-Modus aus:

Code: Alles auswählen

DEBUG:root:MQTT connected with result code 0
INFO:root:AC Watts 820.00 W
INFO:root:DC Volt 206.00 V
INFO:root:DC Current 4.30 A
INFO:root:AC Volt 240.00 V
INFO:root:AC Current 1.10 A
INFO:root:AC Frequency 50.01 Hz
INFO:root:Inverter Temperature 20.90 °C
INFO:root:Generated (All time) 31.00 kWh
INFO:root:Generated (Today) 1.20 kWh
ERROR:root:Exception: 'NoneType' object has no attribute 'items'
Kann mir hier jemand bei der Interpretationhilfe des letzten Meldung "ERROR:root:Exception: 'NoneType' object has no attribute 'items'" helfen? Ich habe hierzu bereits gegoogelt, komme aber nicht auf die Ursache im Script selbst.

Vielen Dank im Voraus und beste Grüße
Bra!NPa!N
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bitte sofort diese komplett hirnrissige Fehlerbehandlung wegwerfen, welche die für uns und dich wichtigen Hinweise auf die konkrete Ursache einfach verschlucken. Wird sehr helfen in der Zukunft.

Die Verwendung von items ist aber auch nur an einer Stelle. Und an der hast du einen dicken Fehler, weil die Zeilen nicht richtig eingerückt sind. Einrückungen sind in Python essentiell.
Bra!NPa!N
User
Beiträge: 4
Registriert: Dienstag 8. November 2022, 10:34

Hallo __deets__,

besten Dank für die schnelle Antwort. Ich gehe davon aus, dass du den folgenden Abschnitt meinst:

Code: Alles auswählen

  for k, v in data.items():
    send(client, "meters/solis_com/{}".format(k), v)
Vielleicht stehe ich auf dem Schlauch, aber aus meiner Sicht ist hier alles richtig eingerückt. Hast du vielleicht ein paar mehr Details?

Vielen Dank und beste Grüße
Bra!NPa!N
Bra!NPa!N
User
Beiträge: 4
Registriert: Dienstag 8. November 2022, 10:34

Hallo Zusammen,

die Lösung des Problems ist ein fehlendes
return data
nach dem ganzen auslesen:

Code: Alles auswählen

def modbus_read(instrument):
  timestamp = time.time()
  # get data from solis
  Realtime_ACW = instrument.read_long(3004, functioncode=4, signed=False) # Read AC Watts as Unsigned 32-Bit
  logging.info("{:<23s}{:10.2f} W".format("AC Watts", Realtime_ACW))
  Realtime_DCV = instrument.read_register(3021, number_of_decimals=2, functioncode=4, signed=False) # Read DC Volts as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} V".format("DC Volt", Realtime_DCV))
  Realtime_DCI = instrument.read_register(3022, number_of_decimals=0, functioncode=4, signed=False) # Read DC Current as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} A".format("DC Current", Realtime_DCI))
  Realtime_ACV = instrument.read_register(3035, number_of_decimals=1, functioncode=4, signed=False) # Read AC Volts as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} V".format("AC Volt", Realtime_ACV))
  Realtime_ACI = instrument.read_register(3038, number_of_decimals=0, functioncode=4, signed=False) # Read AC Current as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} A".format("AC Current", Realtime_ACI))
  Realtime_ACF = instrument.read_register(3042, number_of_decimals=2, functioncode=4, signed=False) # Read AC Frequency as Unsigned 16-Bit
  logging.info("{:<23s}{:10.2f} Hz".format("AC Frequency", Realtime_ACF))
  Inverter_C = instrument.read_register(3041, number_of_decimals=1, functioncode=4, signed=True) # Read Inverter Temperature as Signed 16-Bit
  logging.info("{:<23s}{:10.2f} °C".format("Inverter Temperature", Inverter_C))
  AlltimeEnergy_KW = instrument.read_long(3008, functioncode=4, signed=False) # Read All Time Energy (KWH Total) as Unsigned 32-Bit
  logging.info("{:<23s}{:10.2f} kWh".format("Generated (All time)", AlltimeEnergy_KW))
  Today_KW = instrument.read_register(3014, number_of_decimals=1, functioncode=4, signed=False) # Read Today Energy (KWH Total) as 16-Bit
  logging.info("{:<23s}{:10.2f} kWh".format("Generated (Today)", Today_KW))

  data = {
    'online': timestamp,
    'acw': Realtime_ACW,
    'dcv': Realtime_DCV,
    'dci': Realtime_DCI,
    'acv': Realtime_ACV,
    'aci': Realtime_ACI,
    'acf': Realtime_ACF,
    'inc': Inverter_C
  }

  # Fix for 0-values during inverter powerup
  if AlltimeEnergy_KW > 0: data["gat"] = AlltimeEnergy_KW
  if Today_KW > 0: data["gto"] = Today_KW
  
  return data
Vielen Dank für den Denkanstoß.

Beste Grüße
Bra!NPa!N
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn das bei dir richtig eingerückt sein sollte, dann mischst du Tabs mit Leerzeichen. Was man genau aus dem Grund vermeiden sollte. Wenn du den Code oben anschaust, dann sind da diverse Zeilen nicht korrekt eingerückt. Womit man das dann auch nur schwer analysieren kann.
Bra!NPa!N
User
Beiträge: 4
Registriert: Dienstag 8. November 2022, 10:34

__deets__ hat geschrieben: Dienstag 8. November 2022, 12:03 Wenn das bei dir richtig eingerückt sein sollte, dann mischst du Tabs mit Leerzeichen. Was man genau aus dem Grund vermeiden sollte. Wenn du den Code oben anschaust, dann sind da diverse Zeilen nicht korrekt eingerückt. Womit man das dann auch nur schwer analysieren kann.
Danke für den Hinweis. Werde ich bei der finalen Anpassung auf das benögite überprüfen.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Bra!NPa!N: der Code ist insgesamt sehr schwierig zu lesen.
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, und nicht zwei. Variablenname werden komplett klein geschrieben. Konstante dagegen komplett GROSS.
Man verschachtelt keine Funktionen ineinander.
Nach dem Doppelpunkt (z.B. bei if) fängt eine neue Zeile an.
Exceptions sollte man nur abfangen, wenn man den Fehler auch sinnvoll verarbeiten kann.
Statt Code zu kopieren und leicht anzupassen, benutzt man passende Datenstrukturen.
Damit kommt man ungefähr bei dem raus:

Code: Alles auswählen

import sys
import time
import logging
import minimalmodbus
import paho.mqtt.client as mqtt

MQT_BROKER = "192.168.xxx.yyy"
MQT_PORT = 1883
MQT_USERNAME = "YOUR_MQTT_USER"
MQT_PASSWORD = "YOUR_MQTT_PASSWORD"
MQT_CLIENT_ID = "solis_com"

def on_connect(client, userdata, flags, rc):
    logging.debug("MQTT connected with result code %s", rc)

def on_message(client, userdata, msg):
    logging.debug("[ %s (%s)] %s", msg.topic, msg.qos, msg.payload)

def on_publish(client, userdata, mid):
    logging.debug("[%s] published (%s)", mid, userdata)

def send(client, topic, payload="", qos=2, retain=False):
    res = client.publish(topic, payload, qos, retain)
    res.wait_for_publish()
    logging.debug("[%s] status: %s - %s", res.mid, res.rc, "Published" if res.is_published() else "Failed")
    time.sleep(0.5)

def mqtt_connect():
    # callback for mqtt
    client = mqtt.Client(MQT_CLIENT_ID)
    client.username_pw_set(MQT_USERNAME, MQT_PASSWORD)
    client.on_connect = on_connect
    client.connect_async(MQT_BROKER, MQT_PORT, 60)
    return client

def mqtt_subscribe(client):
    client.subscribe("meters/solis_com/command", 2)
    client.on_message = on_message

def mqtt_publish(client, data):
    client.on_publish = on_publish
    time.sleep(2)
    for k, v in data.items():
        send(client, f"meters/solis_com/{k}", v)

def modbus_connect():
    instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1) # Set to inverter's address
    instrument.serial.baudrate = 9600
    instrument.serial.bytesize = 8
    instrument.serial.parity = serial.PARITY_NONE
    instrument.serial.stopbits = 1
    instrument.serial.timeout = 3
    #instrument.debug = True
    return instrument


REGISTERS = [
    # name, unit, description, type, address, number_of_decimals
    ("acw", "W", "AC Watts", "long", 3004, None),
    ("dcv", "V", "DC Volt", "register", 3021, 2),
    ("dci", "A", "DC Current", "register", 3022, 0),
    ("acv", "V", "AC Volt", "register", 3035, 1),
    ("aci", "A", "AC Current", "register", 3038, 0),
    ("acf", "Hz", "AC Frequency", "register", 3042, 2),
    ("inc", "°C", "Inverter Temperature", "register", 3041, 1),
    ("gat", "kWh", "Generated (All time)", "long", 3008, None),
    ("gto", "kWh", "Generated (Today)", "register", 3014, 1),
]

def modbus_read(instrument):
    data = {
        "online": time.time(),
    }
    for name, description, type, address, number_of_decimals in REGISTERS:
        if type == "long":
            value = instrument.read_long(address, functioncode=4, signed=False)
        else:
            value = instrument.read_register(address, number_of_decimals=number_of_decimals, functioncode=4, signed=False)
        logging.info("{:<23s}{:10.2f} {}".format(description, value, unit))

def main():
    logging.basicConfig(stream=sys.stderr, level=logging.INFO)
    mqttc = mqtt_connect()
    mqtt_subscribe(mqttc)
    mqttc.loop_start()
    modc = modbus_connect()
    data = modbus_read(modc)
    mqtt_publish(mqttc, data)

if __name__ == "__main__":
    main()
Antworten