Skoda: mehrere Schluessel, Wallbox-Ladeverlauf, Stecker als 1/0
- gatherSkodaData.py liest bis zu drei API-Schluessel und nutzt sie reihum;
das Kontingent gilt gemessen fuers ganze Konto, daher feste Aufteilung
14 Abruf / 4 Befehle / 2 Reserve
- nach einem Befehl aus der Weboberflaeche wird einmal ausser der Reihe
nachgesehen
- Wallbox-Verlauf waehrend einer Ladung nach skoda_ladepunkte, dazu
skoda_ladepunkte_nachtragen.py fuer Ladungen aus EnergyFlow
- evPlug ist jetzt ein Wahrheitswert: bool("no car") war immer True
- skoda.conf.example beschreibt das gemeinsame Format
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user