Array dynamisch erstellen und in Tuple packen

Probleme bei der Installation?
Antworten
kiaralle
User
Beiträge: 111
Registriert: Donnerstag 19. August 2021, 19:11

Hallo,
ich möchte ein Arry "wert[x]" dynamisch erstellen, erweitern und in ein Tuple packen.
Hoffe ich liege jetzt richtig mit der Bezeichnung Array. In PHP ist es ja eins :roll:
Über den Sinn, lässt sich streiten. Hatten wir ja schon mal. Ich möchte aber etwas lernen.
Gebrauchen kann man es ja immer.

Ich benutze wieder growatt_registers[] und möchte mir das viele schreiben im statement ersparen.
Das statement funktioniert und ich kann so in einem anderen Script meine Tabelle erstellen.
Wie lang die jetzt ist, interessiert erst einmal nicht. mir geht es ums lernen und verstehen, lösen einer Aufgabe.

data sollte in data() sein.

Mit tuple erhalte ich eins, es schaut aber etwas seltsam aus.
...'register[89]', 'register[90]')
Ausschauen sollte es ja so.
..., register[89], register[90])

Code: Alles auswählen

growatt_registers = [
    [0, "Status", 1],
    [1, "Vpv1", 0.1],
    ....
    [88, "Eop_dischrTotal_L", 1],
    [89, "leer89", 0],
    ["90", "ParaChgCurr", 0.1]  # 90
    ]

register_name = []
register_id = []
register_factor = []

data = ("")
platz = ['%s'] * len(growatt_registers)
platz = ", ".join(platz)

for i in range(0, len(growatt_registers)):
    register_id.append("register[" + str(growatt_registers[i][0]) + "]")
    register_name.append(growatt_registers[i][1])
    register_factor.append(growatt_registers[i][2])

print(tuple(register_id))

register_name_lower = ", ".join(register_name)
register_name_lower = register_name_lower.lower()


cursor = connection.cursor()
statement = "INSERT INTO master(" + register_name_lower + " ) VALUES (" + platz + " )"
data = tuple(register_id)
cursor.execute(statement, data)
connection.commit()

Fehlermeldung:
OperationalError: Incorrect integer value: 'register[0]' for column `solaranlage`.`master`.`status` at row 1
Ich verstehe das jetzt so, 'register[0]' gibt es in der Tabelle nicht. Es gibt nur ein register[0]

Wo liegt mein Fehler? Danke :wink:
Benutzeravatar
Dennis89
User
Beiträge: 1385
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

das sieht nicht komisch aus, du schreibst einen String in die Liste. Man würde aber eher einen `f`-String verwenden, anstatt das zusammen puzzeln mit "+".

Code: Alles auswählen

f"register[{growatt_registers[i][0]}]"
"register[89]" ist ein String, ohne die Anführungszeichen wird das zurück gegeben was sich in der Liste `register` an Position 89 befindet. Also wenn du den String nicht drin haben willst dann `append(register[89])` dann hast du den Wert drin.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18054
Registriert: Sonntag 21. Oktober 2012, 17:20

`growatt_registers` scheint ein Konstante zu sein: `GROWATT_REGISTERS`.
Die Namen in den Registern scheinen ja Tabellenfeldnamen zu sein. Diese sollten genausowenig kryptische Abkürzungen enthalten, wie bei allen anderen Namen ja auch. Was soll ein Eop_dischrTotal_L sein? Und ParaChgCurr sollte wohl besser paraguay_change_currency sein.
Warum ist das erste Element des 91. Eintrag plötzlich ein String? Und wenn da wirklich was von 0 bis 90 durchnummieriert wird, kann man den ersten Eintrag auch weglassen.
Man belegt keine Variablen mit Dummy-Werten, wie das bei `data` der Fall scheint.
Über einen Index iteriert man nicht. Strings stückelt man nicht mit + zusammen.
Anscheinend brauchst Du `data` gar nicht, weil Du `register` direkt verwenden könntest.

Code: Alles auswählen

GROWATT_REGISTERS = [
    ["Status", 1],
    ["Vpv1", 0.1],
    ...
    ["Eop_dischrTotal_L", 1],
    ["leer89", 0],
    ["ParaChgCurr", 0.1]  # 90
]

def insert_registers_in_database(connection, registers):
    register_names = [
        name.lower()
        for name, _ in GROWATT_REGISTERS
    ]
    statement = f"INSERT INTO master({','.join(register_names)}) VALUES ({','.join(['%s' * len(register_names)])})"
    with closing(connection.cursor()) as cursor:
        cursor.execute(statement, tuples(registers))
    connection.commit()
Benutzeravatar
__blackjack__
User
Beiträge: 13565
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kiaralle: Python hat da Listen und Wörterbücher wo PHP (assoziative) Arrays und für beides benutzt.

Das ist alles etwas wirr in dem Code. Der sollte mal aufgeräumt werden was die Namen und die Werte und Typen angeht.

Bei `growatt_registers` ist das erste Element der Listen immer eine ganze Zahl, ausser beim letzten, da ist es eine Zeichenkette mit der Darstellung einer ganzen Zahl. Das sollte wohl auch eine Zahl sein.

Wenn die Position eines Elements die Bedeutung des Elements festlegt, also nicht alle Elemente die gleiche Bedeutung haben, dann verwendet man eher Tupel als Listen.

Wie viele Elemente enthält `growatt_registers` denn? Wenn das 91 sind, und das erste Element lückelos aufsteigend einfach nur durchnummeriert ist, dann macht das keinen Sinn, denn dann ist dieser Wert ja immer gleich dem Index. Den hat man schon wenn man per Index darauf zugreift, und den kann man sich mit `enumerate()` einfach erzeugen wenn man direkt über die Elemente der Liste iteriert.

`register_name`, `register_id` und `register_factor` haben Namen die auf *einen* Wert schliessen lassen, stehen aber für Liste mit *vielen* Werten.

`register_factor` wird zwar befüllt, aber nirgends verwendet.

Man muss nicht jedes kleine Zwischnergebnis an einen Namen binden. Schon gar nicht wenn dann Werte unterschiedlichen Typs an einen Namen gebunden werden. Das ist verwirrend.

`data` wird an eine leere Zeichenkette gebunden die nie irgendwo verwendet wird, und später wird der Name dann an ein Tupel gebunden.

Namen sollten erst an Werte gebunden werden wenn man sie braucht. Und beispielsweise nicht zwischen dem binden an leere Listen und dem befüllen in einer Schleife noch andere Sachen machen.

``for i in range(len(sequence)):`` nur um dann `i` für den Zugriff in die Sequenz zu verwenden ist in Python ein „anti pattern“. Man kann direkt über die Elemente von Sequenzen iterieren, ohne den unnötigen Umweg über einen Laufindex.

Und auch feste ”magische” Indexwerte sind nicht gut, weil die nicht zum Verständnis der Bedeutung der Werte beitragen. Man kann die Werte an Namen binden.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Das zweite Argument von `execute()` muss kein Tupel sein, eine Liste geht da auch.

Der Code würde dann so aussehen:

Code: Alles auswählen

from contextlib import closing


def some_function(connection):
    growatt_registers = [
        (0, "Status", 1),
        (1, "Vpv1", 0.1),
        # ....
        (88, "Eop_dischrTotal_L", 1),
        (89, "leer89", 0),
        (90, "ParaChgCurr", 0.1),
    ]

    register_ids = []
    column_names = []
    for register_id, name, _factor in growatt_registers:
        register_ids.append(f"register[{register_id}]")
        column_names.append(name.lower())

    with closing(connection.cursor()) as cursor:
        cursor.execute(
            "INSERT INTO master ({}) VALUES ({})".format(
                ", ".join(column_names), ", ".join(["%s"] * len(column_names))
            ),
            register_ids,
        )
        connection.commit()
Das Problem sollte eigentlich offensichtlich sein. Du generierst da komische ”register IDs” die wie Pythoncode aussehen als Zeichenketten und versuchst diese Zeichenketten dann als Werte für die Datenbankspalten zu übergeben. Was soll die Datenbank denn machen wenn sie "register[0]" als *Wert* bekommt?

Und so ``..., register[89], register[90])`` kann das Tupel bei der Ausgabe nicht aussehen. Das muss ja *Werte* enthalten. ``register[90]`` ist aber kein Wert sondern ein *Ausdruck*. Das ist in PHP auch nicht anders, da geht das auch nicht ein Array mit Ausdrücken zu erstellen.

Die Lösung ist supersimpel wenn man verstanden hat was Code ist, was Zeichenketten sind, was Werte sind, und die Trennung von Python und SQL/Datenbank. Man kann der SQL-Datenbank keine Python-Ausdrücke als Zeichenketten übergeben und erwarten, dass die damit etwas anfangen kann oder auch nur Zugriff auf die Daten im Python-Prozess hat.

Letztlich solltest Du *dieses* Problem aber gar nicht lösen, sondern die Tabelle so strukturieren das die nur vier Spalten hat: id, timestamp, register_number, value. Sofern die Werte alle den gleichen Typ haben.

Der Code sähe dann ungefähr so aus:

Code: Alles auswählen

from contextlib import closing
from datetime import datetime as DateTime


def some_function(connection, register_values):
    growatt_registers = [
        (0, "Status", 1),
        (1, "Vpv1", 0.1),
        # ....
        (88, "Eop_dischrTotal_L", 1),
        (89, "leer89", 0),
        (90, "ParaChgCurr", 0.1),
    ]

    with closing(connection.cursor()) as cursor:
        now = DateTime.now()
        cursor.executemany(
            "INSERT INTO master (`timestamp`, register_id, value) VALUES (%s, %s, %s)",
            (
                (now, register_id, register_values[register_id])
                for register_id, _, _ in growatt_registers
            ),
        )
        connection.commit()
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
kiaralle
User
Beiträge: 111
Registriert: Donnerstag 19. August 2021, 19:11

Hi,
Und so ``..., register[89], register[90])`` kann das Tupel bei der Ausgabe nicht aussehen. Das muss ja *Werte* enthalten. ``register[90]`` ist aber kein Wert sondern ein *Ausdruck*. Das ist in PHP auch nicht anders, da geht das auch nicht ein Array mit Ausdrücken zu erstellen.
Das alles ist nur ein Bastel-Code.

Ich weiß das die Register...[x] bestückt sein müssen.
Das funktioniert auch, also die Abfrage der Wechselrichter über RS485. Und ich speichere ja bereits in eine Datenbank ab.
Nur teste ich jetzt verschiedene Wege der Speicherung.

Letztlich solltest Du *dieses* Problem aber gar nicht lösen, sondern die Tabelle so strukturieren das die nur vier Spalten hat: id, timestamp, register_number, value. Sofern die Werte alle den gleichen Typ haben.
[/quote

Das wäre der einfacherer Aufbau der DB und sicher auch der einfachere Weg.
Diese sollten genausowenig kryptische Abkürzungen enthalten, wie bei allen anderen Namen ja auch. Was soll ein Eop_dischrTotal_L sein? Und ParaChgCurr sollte wohl besser paraguay_change_currency sein.
Diese Bezeichnungen sind in der Modbus-Liste von Growatt so vorgegeben. Es macht aus meiner Sicht Sinn, dabei zu bleiben.
So kann ich später zuordnen was ich im Modbus abrufe. Warum Sachen neu erfinden.
Eop_dischrTotal_L , Eop_dischrTotal_H Das L ist Low und ein H wäre High.
Wenn Low überläuft, wird High mit eingerechnet. 16bit.
Para system charge current = ParaChgCurr

Danke für die vielen Hinweise, ich werde sicher einige testen und umsetzten. Macht wie immer Spass :-)
Benutzeravatar
DeaD_EyE
User
Beiträge: 1127
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Diese Bezeichnungen sind in der Modbus-Liste von Growatt so vorgegeben. Es macht aus meiner Sicht Sinn, dabei zu bleiben.
Das ist in der Welt der Automatisierung völlig normal und das sollten auch mal die alten Python-Häschen lernen.
Man könnte die Namen ändern, was zusätzliche Arbeit ist und dann steht noch die Frage im Raum: für wen überhaupt?

Die Namen würde ich auch nicht ändern, aber der Code muss gut lesbar sein.
Schwierige Aufgabe, wenn man mit kryptischen Namen arbeitet :-D
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
grubenfox
User
Beiträge: 540
Registriert: Freitag 2. Dezember 2022, 15:49

Wobei man hier
kiaralle hat geschrieben: Donnerstag 23. Mai 2024, 20:29 Para system charge current = ParaChgCurr
anstelle von ParaChgCurr als Namen auch Para_system_charge_current hätte nehmen können. Wenn das Para_system_charge_current erst einmal im Listing steht, dann sollte es bei einem ordentlichen Editor beim späteren verwenden des Namens nur 2-4 Tastendrücke brauchen bis einem der vollständige Namen in einer eher kurzen Liste vom Editor vorgeschlagen wird.
Sirius3
User
Beiträge: 18054
Registriert: Sonntag 21. Oktober 2012, 17:20

@DeaD_EyE: bei Micro-Controllern hat man wenig Speicher, hier sogar nur eine Handvoll Register. Zudem wurde früher solche Micro-Controller auch noch mit obskuren Programmiersprachen programmiert, die Variablennamen noch stark einschränkten.
Alles aber kein Grund, auf einem großen System und einer entsprechenden Datenbank kryptische Namen zu verwenden.

@kiaralle: Du hast ja noch zusätzlich das Problem, das sich die Werte aus den Registern gar nicht direkt verwenden lassen. Wenn Du einen High- und einen Low-Wert hast, dann ist der erste Schritt, den in den eigentlichen Wert umzurechnen. Zusätzlich mußt Du die Zahlen ja noch entsprechend skalieren.
Benutzeravatar
__blackjack__
User
Beiträge: 13565
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Diese Namen müssten halt überhaupt gar nicht im Code stehen als *Namen*, also weder in Python noch in SQL. Problem aus meiner Programmierersicht ist nicht das ein Hersteller oder eine Problemdomäne diese Namen benutzt, sondern das so etwas keine Namen in Programmen sein sollten. Also im konkreten Fall keine Spaltennamen in der Datenbank. Falls das Werte sind, in Programm oder Datenbank, wenn man die Registernummern dem vom Hersteller vergebenen Namen zuordnen möchte, zum Beispiel zur Ausgabe, ist das ja okay.

Man könnte beispielsweise in der Datenbank eine Tabelle mit Registernummer (id), Name, Beschreibung, und Faktor haben, wo dann ``90, 'ParaChgCurr', 'Para system charge current', 0.1`` als Datensatz drin steht. Registernummer als Primärschlüssel, es sei denn es gibt einen Grund dafür noch extra eine künstliche ID einzuführen.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
Benutzeravatar
DeaD_EyE
User
Beiträge: 1127
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

@Sirius ich meinte aktuelle Mikrocontroller, auf denen z.B. Micropython lauffähig ist. Davon gibt es viele und die haben mehr Speicher als damalige PCs. Wie viel RAM möchtest du haben? Es gibt Controller mit 8 MiB SPIRAM. Damalige PCs hatten weniger als 1 MiB Arbeitsspeicher.

Theoretisch sollten lange Namen keinen zusätzlichen Speicher verbrauchen, da im Maschinencode die Namen nicht verwendet werden, sondern Adressen, die immer gleich lang sind.

Die Ausnahme bilden Interpreter, die zwangsläufig die Namen speichern müssen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 18054
Registriert: Sonntag 21. Oktober 2012, 17:20

@DeaD_EyE: ich habe nur versucht, in zu Erklären, warum man das "in der Welt der Automatisierung" für "normal" hält. Du bestätigst ja nur, dass das heutzutage alles überholt ist, und niemand mehr sich mit kryptischen Abkürzungen herumschlagen müsste.
Aber schlechte Angewohnheiten halten sich länger als gedacht.
kiaralle
User
Beiträge: 111
Registriert: Donnerstag 19. August 2021, 19:11

@kiaralle: Du hast ja noch zusätzlich das Problem, das sich die Werte aus den Registern gar nicht direkt verwenden lassen. Wenn Du einen High- und einen Low-Wert hast, dann ist der erste Schritt, den in den eigentlichen Wert umzurechnen. Zusätzlich mußt Du die Zahlen ja noch entsprechend skalieren.
Bis auf zwei Werte welche im Wechselrichter auflaufen, werde ich High höchstwahrscheinlich nie brauchen.
Die Leistung, und andere Werte werden bei LOW die 5000 nie knacken. Deshalb verzichte ich darauf mich mit High abzugeben.
Falls doch, kann ich die im PHP umrechnen. Steuerungstechnisch haben die bei mir keine Relevanz.
Diese Namen müssten halt überhaupt gar nicht im Code stehen als *Namen*, also weder in Python noch in SQL.
Aktuell steige ich schrittweise auf die Maria-DB um und du hast auch hier wieder Recht.
Nur mit Registeradressen arbeiten ist aber auch nicht toll. Wenn Growatt am Modbus etwas ändert, muss ich lange zuordnen und suchen.
Allerdings habe ich dann ein Problem wenn die Maria-Db mal nicht läuft, eventuell, müsste ich halt vorsorgen.
Den Status meiner Steuerung frage ich schon über Maria-DB ab.

Meine Steuerung läuft ohne Aussetzer schon seit 3 Monaten.
Eure Anregungen finde ich klasse und ich schreibe weiter an meiner Solarsteuerung mit Lastabwurf und Überschussladung vom E-Auto.
Man muss ein Projekt haben um mehr zu lernen.
Benutzeravatar
__blackjack__
User
Beiträge: 13565
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kiaralle: Das ist eine gruselige Vorstellung. Wenn es einen High-Wert gibt dann sollte man den immer mit verwenden auch wenn der ”immer” 0 ist. Das sind so Annahmen die halt gerne mal *nicht* stimmen, und sei es nur im Fehlerfall wo dann viel zu grosse Werte nur deshalb nicht auffallen weil man da einfach einen Teil der Daten ignoriert hat und einfach mit falschen Werten weiterarbeitet/-rechnet.

Wer sagt denn es sollte nur mit Registeradressen gearbeitet werden? Man kann und sollte die vernünftigen Namen im Programm zuordnen.

Falls Veränderungen an den Registern/Zuordnungen durch die Firma möglich sind, muss man das halt im Datenbankentwurf berücksichtigen, das man solche Änderungen auch darüber nachverfolgen kann. Da muss man als zusätzliches Datum noch eine Version der Zuordnung speichern. Denn man will ja auch weiterhin die alten Daten korrekt lesen und interpretieren können.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
Benutzeravatar
DeaD_EyE
User
Beiträge: 1127
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Sirius3 hat geschrieben: Freitag 24. Mai 2024, 12:12 @DeaD_EyE: ich habe nur versucht, in zu Erklären, warum man das "in der Welt der Automatisierung" für "normal" hält. Du bestätigst ja nur, dass das heutzutage alles überholt ist, und niemand mehr sich mit kryptischen Abkürzungen herumschlagen müsste.
Aber schlechte Angewohnheiten halten sich länger als gedacht.
@Sirius3
Das wissen wir Automatisierer, aber manche Hersteller interessiert es nicht. Mit kryptischen Namen von anderen Herstellern werde ich öfters genervt. Bevor Industrieanlagen überhaupt gebaut werden, wird alles berechnet. Wenn man z.B. Adernummern verwenden soll, wird extra abgerechnet. Wenn die Namensgebung von Drittherstellern geändert werden soll, wird das extra abgerechnet. Jede zusätzliche Tätigkeit kostet Geld.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 18054
Registriert: Sonntag 21. Oktober 2012, 17:20

Natürlich kostet alles Geld. Und am meisten Geld kostet es, Bugs in fertigen Produkten zu reparieren, weil man am Anfang an guten Namen gespart hat.
Meine Strategie ist es, einen möglichst dünnen Layer auf die externe Schnittstelle zu legen, wo alle Implementierungsdetails in klar verständliche Namen, Methoden, Klasse, etc. umgewandelt werden. Das was der dünne Layer kostet, spart man an anderer Stelle hundertfach.
Antworten