pymodbus liest zu wenig Register.

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

Hallo,

ich dachte ich kenne mich jetzt ein wenig aus, aber scheinbar doch nicht :-)
Ich lese aus Wechselrichter per Modbus Register aus.
Als ersten Versuch habe ich "minimalmodbus" verwendet. Jedes Register wurde einzeln abgefragt.
Es hat funktioniert, aber ich hatte ständig Verbindungsproblem.
Also stieg ich auf pymodbus um. Verbindungsprobleme sind Vergangenheit, aber er liest nicht alle Register.

Was mache ich, ich binde den Client so ein

Code: Alles auswählen

client_wr_1 = ModbusSerialClient(
    port="/dev/ttyUSB0",
    startbit=1,
    databits=8,
    parity="N",
    stopbits=2,
    errorcheck="crc",
    baudrate=9600,
    method="RTU",
    timeout=3,
    )
Ich rufe die Register in einem Schwung ab. Dazu benutze ich eine Liste welche unter "minimalmodbus" super funktioniert hat.

Code: Alles auswählen

spf_register = {
      0: "Status",
      1: "Vpv1",
      2: "Vpv2",
      3: "Ppv1H",
      4: "Ppv1L",
      5: "Ppv2H",
      6: "Ppv2L",
      7: "Buck1Curr",
      8: "Buck2Curr",
      9: "OP_WattH",  # Output power
      10: "OP_WattL",  # Output power
      11: "OP_VAH",  # Output apparent power
      12: "OP_VAL",  # Output apparent power
      .
      .
      .
      67: "Eac_dischrtotalL",
      68: "Acchrcurr",  # kein Eintrag
      69: "AC_dischrwattH",  # AC discharge apparent power 1 oder 65535
      70: "AC_dischrwattL",  # AC discharge apparent power
      71: "AC_dischrvaH",  # AC discharge apparent power 1 oder 65535
      72: "AC_dischrvaL",  # AC discharge apparent power
      73: "Bat_dischrwattH",  # kein Eintrag
      74: "Bat_dischrwattL",  # kein Eintrag
      75: "Bat_dischrvaH",  # kein Eintrag
      76: "Bat_dischrvaL",  # kein Eintrag
      77: "Bat_wattH",  # Leistungsfluss Batt
      78: "Bat_wattL",  # Leistungsfluss Batt
      79: "test",
      80: "Batovercharge",
      81: "Mpptfanspeed",
      82: "Invfanspeed",
    }
Ich ermittle mit len(spf_register) die Anzahl der Einträge um flexibel für spätere Einträge zu bleiben.
Eine weiter Liste enthält die Faktoren mit dem ich diese Werte multipliziere (laut Modbustabelle)

Code: Alles auswählen

spf_register_multipli = {
     0: 1,  # geprüft
      1: 0.1,
      2: 0.1,
      3: 0.01,  # geprüft
      4: 0.01,  # geprüft
      5: 0.01,  # geprüft
      6: 0.01,  # 
.
.
.
      73: 0.1,
      74: 0.1,
      75: 0.1,
      76: 0.1,
      77: 0.1,  # geprüft
      78: 0.1,  # geprüft
      79: 0.1,
      80: 0.1,
      81: 0.1,
      82: 0.1,
    }

Dann rufe ich die Register ab.
Von 0 bis len(spf_register_multipli) Liste ist ja egal, da ja beide gleich lang sind.
So wie ich es sehe, bleibt mein Auslesevorgng bei ca. 70 hängen. 125 sollten doch möglich sein (laut modbus)

Code: Alles auswählen

def register_complett(spf_register, spf_register_multipli):
    client_wr_1.connect()
    row_wr1 = client_wr_1.read_input_registers([b]0, len(spf_register_multipli)[/b])
    client_wr_1.close()


    try:
        for spf_register_nr in spf_register:
            register_value_1 = float(row_wr1.registers[spf_register_nr] * spf_register_multipli[spf_register_nr])
            json_body = [
                {
                    "measurement": spf_register[spf_register_nr],
                    "tags": {
                        "user": user
                        },
                    "fields": {
                       spf_register[spf_register_nr] : register_value_1
                        }
                    }
                ]
            try:
                influx_client_1.write_points(json_body)
            except Exception as db_error:
                print('Influx_1_c write unsuccessfully')
    except:
        print('WR1_c Register n.i.O.')


Gruß Ralf
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kiaralle: Was sollen denn die `_1`-Namenszusätze? Man nummeriert keine Namen. Das ist dann auch noch mal extrasinnlos wenn es sowieso nur `_1` gibt.

Das was Du Listen nennst sind Wörterbücher und wenn die Schlüssel von 0 an aufsteigend und zusammenhängend haben, dann sollten es keine Wörterbücher sein, denn das macht keinen Sinn da dann keine Listen für zu verwenden. Sie müssen zusammenhängend sein, weil sonst das Lesen der Register von 0 bis `len()` von der Datenstruktur ja nicht funktioniert.

Wobei es nicht zwei Listen sein sollten, sondern nur eine. Zusammengehörende Daten speichert man auch zusammen und nicht in “parallelen“ Datenstrukturen. Hier zum Beispiel eine Liste mit Tupeln aus Registernamen und Multiplikator.

Alles was eine Funktion ausser Konstanten benötigt, bekommt sie als Argument(e) übergeben. Der Modbusclient darf nicht einfach magisch aus dem Nichts kommen. Das gleiche gilt für `user`.

Keine nackten ``except`` ohne konkrete Ausnahme. Und dann nicht einfach weitermachen als wäre nichts passiert wenn es eine unerwartete Ausnahme gab. ``except Exception:`` ist nicht besser. Und das dann noch `db_error` zu nennen ist mutig.

Ungetestet:

Code: Alles auswählen

def process_all_registers(modbus_client, registers, influx_client, user):
    modbus_client.connect()
    result = modbus_client.read_input_registers(0, len(registers))
    modbus_client.close()

    for value, (name, multiplier) in zip(result.registers, registers):
        influx_client.write_points(
            [
                {
                    "measurement": name,
                    "tags": {"user": user},
                    "fields": {name: value * multiplier},
                }
            ]
        )
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Vielen Dank für deine Hinweise
Was sollen denn die `_1`-Namenszusätze?
Weil es eigentlich drei Wechselrichter sind. Sollte das ein Problem sein?

Das mit den zip hatte ich schon mal irgendwo gelesen aber wieder vergessen. Wieder was gelernt.
Ich hatte das mit dem Wörterbuch aus einem andern Script übernommen. War wohl wieder ein Fehler der Anfänger :-)

Deine Hinweise werde ich heute Abend gleich umsetzen.
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Das mit der _1 ist kein Problem. Es ist recht sinnfrei.
Wenn man anfängt Namen zu nummerieren, ist das ein deutliches Zeichen dafür, dass man eigentlich eine Datenstruktur verwenden möchte. Beispielsweise eine Liste.
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Ein Hallo in die Runde.
War jetzt etwas länger offline als gedacht.

@__blackjack__
ich versuche gerade deinen Vorschlag zu verstehen, bekomme aber immer verschiedene Fehlermeldungen.
Meinen Code habe ich jetzt mal komplett an gehangen. Sonnst versteht man sich eher selten.

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Sep  8 17:30:52 2023

@author: ralf
"""
from influxdb import InfluxDBClient
from pymodbus.client import ModbusSerialClient

client_wr_1 = ModbusSerialClient(
    port="/dev/ttyUSB0",
    startbit=1,
    databits=8,
    parity="N",
    stopbits=2,
    errorcheck="crc",
    baudrate=9600,
    method="RTU",
    timeout=3,
    )


"""
Nicht alle Register sind beim SPF5000ES belegt!
"""
spf_register_nr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
                   11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
                   21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                   31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
                   41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
                   51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
                   61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
                   71, 72, 73, 74, 75, 76,78, 79, 80, 81, 82]

spf_register_name = ["Status", "Vpv1", "Vpv2", "Ppv1H", "Ppv1L", "Ppv2H",
                     "Ppv2L", "Buck1Curr", "Buck2Curr", "OP_WattH", "OP_WattL",
                     "OP_VAH", "OP_VAL", "ACChr_WattH", "ACChr_WattL",
                     "ACChr_VAH", "ACChr_VAL", "Bat_Volt", "BatterySOC",
                     "BusVolt", "GridVolt", "LineFreq", "OutputVolt",
                     "OutputFreq", "OutputDCV", "InvTemp", "DCDCTemp",
                     "LoadPercent", "Bat_s_Volt", "Bat_dspb_V", "TimeTotalH",
                     "TimeTotalL", "Buck1Temp", "Buck2Temp", "OP_Curr",
                     "Inv_Curr", "AC_InWattH", "AC_InWattL", "AC_InVAH",
                     "AC_InVAL", "Faultbit", "Warnbit", "Faultvalue",
                     "Warnvalue",  "DTC", "CheckStep", "ProductionLM",
                     "ConstPOKF", "Epv1_todayH", "Epv1_todayL", "Epv1_totalH",
                     "Epv1_totalL", "Epv2_todayH", "Epv2_todayL", "Epv2_totalH",
                     "Epv2_totalL", "Eac_chrtodayH", "Eac_chrtodayL", "Eac_chrtotalH",
                     "Eac_chrtotalL", "Ebat_chrtodayH", "Ebat_chrtodayL", "Ebat_chrtotalH",
                     "Ebat_chrtotalL", "Eac_dischrtodayH", "Eac_dischrtodayL",
                     "Eac_dischrtotalH", "Eac_dischrtotalL", "Acchrcurr",
                     "AC_dischrwattH", "AC_dischrwattL", "AC_dischrvaH",
                     "AC_dischrvaL", "Bat_dischrwattH", "Bat_dischrwattL",
                     "Bat_dischrvaH", "Bat_dischrvaL", "Bat_wattH", "Bat_wattL",
                     "Batovercharge", "Mpptfanspeed", "Invfanspeed"]

spf_register_multipli = [1, 0.1, 0.1, 0.01, 0.01, 0.01, 0.01, 0.1, 0.1, 0.01, 0.01,
                         0.01, 0.01, 0.1, 0.1, 0.1, 0.1, 0.01, 1, 0.1,0.1,
                         0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.5,
                         0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
                         0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
                         0.1, 0.1,  0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
                         0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
                         0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]

# regitdata = zip(spf_register_nr, spf_register_name, spf_register_multipli)

def read_all_register(spf_register_nr, spf_register_name, spf_register_multipli):

    client_wr_1.connect()
    result = client_wr_1.read_input_registers(0, len(spf_register_nr))
    client_wr_1.close()


    for value in zip(spf_register_nr, spf_register_name):
        print(value)

read_all_register(spf_register_nr, spf_register_name, spf_register_multipli)

regitdata würde alles schön zusammengefasst anzeigen. Soweit habe ich schon was gelernt und verstanden.

Bei for value ... bekomme ich folgende Fehlermeldungen

Code: Alles auswählen

  for value in zip(result.spf_register_nr, spf_register_name):
AttributeError: 'ReadInputRegistersResponse' object has no attribute 'spf_register_nr'

Code: Alles auswählen

  for value,(spf_register_name) in zip(spf_register_nr, spf_register_name):
ValueError: not enough values to unpack (expected 2, got 1)

Ein print(result) bringt mir 82 raus. result enthält also 82 Werte?

Die Schleife verstehe ich noch nicht so ganz. Wir bastel uns ein Object(result.spf_register_nr) mit welchen Inhalt?

Gruß Ralf
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kiaralle: In meinem Code muss `registers` eine Sequenz mit Paaren aus Name und Multiplikant sein. Weil man wie gesagt zusammengehörende Werte nicht in mehrere parallele Listen steckt. Das ist total unübersichtlich und fehleranfällig. Der Beweis: Nur zwei der drei Listen haben die gleiche Länge. Ich vermute mal in den ersten beiden fehlt jeweils ein Eintrag, denn in der ersten Liste ist die 77 nicht drin. Womit dann zumindest mal ein Wert zu wenig gelesen würde und ein paar Register am Ende den falschen Namen und Multiplikanten zugeordnet werden.

Wenn jetzt beispielsweise der Multiplikant bei "CheckStep" falsch wäre oder aus anderen Gründen geändert werden müsste, dann will man doch nicht ernstaft anfangen zu zählen an welcher Position man den Wert dazu in der anderen Liste findet.

Und eine Liste die aufsteigende Werte von 0 bis 82 enthält ist ja auch eher eine Aufgabe für Leute die viel zu viel Langeweile haben. So eine Folge von Zahlen kann man sich jederzeit mit `range()` erstellen, oder wenn man die zusätzlich beim iterieren benötigt mit `enumerate()`.

Das muss natürlich ``result.registers`` heissen. Sorry.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

@ __blackjack__

ich glaube der Groschen ist gefallen.
Nur als Beispiel:

Code: Alles auswählen

registers = [
		["Registername_1", 1],
		["Registername_2", 0.1],
		["Registername_3", 0.01],
		["Registername_4", 1]	
]
Oder habe ich das jetzt wieder falsch verstanden?

Code: Alles auswählen

    for value, (name, multiplier) in zip(result.registers, registers):
value ist der Wert des Registers welcher über modbus ermittelt wurde.
name und multiplier wird aus dem registers gezogen

Das funktioniert jetzt bei mir. Dank deiner Hilfe. Muss ja auch mal gesagt werden :)
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Hallo, ich melde mich noch mal,

das Script läuft nun seit ein paar Tagen, jedoch bleibt es ab und zu hängen. Das Script läuft als Dienst auf dem Rasperry.
Eine Fehlermeldung über "systemctl status" ist nicht möglich, es kommt nichts.
Die Möglichkeiten für Fehlermöglichkeiten sind aus meiner Sicht nicht doch eher begrenzt.

1. Zugriff auf die USB-Schnittstelle nicht möglich
2. pymodbus fragt Daten ab, jedoch müsste ich prüfen ob der Inhalt vollständig ist. welche Möglichkeiten gibt es da.
3. Daten werden in die Influxdb geschrieben. Ein anderes Script greift aber gleichzeitig auf die selbe DB lesend zu. Kann es da zu Konflikten kommen?

Ein Neustart des Dienstes beseitigt das Problem.

Code: Alles auswählen

def wr1_all_registers(modbus_client_1, registers, influx_client_1):

    modbus_client_1.connect()
    result = modbus_client_1.read_input_registers(0, len(registers))
    modbus_client_1.close()

    if not result.isError():
        for value, (name, multiplier) in zip(result.registers, registers):
            influx_client_1.write_points(
                [
                    {
                        "measurement": name,
                        "tags": {"user": user},
                        "fields": {name: round(float(value * multiplier), 2)},
                        }
                    ]
                )
Die _1 sind wie immer bei mir wegen mehrfacher Datenbanken und Clients. Wird später mal geändert.

Wenn ich den Dienst abschalte und das Script mit python3 script.py aufrufe, laufen lasse und dann den USB-Stecker ziehe, bricht das Script ab. Hatte ich mit kurz mit Try; Except; eingegrenzt und ausgewertet.
Fehlermeldung ist klar, Schnittstelle nicht erreichbar.
Mit if not result.isError(): versuche ich das schreiben bei Fehler von USB zu verhindern.
Ist der Weg so i.O.? Ausreichend oder Käse?

Sollte pymodbus zu wenig Daten lesen (Datenfehler), passiert hier was? Schreibt er was, oder dreht er im Kreise?

Code: Alles auswählen

 for value, (name, multiplier) in zip(result.registers, registers):
Gruß Ralf
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Ein print(result) zeigt mir 91 an.
Im optimalen Fall hab ich diese Anzahl von Register auch.
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Einfall von mir, geht das durch? Scheint zu funktionieren.

if not result.isError() and len(result.registers) == len(registers):
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Was sollen die _1-Postfixe? Das sagt nichts aus und kann weg.
kiaralle hat geschrieben: Montag 25. September 2023, 18:28 Eine Fehlermeldung über "systemctl status" ist nicht möglich, es kommt nichts.
Wenn Dein Programm irgendwo hängen bleibt, und keine Meldung kommt, dann mußt Du dafür sorgen, dass entsprechende Log-Meldungen ausgegeben werden, an denen man erkennen kann, wo das Programm hängen bleibt, alles andere ist nur wild spekulieren und wenig zielführend.

Z.B. gibst Du bei result.isError() nicht aus, so dass das Programm endlos Fehler produzieren kann, ohne dass Du das mitbekommst.
Bei modbus_client hast Du einen timeout angegeben, so dass da nichts hängen bleiben sollte, wie das bei influx_client aussieht, weiß ich nicht, weil Du den Code ja nicht zeigst.
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

hi,
dann mußt Du dafür sorgen, dass entsprechende Log-Meldungen ausgegeben werden
Reicht da ein print()?

Mein Code

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Sep  8 17:30:52 2023

@author: ralf
"""
from influxdb import InfluxDBClient
from pymodbus.client import ModbusSerialClient
import csv
import time

def setuplesen():
    with open('/var/www/html/solarsteuerung/setup.txt', encoding="utf8") as file:
        data = csv.reader(file, delimiter=";")
        return list(data)

setupread = setuplesen()
bd_name = setupread[11]
logindaten = setupread[12]

host = "localhost"
port = 8086
user = logindaten[0]
password = logindaten[1]
#dbname_1 = bd_name[0]
influx_client_1 = InfluxDBClient(host, port, user, password, bd_name[0])
#dbname_2 = bd_name[1]
influx_client_2 = InfluxDBClient(host, port, user, password, bd_name[1])

modbus_client_1 = ModbusSerialClient(
    port="/dev/ttyUSB0",
    startbit=1,
    databits=8,
    parity="N",
    stopbits=2,
    errorcheck="crc",
    baudrate=9600,
    method="RTU",
    timeout=3,
    )

modbus_client_2 = ModbusSerialClient(
    port="/dev/ttyXRUSB0",
    startbit=1,
    databits=8,
    parity="N",
    stopbits=2,
    errorcheck="crc",
    baudrate=9600,
    method="RTU",
    timeout=3,
    )



registers = [
    ["Status", 1],  # 0
    ["Vpv1", 0.1],
    ["Vpv2", 0.1],
    ["Ppv1H", 0.1],
    ["Ppv1L", 0.1],
    ["Ppv2H", 0.1],
    ["Ppv2L", 0.1],
    ["Buck1Curr", 0.1],
    ["Buck2Curr", 0.1],
    ["OP_WattH", 0.1],
    ["OP_WattL", 0.1],  # 10
    ["OP_VAH", 0.1],
    ["OP_VAL", 0.1],
    ["ACChr_WattH", 0.1],
    ["ACChr_WattL", 0.1],
    ["ACChr_VAH", 0.1],
    ["ACChr_VAL", 0.1],
    ["Bat_Volt", 0.01],
    ["BatterySOC", 1],
    ["BusVolt", 0.1],
    ["GridVolt", 0.1],  # 20
    ["LineFreq", 0.01],
    ["OutputVolt", 0.1],
    ["OutputFreq", 0.01],
    ["OutputDCV", 0.1],
    ["InvTemp", 0.1],
    ["DCDCTemp", 0.1],
    ["LoadPercent", 0.1],
    ["Bat_s_Volt", 0.1],
    ["Bat_dspb_V", 0.1],
    ["TimeTotalH", 0.5],  # 30
    ["TimeTotalL", 0.5],
    ["Buck1Temp", 0.1],
    ["Buck2Temp", 0.1],
    ["OP_Curr", 0.1],
    ["Inv_Curr", 0.1],
    ["AC_InWattH", 0.1],
    ["AC_InWattL", 0.1],
    ["AC_InVAH", 0.1],
    ["AC_InVAL", 0.1],
    ["Faultbit", 1],  # 40
    ["Warnbit", 1],
    ["Faultvalue", 1],
    ["Warnvalue", 1],
    ["DTC", 1],
    ["CheckStep", 1],
    ["ProductionLM", 1],
    ["ConstPOKF", 0.1],
    ["Epv1_todayH", 0.1],
    ["Epv1_todayL", 0.1],
    ["Epv1_totalH", 0.1],  # 50
    ["Epv1_totalL", 0.1],
    ["Epv2_todayH", 0.1],
    ["Epv2_todayL", 0.1],
    ["Epv2_totalH", 0.1],
    ["Epv2_totalL", 0.1],
    ["Eac_chrtodayH", 0.1],
    ["Eac_chrtodayL", 0.1],
    ["Eac_chrtotalH", 0.1],
    ["Eac_chrtotalL", 0.1],
    ["Ebat_chrtodayH", 0.1],  # 60
    ["Ebat_chrtodayL", 0.1],
    ["Ebat_chrtotalH", 0.1],
    ["Ebat_chrtotalL", 0.1],
    ["Eac_dischrtodayH", 0.1],
    ["Eac_dischrtodayL", 0.1],
    ["Eac_dischrtotalH", 0.1],
    ["Eac_dischrtotalL", 0.1],
    ["Acchrcurr", 0.1],
    ["AC_dischrwattH", 0.1],
    ["AC_dischrwattL", 0.1],  # 70
    ["AC_dischrvaH", 0.1],
    ["AC_dischrvaL", 0.1],
    ["Bat_dischrwattH", 0.1],
    ["Bat_dischrwattL", 0.1],
    ["Bat_dischrvaH", 0.1],
    ["Bat_dischrvaL", 0.1],
    ["Bat_wattH", 0.1],
    ["Bat_wattL", 0.1],
    ["uwSlaveExistCnt", 1],
    ["Batovercharge", 1],  # 80
    ["Mpptfanspeed", 1],
    ["Invfanspeed", 1],
    ["TotalChgCur", 0.1],
    ["-84-", 0],
    ["Eop_dischrToday_H", 1],
    ["Eop_dischrToday_L", 1],
    ["Eop_dischrTotal_H", 1],
    ["Eop_dischrTotal_L", 1],
    ["-89-", 0],
    ["ParaChgCurr", 0.1]  # 90
    ]


def wr1_all_registers(modbus_client_1, registers, influx_client_1):

    modbus_client_1.connect()
    result = modbus_client_1.read_input_registers(0, len(registers))
    modbus_client_1.close()

    if not result.isError() and len(result.registers) == len(registers):
        for value, (name, multiplier) in zip(result.registers, registers):
            influx_client_1.write_points(
                [
                    {
                        "measurement": name,
                        "tags": {"user": user},
                        "fields": {name: round(float(value * multiplier), 2)},
                        }
                    ]
                )


def wr2_all_registers(modbus_client_2, registers, influx_client_2):

    modbus_client_2.connect()
    result = modbus_client_2.read_input_registers(0, len(registers))
    modbus_client_2.close()

    if not result.isError() and len(result.registers) == len(registers):
        for value, (name, multiplier) in zip(result.registers, registers):
            influx_client_2.write_points(
                [
                    {
                        "measurement": name,
                        "tags": {"user": user},
                        "fields": {name: round(float(value * multiplier), 2)},
                        }
                    ]
                )




while True:
    wr1_all_registers(modbus_client_1, registers, influx_client_1)
    time.sleep(0.5)
    wr2_all_registers(modbus_client_2, registers, influx_client_2)
    time.sleep(0.5)

Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

Funktionen sind dazu da, dass man Code wiederverwenden kann. Wenn man zwei Funktionen mit exakt dem selben Inhalt hat, dann ist das das Gegenteil von "wiederverwenden".
csv-Dateien sind ein schlechter Ersatz für richtige Konfigurationsdateien.
Was steht denn in den Zeilen 3 bis 9?
Aller Code sollte in Funktionen stehen.
Eine Konfigdatei sollte nicht unter /var/www/html/ stehen, oder soll jeder Dein Passwort lesen können?

Code: Alles auswählen

from influxdb import InfluxDBClient
from pymodbus.client import ModbusSerialClient
import logging
import csv
import time
logger = logging.getLogger(__name__)

HOST, PORT = "localhost", 8086
SETUP_FILENAME = '/var/www/html/solarsteuerung/setup.txt'

REGISTERS = [
    ["Status", 1],  # 0
    ["Vpv1", 0.1],
    ["Vpv2", 0.1],
    ["Ppv1H", 0.1],
    ["Ppv1L", 0.1],
    ["Ppv2H", 0.1],
    ["Ppv2L", 0.1],
    ["Buck1Curr", 0.1],
    ["Buck2Curr", 0.1],
    ["OP_WattH", 0.1],
    ["OP_WattL", 0.1],  # 10
    ["OP_VAH", 0.1],
    ["OP_VAL", 0.1],
    ["ACChr_WattH", 0.1],
    ["ACChr_WattL", 0.1],
    ["ACChr_VAH", 0.1],
    ["ACChr_VAL", 0.1],
    ["Bat_Volt", 0.01],
    ["BatterySOC", 1],
    ["BusVolt", 0.1],
    ["GridVolt", 0.1],  # 20
    ["LineFreq", 0.01],
    ["OutputVolt", 0.1],
    ["OutputFreq", 0.01],
    ["OutputDCV", 0.1],
    ["InvTemp", 0.1],
    ["DCDCTemp", 0.1],
    ["LoadPercent", 0.1],
    ["Bat_s_Volt", 0.1],
    ["Bat_dspb_V", 0.1],
    ["TimeTotalH", 0.5],  # 30
    ["TimeTotalL", 0.5],
    ["Buck1Temp", 0.1],
    ["Buck2Temp", 0.1],
    ["OP_Curr", 0.1],
    ["Inv_Curr", 0.1],
    ["AC_InWattH", 0.1],
    ["AC_InWattL", 0.1],
    ["AC_InVAH", 0.1],
    ["AC_InVAL", 0.1],
    ["Faultbit", 1],  # 40
    ["Warnbit", 1],
    ["Faultvalue", 1],
    ["Warnvalue", 1],
    ["DTC", 1],
    ["CheckStep", 1],
    ["ProductionLM", 1],
    ["ConstPOKF", 0.1],
    ["Epv1_todayH", 0.1],
    ["Epv1_todayL", 0.1],
    ["Epv1_totalH", 0.1],  # 50
    ["Epv1_totalL", 0.1],
    ["Epv2_todayH", 0.1],
    ["Epv2_todayL", 0.1],
    ["Epv2_totalH", 0.1],
    ["Epv2_totalL", 0.1],
    ["Eac_chrtodayH", 0.1],
    ["Eac_chrtodayL", 0.1],
    ["Eac_chrtotalH", 0.1],
    ["Eac_chrtotalL", 0.1],
    ["Ebat_chrtodayH", 0.1],  # 60
    ["Ebat_chrtodayL", 0.1],
    ["Ebat_chrtotalH", 0.1],
    ["Ebat_chrtotalL", 0.1],
    ["Eac_dischrtodayH", 0.1],
    ["Eac_dischrtodayL", 0.1],
    ["Eac_dischrtotalH", 0.1],
    ["Eac_dischrtotalL", 0.1],
    ["Acchrcurr", 0.1],
    ["AC_dischrwattH", 0.1],
    ["AC_dischrwattL", 0.1],  # 70
    ["AC_dischrvaH", 0.1],
    ["AC_dischrvaL", 0.1],
    ["Bat_dischrwattH", 0.1],
    ["Bat_dischrwattL", 0.1],
    ["Bat_dischrvaH", 0.1],
    ["Bat_dischrvaL", 0.1],
    ["Bat_wattH", 0.1],
    ["Bat_wattL", 0.1],
    ["uwSlaveExistCnt", 1],
    ["Batovercharge", 1],  # 80
    ["Mpptfanspeed", 1],
    ["Invfanspeed", 1],
    ["TotalChgCur", 0.1],
    ["-84-", 0],
    ["Eop_dischrToday_H", 1],
    ["Eop_dischrToday_L", 1],
    ["Eop_dischrTotal_H", 1],
    ["Eop_dischrTotal_L", 1],
    ["-89-", 0],
    ["ParaChgCurr", 0.1]  # 90
]


def setuplesen():
    with open(SETUP_FILENAME, encoding="utf8") as file:
        data = csv.reader(file, delimiter=";")
        return list(data)


def read_all_registers(modbus_client, registers, influx_client):
    logging.info("read registers")
    result = modbus_client.read_input_registers(0, len(registers))

    if not result.isError() and len(result.registers) == len(registers):
        logging.info("registers: %s", result)
        for value, (name, multiplier) in zip(result.registers, registers):
            influx_client.write_points([{
                "measurement": name,
                "tags": {"user": user},
                "fields": {name: round(float(value * multiplier), 2)},
            }])
    else:
        logging.error("result: %s", result)

def main():
    logging.basicConfig(level=logging.INFO)
    setupread = setuplesen()
    db_names = setupread[11]
    username, password = setupread[12][:2]
    buses = []
    for db_name, port in zip(db_names, ["/dev/ttyUSB0", "/dev/ttyXRUSB0"])
        influx_client = InfluxDBClient(host, port, user, password, db_name)
        modbus_client = ModbusSerialClient(
            port=port,
            startbit=1,
            databits=8,
            parity="N",
            stopbits=2,
            errorcheck="crc",
            baudrate=9600,
            method="RTU",
            timeout=3,
        )
        buses.append((influx_client, modbus_client))

    while True:
        for influx_client, modbus_client in buses:
            read_all_registers(modbus_client, REGISTERS, influx_client)
            time.sleep(0.5)

if __name__ == "__main__":
    main()
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

Funktionen sind dazu da, dass man Code wiederverwenden kann. Wenn man zwei Funktionen mit exakt dem selben Inhalt hat, dann ist das das Gegenteil von "wiederverwenden".
Hab ich nur für die Testversion gemacht. So kann ich den einen Wechselrichter mal schnell weg nehmen.
Wenn es sicher läuft, fallen die Funktionen zum teil oder ganz weg. Der Code ist ja von der Länger überschaubar.
Was steht denn in den Zeilen 3 bis 9?
Ja da stehen die Zugangsdaten und einige Systemparameter. Mein Plan ist die Zugangsdaten in einer extra Datei auszulagern und mit PHP verschlüsselt abzulegen., eventuell Mysql oder eine Datei. Muss ich mich aber erst mal einlesen... mit der Verschlüsselung.
Der Raspi läuft im lokalen privaten Netzwerk und ist nicht nach außen zugänglich.
Die Wechselrichter liefen original auf der Growatt-Cloud in China. Ob mein jetziges Risiko mit der lokalen Lösung nicht kleiner ist?
Aber egal, deine Hinweise werden wohlwollend angenommen :wink:
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

verschlüsseln kann man auch mit Python, da muss man sich nicht mit PHP herumärgern...
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei das recht sinnlos klingt, denn um die Daten benutzen zu können muss man sie ja entschlüsseln, also steht im Programm wie man die wieder entschlüsseln kann. Das kann man sich dann auch sparen.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
kiaralle
User
Beiträge: 132
Registriert: Donnerstag 19. August 2021, 19:11

@__blackjack__
da wirst du recht haben.
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

__blackjack__ hat geschrieben: Dienstag 26. September 2023, 20:43 Wobei das recht sinnlos klingt, denn um die Daten benutzen zu können muss man sie ja entschlüsseln, also steht im Programm wie man die wieder entschlüsseln kann. Das kann man sich dann auch sparen.
Das ist eher falsch. Wenn das ein Problem wäre, dann würden die ganzen Public-Key-Kryptosysteme, die wir so nutzen (bei https, ssh, pgp, ...) nicht als sicher gelten.
Man muss dabei den private-Key eben geheim und private halten, während der zweite Key (der public-Key) für die Öffentlichkeit bestimmt ist. Dadurch dass theoretisch jeder den Algorithmus zum Verschlüsseln und den zum Entschlüsseln überprüfen kann (z.b. bei RSA) sollte hoffentlich gesichert sein das das Teil sicher ist und keine Hintertüren enthält.

Gut, mangels persönlichem Hintergrundwissens kann ich das selbst nicht prüfen und für mich nur hoffen dass uns da kein Mist erzählt wird in Sachen der Verschüsselungsalgorithmen. Und bei den benutzten Bibliotheken (sowohl bei den Python-Bibliothkenen (habe ich eben erst beim googlen erfahren welche es da wohl so gibt), als auch bei denen die hier der Browser und das Mailprogramm nutzt) kann ich nur hoffen dass die auch so implementiert sind wie sie sein sollten.

Die symmetrische Verschlüsselung ist dann noch ein anderes Thema. Da hat es nur einen Schlüssel und der muss geheim bleiben. Aber auch hier gilt jedenfalls: die genutzten Algorithmen sollten öffentlich sein, damit jemand der Ahnung von dem ganzen Kram hat, da mal einen gründlichen Blick drauf werfen kann.

Soweit mein Wissen zum Thema...
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da ist ein Problem, PKI hin oder her. Wenn jemand Zugriff auf das System hat (und darum geht es hier), dann kann derjenige die vorhandene Entschlüsselung auch einfach anwenden, und die Daten abgreifen. Darum ist es durchaus üblich, dass bestimmte Systeme nach einem Neustart ein menschlich anzugebendes secret brauchen. Deine keys im system wallet zb werden mit deiner Anmeldung freigeschaltet. Der OP hier wird aber ein so komplexes System garantiert nicht hinbekommen. Darum ist es dann auch wertlos.
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

@grubenfox: die Algorithmen sind so weit mathematisch geprüft, das ist aber nicht der eigentliche Punkt. Um an die Daten heranzukommen, braucht man die Schlüssel und hat dann alles im Klartext, auf einem Rechner.
Antworten