Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
622 lines
26 KiB
Python
622 lines
26 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
|
|
rtData["evPlug"] = wp.carConnected
|
|
# 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())
|