import asyncio import json import aiohttp import sys import logging import sunspec2.modbus.client as client import datetime from typing import List from dataclasses import dataclass from dataclasses import field from operator import attrgetter from time import sleep import traceback _LOGGER = logging.getLogger(__name__) batStat = { 1: "OFF", 2: "EMPTY", 3: "DISCHARGING", 4: "CHARGING", 5: "FULL", 6: "HOLDING", 7: "TESTING", } wrStat = { 1: "OFF", 2: "SLEEPING", 3: "STARTING", 4: "MPPT", 5: "THROTTLED", 6: "SHUTTING_DOWN", 7: "FAULT", } @dataclass class ModbusRegisters: ppv0_reg = "" ppv1_reg = "" pbat_crg_reg = "" pbat_discrg_reg = "" ubat_reg = "" ibat_crg_reg = "" ibat_discrg_reg = "" pACinv_reg = "" soc_reg = "" battStat_reg = "" wrStat_reg = "" pACevu_reg = "" pL1ACevu_reg = "" pL2ACevu_reg = "" pL3ACevu_reg = "" iL1evu_reg = "" iL2evu_reg = "" iL3evu_reg = "" pACog_reg = "" pL1ACog_reg = "" pL2ACog_reg = "" pL3ACog_reg = "" iL1og_reg = "" iL2og_reg = "" iL3og_reg = "" limitChrgRate = "" setChargeLimits = "" setPowerLimitPercent = "" setPowerLimitEN = "" @dataclass class InverterData: p_PV:List[float] = field(default_factory=lambda: [0.0,0.0]) error:int = 0 load: float = 0.0 soc: float = 0.0 pbat: float = 0.0 ppv: float = 0.0 ppv2: float = 0.0 aut: float = 0.0 pgrid: float = 0.0 ibatt: float = 0.0 ubatt: float = 0.0 temp: float = 0.0 p_l1evu: float = 0.0 p_l2evu: float = 0.0 p_l3evu: float = 0.0 p_l1og: float = 0.0 p_l2og: float = 0.0 p_l3og: float = 0.0 i_l1evu: float = 0.0 i_l2evu: float = 0.0 i_l3evu: float = 0.0 i_l1og: float = 0.0 i_l2og: float = 0.0 i_l3og : float = 0.0 p_wr : float = 0.0 crgMaxPct : float = 0.0 wr_status : str = "" batt_status : str = "" pwrMaxPct : float = 0.0 p_AC : float = 0.0 @dataclass class estimation: estProd = 0.1 increaseTmr = 0 try: inv = client.SunSpecModbusClientDeviceTCP(1, "192.168.179.155", 1502) meter0 = client.SunSpecModbusClientDeviceTCP(200, "192.168.179.155", 1502) meter1 = client.SunSpecModbusClientDeviceTCP(201, "192.168.179.155", 1502) regs = ModbusRegisters() estimations = estimation() except client.SunSpecModbusClientError as e: _LOGGER.error('Modbus TCP Error: %s' % e) _LOGGER.erorr('trace: %s' % traceback.print_exc()) sys.exit(1) async def connect(): if inv is not None and meter0 is not None and meter1 is not None: print('Scanning: ') # print( '\nTimestamp: %s' % (time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))) # read all models in the device inv.scan() meter0.scan() meter1.scan() #_LOGGER.debug(inv.get_text()) if(inv.common[0].Md.value == "Symo GEN24 10.0"): regs.ppv0_reg = "inv.mppt[0].module[0].DCW" regs.ppv1_reg = "inv.mppt[0].module[1].DCW" regs.pbat_crg_reg = "inv.mppt[0].module[2].DCW" regs.pbat_discrg_reg = "inv.mppt[0].module[3].DCW" regs.ubat_reg = "inv.mppt[0].module[3].DCV" regs.ibat_crg_reg = "inv.mppt[0].module[2].DCA" regs.ibat_discrg_reg = "inv.mppt[0].module[3].DCA" regs.pACinv_reg = "inv.inverter_three_phase[0].W" regs.tmpCab_reg = "inv.inverter_three_phase[0].TmpCab" regs.soc_reg = "inv.storage_basic[0].ChaState" regs.battStat_reg = "inv.storage_basic[0].ChaSt" regs.wrStat_reg = "inv.inverter_three_phase[0].St" regs.limitChrgRate = "inv.storage_basic[0].InWRte" regs.setChargeLimits = "inv.storage_basic[0].StorCtl_Mod" regs.setPowerLimitPercent = "inv.controls[0].WMaxLimPct" regs.setPowerLimitEN = "inv.controls[0].WMaxLim_Ena" regs.pACevu_reg = "meter0.ac_meter_abcn[0].W" regs.pL1ACevu_reg = "meter0.ac_meter_abcn[0].WphA" regs.pL2ACevu_reg = "meter0.ac_meter_abcn[0].WphB" regs.pL3ACevu_reg = "meter0.ac_meter_abcn[0].WphC" regs.iL1evu_reg = "meter0.ac_meter_abcn[0].AphA" regs.iL2evu_reg = "meter0.ac_meter_abcn[0].AphB" regs.iL3evu_reg = "meter0.ac_meter_abcn[0].AphC" regs.pACog_reg = "meter1.ac_meter_abcn[0].W" regs.pL1ACog_reg = "meter1.ac_meter_abcn[0].WphA" regs.pL2ACog_reg = "meter1.ac_meter_abcn[0].WphB" regs.pL3ACog_reg = "meter1.ac_meter_abcn[0].WphC" regs.iL1og_reg = "meter1.ac_meter_abcn[0].AphA" regs.iL2og_reg = "meter1.ac_meter_abcn[0].AphB" regs.iL3og_reg = "meter1.ac_meter_abcn[0].AphC" else: _LOGGER.error('undefined Inverter, please define inverter registers!') sys.exit(1) else: _LOGGER.error('One of the expected modbus devices was unreachable') sys.exit(1) #while True: async def get_runtime_data(estProduction, sunset) -> InverterData: ret = InverterData() try: inv.mppt[0].read() inv.inverter_three_phase[0].read() inv.storage_basic[0].read() meter0.ac_meter_abcn[0].read() meter1.ac_meter_abcn[0].read() #print(inv.controls[0].__dir__()) ret.p_PV[0] = eval(regs.ppv0_reg).cvalue ret.p_PV[1] = eval(regs.ppv1_reg).cvalue ret.error = 0 ret.load = eval(regs.pACevu_reg).cvalue + eval(regs.pbat_discrg_reg).cvalue + eval(regs.ppv0_reg).cvalue + eval(regs.ppv1_reg).cvalue ret.soc = eval(regs.soc_reg).cvalue ret.pbat = eval(regs.pbat_discrg_reg).cvalue if(ret.pbat == 0): ret.pbat = -eval(regs.pbat_crg_reg).cvalue ret.crgMaxPct = (eval(regs.limitChrgRate).cvalue)*2.04 #references max charge of batt. (20,48kw) instead of INV (10kw) ret.pwrMaxPct = eval(regs.setPowerLimitPercent).cvalue #references max charge of batt. (20,48kw) instead of INV (10kw) ret.ppv = ret.p_PV[0] ret.ppv2 = ret.p_PV[1] ret.pgrid = eval(regs.pACevu_reg).cvalue if(ret.pgrid < 0): ret.aut = 100 else: ret.aut = 100/ret.load * ret.pgrid if(ret.aut < 0): ret.aut=0 elif(ret.aut > 100): ret.aut = 100 ret.ibatt = eval(regs.ibat_discrg_reg).cvalue if(ret.ibatt == 0): ret.ibatt = -eval(regs.ibat_crg_reg).cvalue ret.ubatt = eval(regs.ubat_reg).cvalue ret.p_l1evu = eval(regs.pL1ACevu_reg).cvalue ret.p_l2evu = eval(regs.pL2ACevu_reg).cvalue ret.p_l3evu = eval(regs.pL3ACevu_reg).cvalue ret.p_l1og = eval(regs.pL1ACog_reg).cvalue ret.p_l2og = eval(regs.pL2ACog_reg).cvalue ret.p_l3og = eval(regs.pL3ACog_reg).cvalue ret.i_l1evu = eval(regs.iL1evu_reg).cvalue ret.i_l2evu = eval(regs.iL2evu_reg).cvalue ret.i_l3evu = eval(regs.iL3evu_reg).cvalue ret.i_l1og = eval(regs.iL1og_reg).cvalue ret.i_l2og = eval(regs.iL2og_reg).cvalue ret.i_l3og = eval(regs.iL3og_reg).cvalue ret.p_AC = eval(regs.pACinv_reg).cvalue ret.p_wr = ret.ppv + ret.ppv2 + ret.pbat - ret.p_AC ret.temp = 0#no need to read reg, reads always null ... eval(regs.tmpCab_reg).cvalue ret.batt_status = batStat[eval(regs.battStat_reg).cvalue] ret.wr_status = wrStat[eval(regs.wrStat_reg).cvalue] except Exception as error: _LOGGER.warning(f"No modbus data received from Inverter: %s",error) #_LOGGER.erorr('trace: %s' % traceback.print_exc()) try: inv.connect() #reconnect the modbus client meter0.connect() #reconnect the modbus client meter1.connect() #reconnect the modbus client _LOGGER.warning(f"Tried to reconnect") except: _LOGGER.warning(f"reconnect failed...") # if(ret.soc > 90 and (ret.i_l1evu > 22 or ret.i_l2evu > 22 or ret.i_l3evu > 22)): # eval(regs.setPowerLimitPercent).value = ret.pwrMaxPct*100 - 500; #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV # eval(regs.setPowerLimitEN).value = 1 #set charge rate: bit1 set discharge rate: bit 2 # eval(regs.limitChrgRate).write() # eval(regs.setChargeLimits).write() # elif (ret.i_l1evu < 18 and ret.i_l2evu < 18 and ret.i_l3evu < 18 and ret.pwrMaxPct < 100): # eval(regs.setPowerLimitPercent).value = ret.pwrMaxPct*100 + 500; #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV # eval(regs.setPowerLimitEN).value = 1 #set charge rate: bit1 set discharge rate: bit 2 # eval(regs.limitChrgRate).write() # eval(regs.setChargeLimits).write() # if datetime.datetime.now().hour == 23 and ret.pwrMaxPct < 99: #reset power limit every night # eval(regs.setPowerLimitPercent).value = 10000; #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV # eval(regs.setPowerLimitEN).value = 1 #set charge rate: bit1 set discharge rate: bit 2 # eval(regs.limitChrgRate).write() # eval(regs.setChargeLimits).write() try: if(estProduction != estimations.estProd and datetime.datetime.now().hour < 9): estimations.estProd = estProduction estimations.increaseTmr = 0 if(estimations.estProd > 35): #_LOGGER.debug("High yeld predicted, reducing battery charge current") if ret.soc > 45: #if SOC in the morning is above 45% wait with charging until midday eval(regs.limitChrgRate).value = int(500/2.04) #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV else: #if less than 45% SOC in the morning charge slow (SOC dependant) until midday eval(regs.limitChrgRate).value = int((5000 - ret.soc*100)/2.04) #divide by two because this references 20.5kw max charge of battery instead of 10kw from INV eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 eval(regs.limitChrgRate).write() eval(regs.setChargeLimits).write() elif ret.crgMaxPct < 70: eval(regs.limitChrgRate).value = int(8000/2.04) #set charge rate to 80% if no high yeld is predicted eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 eval(regs.limitChrgRate).write() eval(regs.setChargeLimits).write() elif datetime.datetime.now().hour == 11 or datetime.datetime.now().hour == 12: #inclease charge rate at midday estimations.increaseTmr = estimations.increaseTmr + 1 if estimations.increaseTmr > 300: #slowdown rate change to 300 * 3sec (15 min.) estimations.increaseTmr = 0 maxMiddDayChrg = 30 if(int(sunset.strftime('%H')) < 18): #inclrease max charge rate during wintertime, where time to full SOC is less. maxMiddDayChrg = 70 if ret.crgMaxPct < maxMiddDayChrg: estimations.increaseTmr = 0 eval(regs.limitChrgRate).value = int((ret.crgMaxPct*100+500)/2.04) #increase by 5% every 15 min --> reach 30% after 5*15min max eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 eval(regs.limitChrgRate).write() eval(regs.setChargeLimits).write() _LOGGER.debug("Increasing charge rate to buffer feed peaks to " + str(int(ret.crgMaxPct*100+500)) + "W") if datetime.datetime.now().hour >= int(sunset.strftime('%H'))-4 and ret.soc < 70 and ret.crgMaxPct < 90: eval(regs.limitChrgRate).value = int((10000)/2.04) eval(regs.setChargeLimits).value = 1 #set charge rate: bit1 set discharge rate: bit 2 eval(regs.limitChrgRate).write() eval(regs.setChargeLimits).write() _LOGGER.debug("Increasing charge rate because of too low SOC " + str(int(10000)) + "W") return ret #print(ret.p_wr) #print("-----") #sleep(1) except Exception as error: _LOGGER.warning(f"No modbus data received from Inverter: %s",error) """ Model: common (1) ID 1 L 65 Mn Fronius Md Symo GEN24 10.0 Opt None Vr 1.34.2-1 SN 33095286 DA 1 Model: inverter_three_phase (103) ID 103 L 50 A 22300 A AphA 8115 A AphB 7527 A AphC 6658 A A_SF -5 PPVphAB 4000 V PPVphBC 4015 V PPVphCA 4033 V PhVphA 2310 V PhVphB 2317 V PhVphC 2328 V V_SF -1 W -5145 W W_SF -2 Hz 4998 Hz Hz_SF -2 VA 5145 VA VA_SF -2 VAr 31275 var VAr_SF -5 PF 1000 Pct PF_SF -1 WH 3939319098 Wh WH_SF -2 DCA None A DCA_SF None DCV None V DCV_SF None DCW 25076 W DCW_SF -5 TmpCab 526 C TmpSnk None C TmpTrns None C TmpOt None C Tmp_SF -1 St 4 StVnd 4 Evt1 0 Evt2 0 EvtVnd1 0 EvtVnd2 0 EvtVnd3 None EvtVnd4 None Model: nameplate (120) ID 120 L 26 DERTyp 82 WRtg 1000 W WRtg_SF 1 VARtg 1000 VA VARtg_SF 1 VArRtgQ1 714 var VArRtgQ2 714 var VArRtgQ3 -714 var VArRtgQ4 -714 var VArRtg_SF 1 ARtg 1443 A ARtg_SF -2 PFRtgQ1 -700 cos() PFRtgQ2 700 cos() PFRtgQ3 -700 cos() PFRtgQ4 700 cos() PFRtg_SF -3 WHRtg 22118 Wh WHRtg_SF 0 AhrRtg None AH AhrRtg_SF None MaxChaRte 20480 W MaxChaRte_SF 0 MaxDisChaRte 20480 W MaxDisChaRte_SF 0 Pad 32768 Model: settings (121) ID 121 L 30 WMax 1000 W VRef 231 V VRefOfs 0 V VMax None V VMin None V VAMax 1000 VA VArMaxQ1 714 var VArMaxQ2 714 var VArMaxQ3 -714 var VArMaxQ4 -714 var WGra None % WMax/sec PFMinQ1 -700 cos() PFMinQ2 700 cos() PFMinQ3 -700 cos() PFMinQ4 700 cos() VArAct None ClcTotVA None MaxRmpRte None % WGra ECPNomHz None Hz ConnPh None WMax_SF 1 VRef_SF 0 VRefOfs_SF 0 VMinMax_SF None VAMax_SF 1 VArMax_SF 1 WGra_SF None PFMin_SF -3 MaxRmpRte_SF None ECPNomHz_SF None Model: status (122) ID 122 L 44 PVConn 7 StorConn 7 ECPConn 1 ActWh 39393190 Wh ActVAh None VAh ActVArhQ1 None varh ActVArhQ2 None varh ActVArhQ3 None varh ActVArhQ4 None varh VArAval None var VArAval_SF None WAval None var WAval_SF None StSetLimMsk None StActCtl 0 TmSrc RTC Tms 791196551 Secs RtSt None Ris None ohms Ris_SF None Model: controls (123) ID 123 L 24 Conn_WinTms 0 Secs Conn_RvrtTms 0 Secs Conn 1 WMaxLimPct 10000 % WMax WMaxLimPct_WinTms 0 Secs WMaxLimPct_RvrtTms 0 Secs WMaxLimPct_RmpTms 0 Secs WMaxLim_Ena 0 OutPFSet 1000 cos() OutPFSet_WinTms 0 Secs OutPFSet_RvrtTms 0 Secs OutPFSet_RmpTms 0 Secs OutPFSet_Ena 0 VArWMaxPct None % WMax VArMaxPct 100 % VArMax VArAvalPct None % VArAval VArPct_WinTms 0 Secs VArPct_RvrtTms 0 Secs VArPct_RmpTms 0 Secs VArPct_Mod 2 VArPct_Ena 0 WMaxLimPct_SF -2 OutPFSet_SF -3 VArPct_SF 0 Model: mppt (160) ID 160 L 88 DCA_SF -4 DCV_SF -2 DCW_SF -1 DCWH_SF -2 Evt None N 4 TmsPer None 01:ID 1 01:IDStr MPPT 1 01:DCA 22696 A 01:DCV 55400 V 01:DCW 12574 W 01:DCWH 3156686802 Wh 01:Tms 791196551 Secs 01:Tmp None C 01:DCSt None 01:DCEvt None 02:ID 2 02:IDStr MPPT 2 02:DCA 15510 A 02:DCV 32689 V 02:DCW 5070 W 02:DCWH 989224180 Wh 02:Tms 791196551 Secs 02:Tmp None C 02:DCSt None 02:DCEvt None 03:ID 3 03:IDStr StCha 3 03:DCA 42263 A 03:DCV 41721 V 03:DCW 17641 W 03:DCWH 877835025 Wh 03:Tms 791196551 Secs 03:Tmp None C 03:DCSt None 03:DCEvt None 04:ID 4 04:IDStr StDisCha 4 04:DCA 0 A 04:DCV 41721 V 04:DCW 0 W 04:DCWH 824803735 Wh 04:Tms 791196551 Secs 04:Tmp None C 04:DCSt None 04:DCEvt None Model: storage_basic (124) ID 124 L 24 WChaMax 20480 W WChaGra 100 % WChaMax/sec WDisChaGra 100 % WChaMax/sec StorCtl_Mod 0 VAChaMax None VA MinRsvPct 0 % WChaMax ChaState 1240 % AhrRtg StorAval None AH InBatV None V ChaSt 4 OutWRte 10000 % WDisChaMax InWRte 10000 % WChaMax InOutWRte_WinTms None Secs InOutWRte_RvrtTms 0 Secs InOutWRte_RmpTms None Secs ChaGriSet 1 WChaMax_SF 0 WChaDisChaGra_SF 0 VAChaMax_SF None MinRsvPct_SF -2 ChaState_SF -2 StorAval_SF None InBatV_SF None InOutWRte_SF -2 """