question

elmcrest avatar image
elmcrest asked

Python Beispiel um einfach Daten abzufragen

Hallo zusammen,

ich habe mir nachfolgendes script gebaut und dachte mir vielleicht hilft es dem einen oder anderen ja auch.
Es wird Python benötigt und es muss eine dependency installiert werden (pymodbus), hierfür empfehle ich eine virtualenv zu verwenden. In einem Terminal seiner Wahl (Windows sollte auch funktionieren, habe ich aber noch nicht getestet):

mkdir ein_name_deiner_wahl

um einen neuen Ordner zu erstellen

cd ein_name_deiner_wahl

um in den neuen Ordner hinein zu "gehen"

python -m venv .venv

python spezifisch um eine virtuelle Umgebung zu bekommen

source .venv/bin/activate

um diese Umgebung zu aktivieren (deaktivieren geht mit 'deactivate')

pip install pymodbus

um die library (dependency) in der aktivierten Umgebung zu installieren

touch main.py

um eine neue Datei mit dem name "main.py" zu erstellen.

Nun in die main.py (oder anderer Name, spielt keine Rolle) den nachfolgenden code kopieren.

import textwrap
import pymodbus.client as ModbusClient
from pymodbus import (
    ExceptionResponse,
    Framer,
    ModbusException,
)
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.constants import Endian


GRID_METER = 30
PV_INVERTER = 32
VICTRON_ENERGY_HUB = 100
VICTRON_ENERGY_SYSTEM = 100
VICTRON_MULTIPLUS = 242  # 261 see `Unit ID mapping in xlsx file`


datapoints = {
    (PV_INVERTER, "com.victronenergy.pvinverter"): {
        "L1 Energy": [1030, "uint16", 100, "kWh"],
        "L2 Energy": [1034, "uint16", 100, "kWh"],
        "L3 Energy": [1038, "uint16", 100, "kWh"],
        "L1 Voltage": [1027, "uint16", 10, "V AC"],
        "L2 Voltage": [1031, "uint16", 10, "V AC"],
        "L3 Voltage": [1035, "uint16", 10, "V AC"],
    },
    (GRID_METER, "com.victronenergy.grid"): {
        "L1 – Voltage": [2616, "uint16", 10, "V AC"],
        "L1 – Current": [2617, "int16", 10, "A AC"],
        "L2 – Voltage": [2618, "uint16", 10, "V AC"],
        "L2 – Current": [2619, "int16", 10, "A AC"],
        "L3 – Voltage": [2620, "uint16", 10, "V AC"],
        "L3 – Current": [2621, "int16", 10, "A AC"],
    },
    (VICTRON_ENERGY_HUB, "com.victronenergy.hub4"): {
        "Grid limiting status": [2709, "int16", 1, "Active"],
    },
    (VICTRON_ENERGY_SYSTEM, "com.victronenergy.system"): {
        "AC Consumption L1": [817, "uint16", 1, "W"],
        "AC Consumption L2": [818, "uint16", 1, "W"],
        "AC Consumption L3": [819, "uint16", 1, "W"],
        "Grid L1": [820, "int16", 1, "W"],
        "Grid L2": [821, "int16", 1, "W"],
        "Grid L3": [822, "int16", 1, "W"],
    },
    (VICTRON_MULTIPLUS, "com.victronenergy.vebus"): {
        "Input voltage phase 1": [3, "uint16", 10, "V AC"],
        "Input voltage phase 2": [4, "uint16", 10, "V AC"],
        "Input voltage phase 3": [5, "uint16", 10, "V AC"],
        "Input current phase 1": [6, "int16", 10, "A AC"],
        "Input current phase 2": [7, "int16", 10, "A AC"],
        "Input current phase 3": [8, "int16", 10, "A AC"],
    },
}
single_register_markers = ["int16", "uint16"]
double_register_markers = ["int32", "uint32"]


def query_victron_modbus_data(host, port, framer=Framer.SOCKET):
    """Run sync client."""
    # uncomment to activate debugging
    # from pymodbus import pymodbus_apply_logging_config
    # pymodbus_apply_logging_config("DEBUG")
    client = ModbusClient.ModbusTcpClient(
        host,
        port=port,
        framer=framer,
    )
    print("connect to server")
    client.connect()
    print("get and verify data")
    try:
        for device, points in datapoints.items():
            for point, value in points.items():
                # print(f"Reading \t {point} from {device[1]}")
                # check if we are reading a 16 or 32 bit value
                if points[point][1] in single_register_markers:
                    rr = client.read_holding_registers(value[0], 1, device[0])
                elif points[point][1] in double_register_markers:
                    rr = client.read_holding_registers(value[0], 2, device[0])
                else:
                    print(f"Invalid data type {points[point][1]}")
                    client.close()
                    return
                decoder = BinaryPayloadDecoder.fromRegisters(
                    rr.registers, byteorder=Endian.BIG
                )
                if points[point][1] == "uint16":
                    decoded = decoder.decode_16bit_uint() / points[point][2]
                elif points[point][1] == "int16":
                    decoded = decoder.decode_16bit_int() / points[point][2]
                elif points[point][1] == "uint32":
                    decoded = decoder.decode_32bit_uint() / points[point][2]
                elif points[point][1] == "int32":
                    decoded = decoder.decode_32bit_int() / points[point][2]
                print(
                    f"{device[1]+':' :<30} {point+':' :<50} {decoded} {textwrap.shorten(points[point][3], width=15)}"
                )
    except ModbusException as exc:
        print(f"Received ModbusException({exc}) from library")
        client.close()
        return
    if rr.isError():
        print(f"Received Modbus library error({rr})")
        client.close()
        return
    if isinstance(rr, ExceptionResponse):
        print(f"Received Modbus library exception ({rr})")
        # THIS IS NOT A PYTHON EXCEPTION, but a valid modbus message
        client.close()
    print("close connection")
    client.close()


if __name__ == "__main__":
    query_victron_modbus_data("192.168.1.5", "502")

Man muss dann noch die IP (hier "192.168.1.5") in die richtige für seine Anlage ändern, entsprechend die device IDs (ganz oben im code) anpassen und anhand der XLSX Datei von Victron die benötigten Daten copy und pasten.

wenn alles klappt bekommt man die Daten ausgegeben wenn man einfach

python main.py

entsprechend ausführt.

Ist nichts besonderes aber ich hoffe es hilft jemanden.

Stand heute verwende ich "pymodbus" in der Version 3.6.6 und Python Version 3.12.1

Die Ausgabe sieht dann so aus (Unschöne Zeilenumbrüche kann man vermeiden wenn das Fenster groß genug ist, dann ist es eine Art Tabelle):

$ python main.py
connect to server
get and verify data
com.victronenergy.pvinverter:  L1 Energy:                             298.78 kWh
com.victronenergy.pvinverter:  L2 Energy:                             587.68 kWh
com.victronenergy.pvinverter:  L3 Energy:                             116.56 kWh
com.victronenergy.pvinverter:  L1 Voltage:                            226.1 V AC
com.victronenergy.pvinverter:  L2 Voltage:                            223.8 V AC
com.victronenergy.pvinverter:  L3 Voltage:                            224.7 V AC
com.victronenergy.pvinverter:  L1 Current:                            1.4 A AC
com.victronenergy.pvinverter:  L2 Current:                            1.4 A AC
com.victronenergy.pvinverter:  L3 Current:                            1.6 A AC
com.victronenergy.pvinverter:  L1 Power:                              325.0 W
com.victronenergy.pvinverter:  L2 Power:                              319.0 W
com.victronenergy.pvinverter:  L3 Power:                              360.0 W
com.victronenergy.grid:        L1 – Voltage:                          223.5 V AC
com.victronenergy.grid:        L1 – Current:                          -2.4 A AC
com.victronenergy.grid:        L2 – Voltage:                          225.6 V AC
com.victronenergy.grid:        L2 – Current:                          -1.2 A AC
com.victronenergy.grid:        L3 – Voltage:                          224.6 V AC
com.victronenergy.grid:        L3 – Current:                          -1.4 A AC
Modbus TCP
2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

0 Answers

Related Resources