- 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>
629 lines
27 KiB
Python
629 lines
27 KiB
Python
import asyncio
|
|
from Wattpilot import Wattpilot
|
|
from dataclasses import dataclass
|
|
import sys
|
|
import logging
|
|
import gatherModbusData
|
|
import charger_goE
|
|
import webSocketServer
|
|
import time
|
|
from mysql.connector import connect, Error
|
|
import json
|
|
import gatherOpenDTUData
|
|
import gatherDTUBIData
|
|
import gatherSkodaData
|
|
import gatherWaterData
|
|
import gatherHeaterData
|
|
import gatherShellyEM3DataEG
|
|
import gatherShellyEM3DataUG
|
|
import datetime
|
|
from dateutil import tz
|
|
from suntime import Sun, SunTimeException
|
|
from typing import List
|
|
from dataclasses import dataclass
|
|
from dataclasses import field
|
|
from time import sleep
|
|
import dataclasses
|
|
import logging.config
|
|
import mqttClient
|
|
import konfig
|
|
|
|
logging.config.fileConfig('./logging.ini')
|
|
|
|
|
|
def is_summer_time(aware_dt):
|
|
assert aware_dt.tzinfo is not None
|
|
assert aware_dt.tzinfo.utcoffset(aware_dt) is not None
|
|
return bool(aware_dt.dst())
|
|
|
|
#Log_Format = "%(levelname)s %(asctime)s - %(message)s"
|
|
|
|
#logging.basicConfig(stream = sys.stdout,
|
|
# filemode = "w",
|
|
# format = Log_Format,
|
|
# level = logging.INFO)
|
|
|
|
#_LOGGER = logging.getLogger()
|
|
#Log_Format = "%(levelname)s %(module)s:%(lineno)d %(asctime)s - %(message)s"
|
|
#logging.basicConfig(stream=sys.stdout, format = Log_Format, level=logging.INFO)
|
|
_LOGGER = logging.getLogger()
|
|
#_LOGGER.setLevel(logging.INFO)
|
|
#logging.getLogger("Wattpilot").setLevel(logging.WARNING)
|
|
|
|
#handler = logging.StreamHandler(sys.stdout)
|
|
#handler.setLevel(logging.WARNING)
|
|
#formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
#handler.setFormatter(formatter)
|
|
#_LOGGER.addHandler(handler)
|
|
|
|
class EnhancedJSONEncoder(json.JSONEncoder):
|
|
def default(self, o):
|
|
if dataclasses.is_dataclass(o):
|
|
return dataclasses.asdict(o)
|
|
return super().default(o)
|
|
|
|
#"inverter1":"Symo Gen24 10.0",
|
|
#"inverter1nPV":2,
|
|
#"inverter2":"HM1500",
|
|
#"inverter2nPV":4,
|
|
#"inverter3":"HMT1800",
|
|
#"inverter3nPV":6,
|
|
#"inverter4":"HMs800w",
|
|
#"inverter4nPV":2,
|
|
|
|
@dataclass
|
|
class InvData:
|
|
p_PV:List[float] = field(default_factory=list)
|
|
p_AC:float = 0.0
|
|
temp:float = 0.0
|
|
error:int = 0
|
|
name:str = ""
|
|
|
|
|
|
rtData = {"P_Load":0.0,
|
|
"SOC":0.0,
|
|
"P_Akku":0.0,
|
|
"crgMaxPct":0.0,
|
|
"eff":0.0,
|
|
"P_PVn":[0.0]*34,
|
|
"P_PV":0.0,
|
|
"P_PV_est":0.0,
|
|
"invErr":0,
|
|
"autarky":0.0,
|
|
"P_Grid":0.0,
|
|
"P_WR":0.0,
|
|
"P_GridCons":0.0,
|
|
"P_GridFeed":0.0,
|
|
"ibatt":0.0,
|
|
"ubatt":0.0,
|
|
"tbatt":0.0,
|
|
"p_l1evu":0.0,
|
|
"p_l2evu":0.0,
|
|
"p_l3evu":0.0,
|
|
"p_l1og":0.0,
|
|
"p_l2og":0.0,
|
|
"p_l3og":0.0,
|
|
"p_l1eg":0.0,
|
|
"p_l2eg":0.0,
|
|
"p_l3eg":0.0,
|
|
"p_l1ug":0.0,
|
|
"p_l2ug":0.0,
|
|
"p_l3ug":0.0,
|
|
"og":0.0,
|
|
"eg":0.0,
|
|
"ug":0.0,
|
|
"i_l1evu":0.0,
|
|
"i_l2evu":0.0,
|
|
"i_l3evu":0.0,
|
|
"i_l1og":0.0,
|
|
"i_l2og":0.0,
|
|
"i_l3og":0.0,
|
|
"p_l1ev":0.0,
|
|
"p_l2ev":0.0,
|
|
"p_l3ev":0.0,
|
|
"evPower":0.0,
|
|
"evSOC":0.0,
|
|
"evLock":False,
|
|
"evFuel":0.0,
|
|
"evMode":"man",
|
|
"evRemChrgTime":0.0,
|
|
"evRange":0.0,
|
|
"evChgState":"",
|
|
"wbWh":0,
|
|
"wbogWh":0,
|
|
"evModeOG":"man",
|
|
"p_l1evOG":0.0,
|
|
"p_l2evOG":0.0,
|
|
"p_l3evOG":0.0,
|
|
"evPowerOG":0.0,
|
|
"evPlugOG":False,
|
|
"shellyUG":False,
|
|
"shellyOG":False,
|
|
"evPlug":False,
|
|
"t_buffT":0.0,
|
|
"t_buffM":0.0,
|
|
"t_buffB":0.0,
|
|
"t_heatVL":0.0,
|
|
"t_heatRL":0.0,
|
|
"t_gasVLu":0.0,
|
|
"t_gasVLo":0.0,
|
|
"t_gasRL":0.0,
|
|
"t_fbVL":0.0,
|
|
"t_fbRL":0.0,
|
|
"t_triac":0.0,
|
|
"pHeat":0.0,
|
|
"heatMode":"man",
|
|
"waterHeight":0,
|
|
"waterTemp": 0.0,
|
|
"inverters":[InvData(),InvData(),InvData(),InvData(),InvData(),InvData(),InvData(),InvData()],
|
|
"P_AC": 0.0
|
|
}
|
|
avgData = dict(rtData)
|
|
|
|
lock = asyncio.Lock()
|
|
|
|
def wp_handle_events(event, *args):
|
|
_LOGGER.debug(f"wp_handle_events(event={event},{args})")
|
|
_LOGGER.debug(f"wp_handle_events(): MQTT client not yet initialized - status publishing skipped.")
|
|
if event['type'] == 'fullStatus':
|
|
_LOGGER.debug(f"wp_handle_events()")
|
|
return
|
|
|
|
def isfloat(num):
|
|
try:
|
|
float(num)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
async def repeat(interval):
|
|
"""Run func every interval seconds.
|
|
If func has not finished before *interval*, will run again
|
|
immediately when the previous iteration finished.
|
|
*args and **kwargs are passed as the arguments to func.
|
|
"""
|
|
# Zugangsdaten stehen in config.ini und nicht mehr hier, siehe konfig.py.
|
|
_zugang = konfig.datenbank()
|
|
mysqlHost = _zugang["host"]
|
|
mysqlPort = _zugang["port"]
|
|
mysqlUser = _zugang["user"]
|
|
mysqlPW = _zugang["password"]
|
|
mysqlDB = _zugang["database"]
|
|
gatherSkodaData.setDbPasswort(mysqlPW)
|
|
|
|
avgCounter = 0
|
|
invErrAcc = 0
|
|
next_dbEntry = round(time.time()) + 300
|
|
_LOGGER.info("Manager started!")
|
|
|
|
lat = 47.5779944
|
|
lon = 10.2623373
|
|
sun = Sun(lat, lon)
|
|
lastDay = datetime.datetime.now()
|
|
query = "SELECT date FROM daylight ORDER BY date DESC LIMIT 1;"
|
|
lastSunriseDate = 0
|
|
try:
|
|
with connect(
|
|
host=mysqlHost,
|
|
port=mysqlPort,
|
|
user=mysqlUser,
|
|
password=mysqlPW,
|
|
database=mysqlDB,
|
|
) as connection:
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query)
|
|
myresult = cursor.fetchone()
|
|
connection.commit()
|
|
if myresult:
|
|
lastSunriseDate = myresult[0]
|
|
|
|
if(lastSunriseDate):
|
|
newDay = lastSunriseDate
|
|
while newDay < datetime.datetime.now().date() + datetime.timedelta(days=2):
|
|
sunrise = sun.get_sunrise_time(newDay,tz.gettz('Europe/Berlin')) #tz.gettz('Europe/Kaliningrad') tz.gettz('Europe/Berlin')
|
|
sunset = sun.get_sunset_time(newDay,tz.gettz('Europe/Berlin'))
|
|
if(is_summer_time(sunrise) == False):
|
|
sunrise = sunrise + datetime.timedelta(hours=1)
|
|
#_LOGGER.info("Wintertime!!")
|
|
if(is_summer_time(sunset) == False):
|
|
sunset = sunset + datetime.timedelta(hours=1)
|
|
#_LOGGER.info(str(sunrise.strftime('%H:%M:%S')))
|
|
query = "REPLACE INTO daylight (date, sunrise, sunset) VALUES ('"+newDay.isoformat()+"', '"+str(sunrise.strftime('%H:%M:%S'))+"', '"+str(sunset.strftime('%H:%M:%S'))+"');"
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query)
|
|
connection.commit()
|
|
newDay = newDay + datetime.timedelta(days=1)
|
|
except Error as e:
|
|
print(e)
|
|
sunrise = sun.get_sunrise_time(datetime.datetime.now(),tz.gettz('Europe/Berlin')) #tz.gettz('Europe/Kaliningrad') tz.gettz('Europe/Berlin')
|
|
sunset = sun.get_sunset_time(datetime.datetime.now(),tz.gettz('Europe/Berlin'))
|
|
if(is_summer_time(sunrise) == False):
|
|
sunrise = sunrise + datetime.timedelta(hours=1)
|
|
#_LOGGER.info("Wintertime!!")
|
|
if(is_summer_time(sunset) == False):
|
|
sunset = sunset + datetime.timedelta(hours=1)
|
|
|
|
wp = Wattpilot.Wattpilot(konfig.wert("wattpilot", "host"),
|
|
konfig.wert("wattpilot", "password"))
|
|
wp._auto_reconnect = 1
|
|
wp._reconnect_interval = 60
|
|
wp.connect()
|
|
wp.add_event_handler("ws_close", wp_handle_events)
|
|
wp.add_event_handler("ws_open", wp_handle_events)
|
|
wp.add_event_handler("wp_fullStatus_finished", wp_handle_events)
|
|
i=0
|
|
estProduction = 0.0
|
|
|
|
|
|
while (wp.allPropsInitialized == 0 and i < 10):
|
|
sleep(1);
|
|
i = i + 1;
|
|
|
|
water=await gatherWaterData.getWaterData()
|
|
rtData["waterHeight"] = water.waterHeight
|
|
rtData["waterTemp"] = water.temp
|
|
await gatherModbusData.connect()
|
|
mqttClient.startMqttClient()
|
|
rtData["inverters"][0].p_PV.append(0)
|
|
rtData["inverters"][0].p_PV.append(0)
|
|
while True:
|
|
|
|
inv,dtubi,dtu,heat,chrg,pEG,pUG,skoda,slp = await asyncio.gather(
|
|
gatherModbusData.get_runtime_data(estProduction, sunset),
|
|
gatherDTUBIData.gatherData(),
|
|
gatherOpenDTUData.gatherData(),
|
|
gatherHeaterData.gatherData(),
|
|
charger_goE.gatherNeededStatus(),
|
|
gatherShellyEM3DataEG.gatherData(),
|
|
gatherShellyEM3DataUG.gatherData(),
|
|
gatherSkodaData.gatherData(rtData["evPower"], rtData["evPlug"],
|
|
rtData["evPowerOG"], rtData["evPlugOG"],
|
|
rtData["P_PV"]/1000.0, rtData["P_Grid"]/1000.0,
|
|
rtData["wbWh"], rtData["wbogWh"]),
|
|
asyncio.sleep(interval),
|
|
)
|
|
rtData["evSOC"] = skoda.soc
|
|
rtData["evRange"] = skoda.range_km
|
|
rtData["evRemChrgTime"] = skoda.chgRemMin
|
|
rtData["evChgState"] = skoda.chgState
|
|
rtData["evFuel"] = skoda.fuelPct
|
|
if skoda.locked is not None:
|
|
rtData["evLock"] = skoda.locked
|
|
|
|
pvIt = 0
|
|
if inv is None:
|
|
_LOGGER.error("no inverter data available, skipping PV calculation fot Fronius!")
|
|
else:
|
|
for ppv in inv.p_PV:
|
|
if ppv is not None:
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 2
|
|
for ppv in dtubi.inverter[0].p_PV: #Veranda UG2
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 4
|
|
for ppv in dtu.inverter[0].p_PV: #Veranda UG/OG1
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 8
|
|
for ppv in dtu.inverter[3].p_PV: #was inverter 3 Veranda OG2-4
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 14
|
|
for ppv in dtu.inverter[2].p_PV: #carport 1 (HMS2250-6T)
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 20
|
|
for ppv in dtu.inverter[1].p_PV: #carport 2 (HMS2250-6T)
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 26
|
|
for ppv in dtu.inverter[4].p_PV: #carport 3 (HMS1800-4T)
|
|
if ppv < 1:
|
|
ppv = 0
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
pvIt = 30
|
|
for ppv in dtu.inverter[5].p_PV: #carport 4 (HMS1600-4T) only take three plates
|
|
if ppv < 1:
|
|
ppv = 0
|
|
if pvIt < 34:
|
|
rtData["P_PVn"][pvIt] = round(ppv,2)
|
|
pvIt =pvIt+1
|
|
invNum = 0
|
|
if inv is None:
|
|
pass
|
|
else:
|
|
rtData["inverters"][invNum].p_PV[0] = (inv.ppv)
|
|
rtData["inverters"][invNum].p_PV[1] = (inv.ppv2)
|
|
rtData["inverters"][invNum].p_AC = inv.p_AC
|
|
rtData["inverters"][invNum].temp = inv.temp
|
|
rtData["inverters"][invNum].name = "Gen24 "+str(inv.temp)
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtubi.inverter[0] #Veranda UG2
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtu.inverter[0] #Veranda UG/OG1
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtu.inverter[3] #Veranda OG2-4
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtu.inverter[2] #carport 1-6
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtu.inverter[1] #carport 7-12
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtu.inverter[4] #carport 13-16
|
|
invNum = invNum+1
|
|
rtData["inverters"][invNum] = dtu.inverter[5] #carport 17-19
|
|
invNum = invNum+1
|
|
|
|
# Wer hat in diesem Durchlauf keine frischen Daten geliefert? Ein Bit je
|
|
# Wechselrichter in der Reihenfolge oben, damit die Zeile spaeter selbst
|
|
# sagt, ob pvP eine Messung ist. P_PV_est haelt fest, wieviel davon aus
|
|
# den gesunden Nachbarn hochgerechnet wurde - pvP minus pvP_est ist
|
|
# also weiterhin die reine Messung.
|
|
if inv is None:
|
|
rtData["inverters"][0].error += 1
|
|
else:
|
|
rtData["inverters"][0].error = 0
|
|
rtData["invErr"] = 0
|
|
estSum = 0.0
|
|
for wrNum, wr in enumerate(rtData["inverters"]):
|
|
if wr.error:
|
|
rtData["invErr"] |= (1 << wrNum)
|
|
estSum += getattr(wr, "p_PV_est", 0.0)
|
|
rtData["P_PV_est"] = round(estSum,2)
|
|
|
|
try:
|
|
rtData["P_AC"] = 0#rtData["inverters"][0].p_AC#inv.p_AC
|
|
for inverter in rtData["inverters"]:
|
|
rtData["P_AC"] = rtData["P_AC"] + inverter.p_AC
|
|
rtData["P_PV"] = round(sum(rtData["P_PVn"]),2)
|
|
rtData["P_Grid"] = inv.pgrid
|
|
if inv.pgrid > 0:
|
|
rtData["P_GridCons"] = inv.pgrid
|
|
rtData["P_GridFeed"] = 0
|
|
else:
|
|
rtData["P_GridCons"] = 0
|
|
rtData["P_GridFeed"] = -inv.pgrid
|
|
rtData["P_WR"] = inv.p_wr
|
|
rtData["ibatt"] = inv.ibatt
|
|
rtData["ubatt"] = inv.ubatt
|
|
rtData["tbatt"] = 0
|
|
rtData["crgMaxPct"] = inv.crgMaxPct
|
|
#rtData["pwrMaxPct"] = inv.pwrMaxPct
|
|
if((rtData["P_PV"]+rtData["P_Akku"]) != 0):
|
|
rtData["eff"] = ((100.0 / (rtData["P_PV"]+rtData["P_Akku"])) * rtData["P_AC"])
|
|
else:
|
|
rtData["eff"] = 0
|
|
rtData["p_l1evu"] = inv.p_l1evu
|
|
rtData["p_l2evu"] = inv.p_l2evu
|
|
rtData["p_l3evu"] = inv.p_l3evu
|
|
rtData["i_l1evu"] = inv.i_l1evu
|
|
rtData["i_l2evu"] = inv.i_l2evu
|
|
rtData["i_l3evu"] = inv.i_l3evu
|
|
rtData["p_l1og"] = inv.p_l1og
|
|
rtData["p_l2og"] = inv.p_l2og
|
|
rtData["p_l3og"] = inv.p_l3og
|
|
rtData["i_l1og"] = inv.i_l1og
|
|
rtData["i_l2og"] = inv.i_l2og
|
|
rtData["i_l3og"] = inv.i_l3og
|
|
rtData["og"] = inv.p_l1og + inv.p_l2og + inv.p_l3og - (chrg.p_l1ev+chrg.p_l2ev+chrg.p_l3ev)*1000
|
|
rtData["p_l1eg"] = pEG.P_L1
|
|
rtData["p_l2eg"] = pEG.P_L2
|
|
rtData["p_l3eg"] = pEG.P_L3
|
|
rtData["eg"] = pEG.P_L1 + pEG.P_L2 + pEG.P_L3 + (wp.power1+wp.power2+wp.power3)*1000
|
|
rtData["p_l1ug"] = pUG.P_L1
|
|
rtData["p_l2ug"] = pUG.P_L2
|
|
rtData["p_l3ug"] = pUG.P_L3
|
|
rtData["ug"] = pUG.P_L1 + pUG.P_L2 + pUG.P_L3
|
|
|
|
rtData["P_Akku"] = inv.pbat
|
|
|
|
|
|
rtData["SOC"] = inv.soc
|
|
rtData["evMode"] = wp.mode
|
|
rtData["p_l1ev"] = wp.power1
|
|
rtData["p_l2ev"] = wp.power2
|
|
rtData["p_l3ev"] = wp.power3
|
|
rtData["evPower"] = wp.power
|
|
# carConnected ist ein Text ("no car", "charging", "ready",
|
|
# "complete"), kein Wahrheitswert - und bool("no car") ist True.
|
|
# So stand der Stecker der Wallbox im Carport dauerhaft auf
|
|
# gesteckt: in der Tabelle skoda, im Abruftakt von
|
|
# gatherSkodaData.py und als Steckersymbol im Realtime-SVG.
|
|
# "ready" heisst: Auto dran, wartet noch - also gesteckt.
|
|
# Als bool passt es zu evPlugOG und kommt per MQTT als 1/0 an.
|
|
rtData["evPlug"] = wp.carConnected not in (None, "no car")
|
|
# Gesamtzaehler beider Wallboxen in Wh. Die Differenz zweier
|
|
# Staende trifft eine Ladung auf die Wattstunde genau - genauer
|
|
# als jede Summe ueber die Fuenf-Minuten-Leistungswerte, deren
|
|
# Fenstergrenzen nie auf Anfang und Ende der Ladung fallen.
|
|
rtData["wbWh"] = int(wp.energyCounterTotal or 0)
|
|
|
|
rtData["evModeOG"] = chrg.mode
|
|
rtData["p_l1evOG"] = chrg.p_l1ev
|
|
rtData["p_l2evOG"] = chrg.p_l2ev
|
|
rtData["p_l3evOG"] = chrg.p_l3ev
|
|
rtData["evPowerOG"] = chrg.p_l1ev + chrg.p_l2ev + chrg.p_l3ev
|
|
rtData["evPlugOG"] = chrg.connected
|
|
rtData["wbogWh"] = chrg.eto
|
|
|
|
rtData["heatMode"] = heat.mode
|
|
rtData["t_buffT"] = heat.t_buffT
|
|
rtData["t_buffM"] = heat.t_buffM
|
|
rtData["t_buffB"] = heat.t_buffB
|
|
rtData["t_heatVL"] = heat.t_heatVL
|
|
rtData["t_heatRL"] = heat.t_heatRL
|
|
rtData["t_gasVLu"] = heat.t_gasVLu
|
|
rtData["t_gasVLo"] = heat.t_gasVLo
|
|
rtData["t_gasRL"] = heat.t_gasRL
|
|
rtData["t_fbVL"] = heat.t_fbVL
|
|
rtData["t_fbRL"] = heat.t_fbRL
|
|
rtData["t_triac"] = heat.t_triac
|
|
rtData["pHeat"] = heat.p_heat
|
|
rtData["P_Load"] = -(rtData["P_AC"] + rtData["P_Grid"])# + rtData["P_Akku"])
|
|
if(-rtData["P_Load"] < -rtData["og"] + rtData["eg"] + rtData["ug"] + rtData["pHeat"] ): #if Pload is less than combined consumption, there is a measurement error-- assume Pload still a little more than consumption to leave some common consumption
|
|
rtData["P_Load"] = -(-rtData["og"] + rtData["eg"] + rtData["ug"] + rtData["pHeat"])-50
|
|
|
|
if rtData["P_Grid"] > 0 and rtData["P_Load"] < 0:
|
|
rtData["autarky"] = 100 + 100*(rtData["P_Grid"]/rtData["P_Load"]) #load is negative
|
|
if rtData["autarky"] > 100:
|
|
rtData["autarky"] = 100
|
|
if rtData["autarky"] < 0:
|
|
rtData["autarky"] = 0
|
|
else:
|
|
rtData["autarky"] = 100
|
|
|
|
try:
|
|
for key in avgData:
|
|
if isinstance(avgData[key],float):
|
|
avgData[key] += rtData[key]
|
|
invErrAcc |= rtData["invErr"] # Bits sammeln, nicht mitteln
|
|
avgCounter = avgCounter+1
|
|
except:
|
|
_LOGGER.error("data average failed!")
|
|
except Exception as e:
|
|
_LOGGER.error(e)
|
|
_LOGGER.error("rtdata fill failed!")
|
|
if round(time.time()) >= next_dbEntry:
|
|
next_dbEntry = next_dbEntry + 300
|
|
water=await gatherWaterData.getWaterData()
|
|
rtData["waterHeight"] = water.waterHeight
|
|
rtData["waterTemp"] = water.temp
|
|
|
|
if avgCounter:
|
|
for key in avgData:
|
|
if isinstance(avgData[key],float):
|
|
avgData[key] /= avgCounter
|
|
#_LOGGER.info("DB-OUTPUT!!")
|
|
if(-avgData["P_Load"] < (-avgData["p_l1og"] - avgData["p_l2og"] - avgData["p_l3og"] + avgData["p_l1ev"] + avgData["p_l2ev"] + avgData["p_l3ev"] + avgData["p_l1evOG"] + avgData["p_l2evOG"] + avgData["p_l3evOG"] + avgData["pHeat"])): #null heater consumption, if total consumption is too small so it would result in negative UG consumption
|
|
avgData["pHeat"] = -avgData["P_Load"] - (-avgData["p_l1og"] - avgData["p_l2og"] - avgData["p_l3og"]) - 300; #reduce heater consumption in case PPV power is not gathered correctly
|
|
if(-avgData["P_Load"] < (-avgData["p_l1og"] - avgData["p_l2og"] - avgData["p_l3og"] + avgData["p_l1ev"] + avgData["p_l2ev"] + avgData["p_l3ev"] + avgData["p_l1evOG"] + avgData["p_l2evOG"] + avgData["p_l3evOG"] + avgData["pHeat"])): #null car consumption, of total consumption is too small so it would result in negative UG consumption
|
|
avgData["p_l1ev"] = 0
|
|
avgData["p_l2ev"] = 0
|
|
avgData["p_l3ev"] = 0
|
|
avgData["p_l1evOG"] = 0
|
|
avgData["p_l2evOG"] = 0
|
|
avgData["p_l3evOG"] = 0
|
|
if(avgData["pHeat"] < 0): #when we reduced too much from heater, there must have been more PV power available (inverters didn't report production)
|
|
avgData["P_PV"] = avgData["P_PV"] - avgData["pHeat"]
|
|
avgData["pHeat"] = 0
|
|
if avgData["P_Load"] > 0 or -avgData["P_Load"] < -avgData["og"] + avgData["p_l2ev"] + avgData["p_l3ev"] + avgData["p_l1evOG"] + avgData["p_l2evOG"] + avgData["p_l3evOG"] + avgData["pHeat"]: #if load is not negative something is wrong
|
|
avgData["P_Load"] = -(avgData["P_PV"] + avgData["P_Akku"]) + 20 #try to recalculate real consumption from corrected PV production
|
|
if avgData["P_Load"] > 0 or -avgData["P_Load"] < -avgData["og"] + avgData["p_l2ev"] + avgData["p_l3ev"] + avgData["p_l1evOG"] + avgData["p_l2evOG"] + avgData["p_l3evOG"] + avgData["pHeat"]: #if the correction still is not matching assume zero consumption for UG for smallest error
|
|
avgData["P_Load"] = -(-avgData["og"] + avgData["p_l2ev"] + avgData["p_l3ev"] + avgData["p_l1evOG"] + avgData["p_l2evOG"] + avgData["p_l3evOG"] + avgData["pHeat"])
|
|
|
|
try:
|
|
with connect(
|
|
host=mysqlHost,
|
|
port=mysqlPort,
|
|
user=mysqlUser,
|
|
password=mysqlPW,
|
|
database=mysqlDB,
|
|
) as connection:
|
|
query = ("INSERT INTO EnergyFlow (datetime, "
|
|
"totalConsumption, soc, battP, "
|
|
"pvP, autonomy, gridP, "
|
|
"gridPcons, gridPfeed, "
|
|
"battI, battU, battTemp, "
|
|
"IL1_EVU, IL2_EVU, IL3_EVU, "
|
|
"IL1_OG, IL2_OG, IL3_OG, "
|
|
"PL1_EVU, PL2_EVU, PL3_EVU, "
|
|
"PL1_OG, PL2_OG, PL3_OG, "
|
|
"PL1_EG, PL2_EG, PL3_EG, "
|
|
"PL1_UG, PL2_UG, PL3_UG, "
|
|
"PL1_EV, PL2_EV, PL3_EV, "
|
|
"PL1_EVog, PL2_EVog, PL3_EVog, "
|
|
"HeizungOG, HeizungEG, heaterPwr, "
|
|
"invErr, pvP_est) VALUES ("
|
|
"NOW(),"
|
|
+str(avgData["P_Load"])+","+str(avgData["SOC"])+","+str(avgData["P_Akku"])+","
|
|
+str(avgData["P_PV"])+","+str(avgData["autarky"])+","+str(avgData["P_Grid"])+","
|
|
+str(avgData["P_GridCons"])+","+str(avgData["P_GridFeed"])+","
|
|
+str(avgData["ibatt"])+","+str(avgData["ubatt"])+","+str(avgData["tbatt"])+","
|
|
+str(avgData["i_l1evu"])+","+str(avgData["i_l2evu"])+","+str(avgData["i_l3evu"])+","
|
|
+str(avgData["i_l1og"])+","+str(avgData["i_l2og"])+","+str(avgData["i_l3og"])+","
|
|
+str(avgData["p_l1evu"])+","+str(avgData["p_l2evu"])+","+str(avgData["p_l3evu"])+","
|
|
+str(avgData["p_l1og"])+","+str(avgData["p_l2og"])+","+str(avgData["p_l3og"])+","
|
|
+str(avgData["p_l1eg"])+","+str(avgData["p_l2eg"])+","+str(avgData["p_l3eg"])+","
|
|
+str(avgData["p_l1ug"])+","+str(avgData["p_l2ug"])+","+str(avgData["p_l3ug"])+","
|
|
+str(avgData["p_l1ev"])+","+str(avgData["p_l2ev"])+","+str(avgData["p_l3ev"])+","
|
|
+str(avgData["p_l1evOG"])+","+str(avgData["p_l2evOG"])+","+str(avgData["p_l3evOG"])+","
|
|
+str(0)+","+str(0)+","+str(avgData["pHeat"])+","
|
|
+str(invErrAcc)+","+str(avgData["P_PV_est"])+");")
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query)
|
|
connection.commit()
|
|
newDay = datetime.datetime.now() + datetime.timedelta(hours=24-6) #run this at 6 o'clock
|
|
if(newDay.strftime('%d') != lastDay.strftime('%d')):
|
|
lastDay = newDay
|
|
sunrise = sun.get_sunrise_time(newDay,tz.gettz('Europe/Berlin')) #tz.gettz('Europe/Kaliningrad') tz.gettz('Europe/Berlin')
|
|
sunset = sun.get_sunset_time(newDay,tz.gettz('Europe/Berlin'))
|
|
if(is_summer_time(sunrise) == False):
|
|
sunrise = sunrise + datetime.timedelta(hours=1)
|
|
#_LOGGER.info("Wintertime!!")
|
|
if(is_summer_time(sunset) == False):
|
|
sunset = sunset + datetime.timedelta(hours=1)
|
|
#_LOGGER.info(str(sunrise.strftime('%H:%M:%S')))
|
|
query = "REPLACE INTO daylight (date, sunrise, sunset) VALUES (CURDATE()+1, '"+str(sunrise.strftime('%H:%M:%S'))+"', '"+str(sunset.strftime('%H:%M:%S'))+"');"
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query)
|
|
connection.commit()
|
|
query = "SELECT SUM(power)/2 AS estProd FROM simPower WHERE DATE(period_end) = DATE(NOW());"
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query)
|
|
myresult = cursor.fetchone()
|
|
connection.commit()
|
|
estProduction = myresult[0]
|
|
except Error as e:
|
|
_LOGGER.error(e)
|
|
|
|
for key in avgData:
|
|
if isinstance(avgData[key],float):
|
|
avgData[key] = 0.0
|
|
avgCounter = 0
|
|
invErrAcc = 0
|
|
#if(rtData["SOC"] > 70.0):
|
|
#await charger_goE.setPVparameters((rtData["P_Grid"]+rtData["P_Akku"])+(4000*(100-rtData["SOC"])/100),rtData["P_PV"]+rtData["P_PV2"]+rtData["P_PV3"],0)
|
|
#else:
|
|
#await charger_goE.setPVparameters(500,0,0)
|
|
#if(chrg.mode == "eco" and avgCounter == 5):
|
|
# await charger_goE.setChargePower(rtData["P_PV"],chrg.Phase_set_ev,chrg.Iset_ev,chrg.AllowCharging_ev)
|
|
#print(json.dumps(rtData))
|
|
publishData = json.dumps(rtData, cls=EnhancedJSONEncoder)
|
|
webSocketServer.message_all(publishData)
|
|
mqttClient.publish(rtData)
|
|
if(rtData["SOC"] > 70.0):
|
|
chrgCons = (rtData["P_Grid"]+rtData["P_Akku"])+(4000*(100-rtData["SOC"])/100)
|
|
pv_ges = rtData["P_PV"]
|
|
await charger_goE.setPVparameters(chrgCons,pv_ges,0)
|
|
#_LOGGER.error(f"goE Parameters Sent PV")
|
|
else:
|
|
await charger_goE.setPVparameters(500,0,0)
|
|
#_LOGGER.error(f"goE Parameters Sent")
|
|
|
|
async def main():
|
|
|
|
await webSocketServer.serveWebSocket()
|
|
await asyncio.gather(
|
|
repeat(3)
|
|
)
|
|
await webSocketServer.closeWebSocket()
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
loop.run_until_complete(main())
|