"""Wallbox-Verlauf vergangener Ladungen aus EnergyFlow nach skoda_ladepunkte. Den laufenden Verlauf schreibt gatherSkodaData.py selbst, im Minutentakt. Fuer alles davor - und fuer Zeiten, in denen der Manager nicht lief - gibt es nur EnergyFlow, und das wird nach zwoelf Monaten ausgeduennt. Dieses Skript holt die Fuenf-Minuten-Werte heraus, solange sie noch da sind. python3 skoda_ladepunkte_nachtragen.py 2026-09-11 2026-09-14 python3 skoda_ladepunkte_nachtragen.py 2026-09-11 2026-09-14 --probe Ohne --probe wird geschrieben. Es darf beliebig oft laufen: nachgetragene Zeilen im Zeitraum werden vorher entfernt, und wo der Manager selbst schon Punkte geschrieben hat, traegt es nichts nach - die sind genauer. EnergyFlow kennt keinen Zaehlerstand, nur Leistungen. wh_total wird deshalb gerechnet: die Leistung ueber die Zeit aufsummiert und dann an die beiden Zaehlerstaende angeglichen, die die Tabelle skoda vor und nach der Ladung kennt. Fehlt einer davon, bleibt wh_total leer. Jede so entstandene Zeile traegt quelle = 'energyflow'. """ import sys import datetime import mysql.connector as mc import konfig LUECKE = datetime.timedelta(hours=3) # wie LADUNG_LUECKE_S in ajax/skoda.php RUHE_KW = 0.5 # wie _LADEPUNKT_KW in gatherSkodaData.py WALLBOXEN = { # Name: (Leistung in EnergyFlow, Zaehlerspalte in skoda) "carport": ("PL1_EV + PL2_EV + PL3_EV", "wb_wh_total"), "og": ("PL1_EVog + PL2_EVog + PL3_EVog", "wbog_wh_total"), } def bloecke(zeilen): """Zusammenhaengende Strecken mit Leistung, jeweils mit der Zeile danach.""" block = [] for i, (zeit, kw) in enumerate(zeilen): if kw > RUHE_KW: block.append((zeit, kw)) elif block: block.append((zeit, 0.0)) # der Endpunkt, wie im Manager yield block block = [] if block: yield block # laeuft noch - ohne Endpunkt def zaehlerstand(cursor, spalte, zeit, richtung): """Zaehlerstand aus skoda direkt vor bzw. nach einem Zeitpunkt.""" if richtung < 0: cursor.execute("SELECT datetime, " + spalte + " FROM skoda WHERE datetime <= %s " "AND datetime >= %s AND " + spalte + " > 0 " "ORDER BY datetime DESC LIMIT 1", (zeit, zeit - LUECKE)) else: cursor.execute("SELECT datetime, " + spalte + " FROM skoda WHERE datetime >= %s " "AND datetime <= %s AND " + spalte + " > 0 " "ORDER BY datetime LIMIT 1", (zeit, zeit + LUECKE)) z = cursor.fetchone() return int(z[1]) if z else None def main(): if len(sys.argv) < 3: print(__doc__) sys.exit(1) von = datetime.datetime.fromisoformat(sys.argv[1]) bis = datetime.datetime.fromisoformat(sys.argv[2]) probe = "--probe" in sys.argv zugang = konfig.datenbank() with mc.connect(host=zugang["host"], port=zugang["port"], user=zugang["user"], password=zugang["password"], database=zugang["database"]) as v: with v.cursor() as c: neu = [] for name, (leistung, spalte) in WALLBOXEN.items(): c.execute("SELECT datetime, " + leistung + " FROM EnergyFlow " "WHERE datetime BETWEEN %s AND %s ORDER BY datetime", (von, bis)) zeilen = [(z[0], float(z[1] or 0.0)) for z in c.fetchall()] for block in bloecke(zeilen): beginn, ende = block[0][0], block[-1][0] c.execute("SELECT COUNT(*) FROM skoda_ladepunkte WHERE wallbox = %s " "AND quelle = 'manager' AND datetime BETWEEN %s AND %s", (name, beginn - datetime.timedelta(minutes=5), ende)) if c.fetchone()[0]: print(name, beginn, "bis", ende, "- vom Manager schon erfasst, uebersprungen") continue # Leistung ueber die Zeit. Ein EnergyFlow-Wert ist das Mittel # der fuenf Minuten, die er abschliesst. summe, vorher, verlauf = 0.0, None, [] for zeit, kw in block: dt = (zeit - vorher).total_seconds() if vorher else 300.0 summe += kw * dt / 3.6 if kw > 0 else 0.0 verlauf.append((zeit, kw, summe)) vorher = zeit anfang = zaehlerstand(c, spalte, beginn - datetime.timedelta(minutes=5), -1) schluss = zaehlerstand(c, spalte, ende, +1) if anfang is not None and schluss is not None and schluss > anfang and summe > 0: faktor = (schluss - anfang) / summe else: faktor = None for zeit, kw, bis_hier in verlauf: wh = round(anfang + bis_hier * faktor) if faktor else None neu.append((zeit, name, round(kw, 3), wh)) print(name, beginn, "bis", ende, "-", len(verlauf), "Punkte,", round(summe / 1000, 2), "kWh aufsummiert", ("gegen " + str(round((schluss - anfang) / 1000, 2)) + " kWh Zaehler" if faktor else "(ohne Zaehlerstaende, wh_total bleibt leer)")) if probe: print(len(neu), "Zeilen - Probe, nichts geschrieben.") return c.execute("DELETE FROM skoda_ladepunkte WHERE quelle = 'energyflow' " "AND datetime BETWEEN %s AND %s", (von, bis)) entfernt = c.rowcount c.executemany("INSERT INTO skoda_ladepunkte (datetime, wallbox, kw, wh_total, quelle) " "VALUES (%s,%s,%s,%s,'energyflow')", neu) v.commit() print(len(neu), "Zeilen nachgetragen,", entfernt, "fruehere Nachtraege ersetzt.") if __name__ == "__main__": main()