Skip to content

Commit

Permalink
Merge pull request britkat1980#92 from britkat1980/dev3
Browse files Browse the repository at this point in the history
2.3.1
  • Loading branch information
britkat1980 authored Jul 30, 2023
2 parents 0706aa0 + 8dbbfe1 commit 848f236
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 71 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [2.3.1] - 2023-06-30
### Fixed
- REST api fixed to correct (dis)charge timeslot error
- Corrected Config page to clarify the Inverter_AC_X setting applies to all invertoers on "old" firmware

### Added
- Inverter frequency stats added
- PALM updated to v1.0.0, imporving Smart Target (Thanks @salewis38)


## [2.3] - 2023-06-29
### Breaking Change
- Inverter_Max_Rate now called Inverter_Max_Bat_Rate
Expand Down
22 changes: 18 additions & 4 deletions GivTCP/GivLUT.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class GivLUT:
logging.basicConfig(format='%(asctime)s - Inv'+ str(GiV_Settings.givtcp_instance)+' - %(module)-11s - [%(levelname)-8s] - %(message)s')
formatter = logging.Formatter(
'%(asctime)s - %(module)s - [%(levelname)s] - %(message)s')
fh = TimedRotatingFileHandler(GiV_Settings.Debug_File_Location, when='D', interval=1, backupCount=7)
fh = TimedRotatingFileHandler(GiV_Settings.Debug_File_Location, when='midnight', backupCount=7)
fh.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(fh)
Expand Down Expand Up @@ -76,10 +76,12 @@ class GivLUT:
invippkl=GiV_Settings.cache_location+"/invIPList.pkl"


if "TZ" in os.environ:
if hasattr(GiV_Settings,'timezone'): # If in Addon, use the HA Supervisor timezone
timezone=zoneinfo.ZoneInfo(key=GiV_Settings.timezone)
elif "TZ" in os.environ: # Otherwise use the ENV (for Docker)
timezone=zoneinfo.ZoneInfo(key=os.getenv("TZ"))
else:
timezone=zoneinfo.ZoneInfo(key="Europe/London")
timezone=zoneinfo.ZoneInfo(key="Europe/London") # Otherwise Assume everyone is in UK!

# Standard values for devices
maxInvPower=11000
Expand Down Expand Up @@ -291,6 +293,8 @@ class GivLUT:
"Local_control_mode":GEType("select","","setLocalControlMode","","",True,False,False),
"Battery_pause_mode":GEType("select","","setBatteryPauseMode","","",True,False,False),
"PV_input_mode":GEType("select","","setPVInputMode","","",True,False,False),
"Grid_Frequency":GEType("sensor","frequency","",0,60,True,False,False),
"Inverter_Output_Frequency":GEType("sensor","frequency","",0,60,True,False,False),
}
time_slots=[
"00:00:00","00:01:00","00:02:00","00:03:00","00:04:00","00:05:00","00:06:00","00:07:00","00:08:00","00:09:00","00:10:00","00:11:00","00:12:00","00:13:00","00:14:00","00:15:00","00:16:00","00:17:00","00:18:00","00:19:00","00:20:00","00:21:00","00:22:00","00:23:00","00:24:00","00:25:00","00:26:00","00:27:00","00:28:00","00:29:00","00:30:00","00:31:00","00:32:00","00:33:00","00:34:00","00:35:00","00:36:00","00:37:00","00:38:00","00:39:00","00:40:00","00:41:00","00:42:00","00:43:00","00:44:00","00:45:00","00:46:00","00:47:00","00:48:00","00:49:00","00:50:00","00:51:00","00:52:00","00:53:00","00:54:00","00:55:00","00:56:00","00:57:00","00:58:00","00:59:00",
Expand Down Expand Up @@ -328,4 +332,14 @@ class GivLUT:

def getTime(timestamp):
timeslot=timestamp.strftime("%H:%M")
return (timeslot)
return (timeslot)


'''
Firmware Versions for each Model
AC coupled 5xx old, 2xx new. 28x, 29x beta
Gen1 4xx Old, 1xx New. 19x Beta
Gen 2 909+ New. 99x Beta
Gen3 303+ New 39x Beta
AIO 6xx New 69x Beta
'''
3 changes: 3 additions & 0 deletions GivTCP/HA_Discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ def create_device_payload(topic,SN):
if GivLUT.entity_type[str(topic).split("/")[-1]].sensorClass=="voltage":
tempObj['unit_of_meas']="V"
tempObj['device_class']="Voltage"
if GivLUT.entity_type[str(topic).split("/")[-1]].sensorClass=="frequency":
tempObj['unit_of_meas']="Hz"
tempObj['device_class']="frequency"
if GivLUT.entity_type[str(topic).split("/")[-1]].sensorClass=="current":
tempObj['unit_of_meas']="A"
tempObj['device_class']="Current"
Expand Down
3 changes: 3 additions & 0 deletions GivTCP/palm_soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,9 @@ def t_to_hrs(time_in: int) -> str:
# GivEnergy power object initialisation
ge: GivEnergyObj = GivEnergyObj()

if exists(ge.batcap):
logger.info("Battery Capacity: "+ str(ge.batcap))

# Solcast PV prediction object initialisation
solcast: SolcastObj = SolcastObj()
solcast.update()
Expand Down
134 changes: 69 additions & 65 deletions GivTCP/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,78 +411,82 @@ def getData(fullrefresh): # Read from Inverter put in cache

############ Battery Power Stats ############

# Battery Power
Battery_power = GEInv.p_battery
if GiV_Settings.first_run: # Make sure that we publish the HA message for both Charge and Discharge times
power_output['Charge_Time_Remaining'] = 0
power_output['Charge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
power_output['Discharge_Time_Remaining'] = 0
power_output['Discharge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
if Battery_power >= 0:
discharge_power = abs(Battery_power)
charge_power = 0
power_output['Charge_Time_Remaining'] = 0
#power_output['Charge_Completion_Time'] = finaltime.replace(tzinfo=GivLUT.timezone).isoformat()
if discharge_power!=0:
# Time to get from current SOC to battery Reserve at the current rate
power_output['Discharge_Time_Remaining'] = max(int((((batteryCapacity)/1000)*((power_output['SOC'] - controlmode['Battery_Power_Reserve'])/100) / (discharge_power/1000)) * 60),0)
finaltime=datetime.datetime.now() + timedelta(minutes=power_output['Discharge_Time_Remaining'])
power_output['Discharge_Completion_Time'] = finaltime.replace(tzinfo=GivLUT.timezone).isoformat()
else:
power_output['Discharge_Time_Remaining'] = 0
#power_output['Discharge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
elif Battery_power <= 0:
discharge_power = 0
charge_power = abs(Battery_power)
# Battery Power
Battery_power = GEInv.p_battery
if GiV_Settings.first_run: # Make sure that we publish the HA message for both Charge and Discharge times
power_output['Charge_Time_Remaining'] = 0
power_output['Charge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
power_output['Discharge_Time_Remaining'] = 0
power_output['Discharge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
if Battery_power >= 0:
discharge_power = abs(Battery_power)
charge_power = 0
power_output['Charge_Time_Remaining'] = 0
#power_output['Charge_Completion_Time'] = finaltime.replace(tzinfo=GivLUT.timezone).isoformat()
if discharge_power!=0:
# Time to get from current SOC to battery Reserve at the current rate
power_output['Discharge_Time_Remaining'] = max(int((((batteryCapacity)/1000)*((power_output['SOC'] - controlmode['Battery_Power_Reserve'])/100) / (discharge_power/1000)) * 60),0)
finaltime=datetime.datetime.now() + timedelta(minutes=power_output['Discharge_Time_Remaining'])
power_output['Discharge_Completion_Time'] = finaltime.replace(tzinfo=GivLUT.timezone).isoformat()
else:
power_output['Discharge_Time_Remaining'] = 0
#power_output['Discharge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
if charge_power!=0:
# Time to get from current SOC to target SOC at the current rate (Target SOC-Current SOC)xBattery Capacity
power_output['Charge_Time_Remaining'] = max(int((((batteryCapacity)/1000)*((controlmode['Target_SOC'] - power_output['SOC'])/100) / (charge_power/1000)) * 60),0)
finaltime=datetime.datetime.now() + timedelta(minutes=power_output['Charge_Time_Remaining'])
power_output['Charge_Completion_Time'] = finaltime.replace(tzinfo=GivLUT.timezone).isoformat()
else:
power_output['Charge_Time_Remaining'] = 0
#power_output['Charge_Time_Remaining'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
power_output['Battery_Power'] = Battery_power
power_output['Charge_Power'] = charge_power
power_output['Discharge_Power'] = discharge_power

# Power flows
logger.debug("Getting Solar to H/B/G Power Flows")
if PV_power > 0:
S2H = min(PV_power, Load_power)
power_flow_output['Solar_to_House'] = S2H
S2B = max((PV_power-S2H)-export_power, 0)
power_flow_output['Solar_to_Battery'] = S2B
power_flow_output['Solar_to_Grid'] = max(PV_power - S2H - S2B, 0)

elif Battery_power <= 0:
discharge_power = 0
charge_power = abs(Battery_power)
power_output['Discharge_Time_Remaining'] = 0
#power_output['Discharge_Completion_Time'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
if charge_power!=0:
# Time to get from current SOC to target SOC at the current rate (Target SOC-Current SOC)xBattery Capacity
power_output['Charge_Time_Remaining'] = max(int((((batteryCapacity)/1000)*((controlmode['Target_SOC'] - power_output['SOC'])/100) / (charge_power/1000)) * 60),0)
finaltime=datetime.datetime.now() + timedelta(minutes=power_output['Charge_Time_Remaining'])
power_output['Charge_Completion_Time'] = finaltime.replace(tzinfo=GivLUT.timezone).isoformat()
else:
power_flow_output['Solar_to_House'] = 0
power_flow_output['Solar_to_Battery'] = 0
power_flow_output['Solar_to_Grid'] = 0
power_output['Charge_Time_Remaining'] = 0
#power_output['Charge_Time_Remaining'] = datetime.datetime.now().replace(tzinfo=GivLUT.timezone).isoformat()
power_output['Battery_Power'] = Battery_power
power_output['Charge_Power'] = charge_power
power_output['Discharge_Power'] = discharge_power
power_output['Grid_Frequency'] = GEInv.f_ac1
power_output['Inverter_Output_Frequency'] = GEInv.f_eps_backup

# Power flows
logger.debug("Getting Solar to H/B/G Power Flows")
if PV_power > 0:
S2H = min(PV_power, Load_power)
power_flow_output['Solar_to_House'] = S2H
S2B = max((PV_power-S2H)-export_power, 0)
power_flow_output['Solar_to_Battery'] = S2B
power_flow_output['Solar_to_Grid'] = max(PV_power - S2H - S2B, 0)

# Battery to House
logger.debug("Getting Battery to House Power Flow")
B2H = max(discharge_power-export_power, 0)
power_flow_output['Battery_to_House'] = B2H
else:
power_flow_output['Solar_to_House'] = 0
power_flow_output['Solar_to_Battery'] = 0
power_flow_output['Solar_to_Grid'] = 0

# Grid to Battery/House Power
logger.debug("Getting Grid to Battery/House Power Flow")
if import_power > 0:
power_flow_output['Grid_to_Battery'] = charge_power-max(PV_power-Load_power, 0)
power_flow_output['Grid_to_House'] = max(import_power-charge_power, 0)
# Battery to House
logger.debug("Getting Battery to House Power Flow")
B2H = max(discharge_power-export_power, 0)
power_flow_output['Battery_to_House'] = B2H

else:
power_flow_output['Grid_to_Battery'] = 0
power_flow_output['Grid_to_House'] = 0
# Grid to Battery/House Power
logger.debug("Getting Grid to Battery/House Power Flow")
if import_power > 0:
power_flow_output['Grid_to_Battery'] = charge_power-max(PV_power-Load_power, 0)
power_flow_output['Grid_to_House'] = max(import_power-charge_power, 0)

# Battery to Grid Power
logger.debug("Getting Battery to Grid Power Flow")
if export_power > 0:
power_flow_output['Battery_to_Grid'] = max(discharge_power-B2H, 0)
else:
power_flow_output['Battery_to_Grid'] = 0
else:
power_flow_output['Grid_to_Battery'] = 0
power_flow_output['Grid_to_House'] = 0

# Battery to Grid Power
logger.debug("Getting Battery to Grid Power Flow")
if export_power > 0:
power_flow_output['Battery_to_Grid'] = max(discharge_power-B2H, 0)
else:
power_flow_output['Battery_to_Grid'] = 0



# Check for all zeros
checksum = 0
Expand Down
2 changes: 1 addition & 1 deletion buildx.bat
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-dev:2.3.25 -t britkat/giv_tcp-dev:latest --push .
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-dev:2.3.26 -t britkat/giv_tcp-dev:latest --push .
::docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-dev:latest --push .
::docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 -t britkat/giv_tcp-ma:latest -t britkat/giv_tcp-ma:2.3.0 --push .
12 changes: 11 additions & 1 deletion startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ def getInvDeets(HOST):
hasMQTT=False
logger.critical("No HA MQTT service has been found")

#Get Timezone
url="http://supervisor/info"
result = requests.get(url,
headers={'Content-Type':'application/json',
'Authorization': 'Bearer {}'.format(access_token)})
info=result.json()
SuperTimezone=info['data']['timezone']
logger.info("Supervisor Timezone: "+str(SuperTimezone))

#Get Host Details
url="http://supervisor/network/info"
result = requests.get(url,
Expand Down Expand Up @@ -115,7 +124,7 @@ def getInvDeets(HOST):
else:
break
if list:
logger.info("Inverters found on "+str(networks[subnet])+" - "+str(list))
logger.info(str(len(list))+" Inverters found on "+str(networks[subnet])+" - "+str(list))
invList.update(list)
for inv in invList:
deets={}
Expand Down Expand Up @@ -244,6 +253,7 @@ def getInvDeets(HOST):
outp.write(" cache_location=\""+str(os.getenv("CACHELOCATION")+"\"\n"))
outp.write(" Debug_File_Location=\""+os.getenv("CACHELOCATION")+"/log_inv_"+str(inv)+".log\"\n")
outp.write(" inverter_num=\""+str(inv)+"\"\n")
if SuperTimezone: outp.write(" timezone=\""+str(SuperTimezone)+"\"\n")


######
Expand Down

0 comments on commit 848f236

Please sign in to comment.