batpred
batpred copied to clipboard
Cloudy days, solar clipping
So I've been having a think about management of solar clipping, especially on cloudy days, where I want to export as much PV as I can (because of the rate I'm paid for PV, versus the rate I can subsequently buy power at from the grid.
My setup is particularly prone to clipping, because I have a Gen1 3.6 hybrid inverter, which can charge the battery at 2600, pass through the inverter at 3600, but I have 11 x 405W panels with a theoretical peak of about 4.5kWp (but I've been seeing 4800W today, and it's not even lunchtime). So I want to make sure that I'm passing all that extra solar into the battery to avoid losing it. It's also very intermittently cloudy, so the charger could be set to, say, 1250W to soak up all that spare solar into the battery, but when the clouds come over, I'm putting too much into the battery, when I'd be better off just exporting it. (Hey, why waste >10% on thermal losses!)
So, I've started writing a little AppDaemon app, at the moment it's just reading data and not doing anything with it as I'm trying to think of the best way to handle it. It's running this every 1 minute, collecting the last 5 minutes of data regarding PV generation and inverter throughput and processing it. This is the little routine:
def collect_sensor_data(self, kwargs=None):
# Calculate the start and end time for the data collection (last 5 minutes)
end_time = datetime.datetime.now()
start_time = end_time - datetime.timedelta(minutes=5)
# Retrieve sensor data from Home Assistant
pvgen_entity = "sensor.givtcp_saXXXXXXXX_pv_power"
inverter_entity = "sensor.givtcp_saXXXXXXXX_invertor_power"
forecast_now_entity = "sensor.solcast_pv_forecast_power_now"
forecast_next_entity = "sensor.solcast_pv_forecast_power_next_30_mins"
pvgen_data = self.get_history_async(pvgen_entity, start_time, end_time)
inverter_data = self.get_history_async(inverter_entity, start_time, end_time)
pvgen_values = [int(state['state']) for states in pvgen_data for state in states]
inverter_values = [int(state['state']) for states in inverter_data for state in states]
forecast_now_value = self.get_state(forecast_now_entity)
forecast_next_value = self.get_state(forecast_next_entity)
pvgen_min = min(pvgen_values)
pvgen_max = max(pvgen_values)
pvgen_avg = int(statistics.mean(pvgen_values))
pvgen_stddev = int(statistics.stdev(pvgen_values))
inverter_min = min(inverter_values)
inverter_max = max(inverter_values)
inverter_avg = int(statistics.mean(inverter_values))
inverter_stddev = int(statistics.stdev(inverter_values))
# Process collected sensor data (example: printing)
self.log("PV Generation Data Collected from {} to {}: {}".format(start_time, end_time, pvgen_values))
self.log("Inverter Data Collected from {} to {}: {}".format(start_time, end_time, inverter_values))
self.log("PV Generation Statistics - Min: {}, Max: {}, Average: {}, Std Deviation: {}".format(pvgen_min, pvgen_max, pvgen_avg, pvgen_stddev))
self.log("Inverter Statistics - Min: {}, Max: {}, Average: {}, Std Deviation: {}".format(inverter_min, inverter_max, inverter_avg, inverter_stddev))
self.log("Solcast Forecast now: {}".format(forecast_now_value))
self.log("Solcast Forecast next: {}".format(forecast_next_value))
It calls the get_history_async procedure which is just cut+paste from predbat (except it looks at and end time and start time rather than days.
The idea here being that I can capture the highest solar and inverter throughput measured in the last 5 mintues, and also look at averages and standard deviations (a high standard deviation means it's cloudy).
A typical output from this would be:
2024-03-28 10:37:18.047955 INFO PVCalc:: PV Generation Data Collected from 2024-03-28 10:32:17.939639 to 2024-03-28 10:37:17.939639: [4878, 1920, 1622, 1761, 1612, 1586, 2020, 2091, 3195, 1893, 1661, 1752, 1526, 1413]
2024-03-28 10:37:18.049386 INFO PVCalc:: Inverter Data Collected from 2024-03-28 10:32:17.939639 to 2024-03-28 10:37:17.939639: [3490, 1009, 1142, 1186, 1130, 1151, 1152, 1164, 1841, 1139, 1149, 1153, 1163, 1152]
2024-03-28 10:37:18.050675 INFO PVCalc:: PV Generation Statistics - Min: 1413, Max: 4878, Average: 2066, Std Deviation: 917
2024-03-28 10:37:18.051979 INFO PVCalc:: Inverter Statistics - Min: 1009, Max: 3490, Average: 1358, Std Deviation: 642
2024-03-28 10:37:18.055044 INFO PVCalc:: Solcast Forecast now: 2794
2024-03-28 10:37:18.057885 INFO PVCalc:: Solcast Forecast next: 3392
I can see from that, the solcast forecast suggests 2794W, the peak I've seen is 4898 (because my battery charge rate was set to about 1200), and it's pretty cloudy, which can be seen by the high standard deviation figure.
My theory here being that if this little app sees maximum inverter throughput at close to maximum, then I probably want to be increasing the battery charge rate to collect more spare solar. (and similarly, if peak inverter throughput is lower than it can actually handle, then we might want to reduce throughput.
The figures derived from this could also be used to establish the cloud model used by Predbat for the next hour or so, perhaps in a way that tapers from one established here to the one calculated using the solcast forecasts?
This is very much a work in progress that I'm looking at here, but I figured I'd share it here as it may well be of interest to integrate with Predbat and get more thoughts? (I think it should be a seperate .py file which runs every minute rather than every 5 minutes, to try and capture cloudy days, minimise clipping, and maximise export).
There is logic to predict max inverter throughput rate based on setting inverter_limit in apps.yaml but I don't believe at the moment that Predbat does anything proactive with the battery charge level to minimise the impact of solar generation clipping. Its just full rate charging or zero rate charging.
I think there's different types of clipping that can occur:
- solar generation clipping (too much DC PV for the inverter)
- inverter throughput clipping (too much AC for the inverter)
The inverters can handle something like 50% extra DC I believe so its the latter in various forms is the more likely issue.
I've certainly started to see clipping happening in one of my own inverters. Array is 6.2kW on a 5kW gen 1 hybrid inverter. This morning ahead of the octopus power-up event from 11am Predbat was trying to discharge and export the battery, but that array was generating 4.7kW so the inverter was only able to discharge the battery at ~400W, resulting in the battery only getting down to 22% at the start of the power up event. If Predbat had been more aware that this could happen it could perhaps have started the discharge earlier.
The scenario you're highlighting @slopemad needs a different treatment, charging the battery at a lower rate so it trickle charges DC into the battery over the day and exports the remainder rather than the current 'fill up at full rate and export when full' which then can result in AC clipping.
More sophisticated understanding of clipping and dealing with it would be good in predbat.
An additional wrinkle for me having two arrays with different strings on different orientations is the solcast integration doesn't differentiate the different arrays.
With the Hybrid inverters, the solar is connected to the DC side, and I can have up to 3.6kW through the inverter, and 2.6kW into the battery. For some reason it cannot be configured so that if you are clipping, it'll automatically put excess into the battery. Eco in GivEnergy means Solar through the inverter to supply house load, then Solar to the battery up to the charge limit, and then additional Solar through the inverter for export.
Obviously, if I allow the full 2600 into the battery, then it'll fill up early on sunny days and then I'm clipping for the rest of the day. (I only have 8.x kWh of battery).
What I did last year was set the charge rate to 1357 in certain conditions which meant I would always have enough inverter and charge capacity to avoid clipping, provided the battery didn't fill up. But I was on Octopus cosy last year, so export rates were lower than import rates, it was worth charging from solar.
The maths are different on Agile. I was already clipping just after 9am this morning the sun was that shiny. It's March, I wasn't expecting it. The battery was already 57%, a fully battery was possible on solar (but I didn't necessarily want that, because export rates are higher than import rates - I'd rather use the grid as a battery, export at 15p and buy back at lower prices. Winner winner chicken dinner).
So what I'm trying to create here is a solution to more dynamically manage the charge rate, to minimise solar charging when it isn't wanted, to try and avoid a full battery before the panels stop generating more than 3.6kW. The solcast forecasts are absolute garbage at the moment in terms of predicting the amount of power I'm going to generate and when, but they're good enough to base the morning battery SOC on.
Looks like it's a sunny day again tomorrow to try and do some more prototyping.
I like the idea of dynamically controlling the charge rate to reduce clipping, but it would also have to be modelled in Predbat to ensure the plan aligns to reality.
Would it not be simply the case, for most people, of setting the charge rate to array_size_kwp - inverter_size_kw rather than setting it to zero?
If so I could include that as a 3rd option between 0 and full rate charge mode?
Would it not be simply the case, for most people, of setting the charge rate to array_size_kwp - inverter_size_kw rather than setting it to zero?
Not sure I follow how this would work in practice Trefor, I think there needs to be more intelligent prediction of solar clipping rather than just always charging the battery at a level that stops solar generation above the AC level of the inverter being lost.
e.g. today, very sunny first thing, my panels were generating 4.7kW of which a theoretical maximum of 2.6 was going into the battery (actually I think it was 2.4 in reality), and the rest was exported.
But by lunchtime it started raining and solar generation dropped right off to just under 1kW.
If I'd been throttling charging to 1.2kW (my excess of array vs inverter size) then I'd have ended up with a partially filled battery.
Maybe something like:
max_ac_charging_kwh = (number of hours of solar remaining * max inverter AC rate)
potential_clipped_kwh = SUM(PV prediction remaining today) - max_ac_charging_kwh
IF potential_clipped_kwh > 0
then:
# set charge rate to capture the clipped energy
charge rate = potential_clipped_kwh / (number of hours of solar remaining)
# and add on charge rate required to hit battery target SoC
charge rate += (target SoC in Wh - current SoC in Wh) / (number of hours of solar remaining)
else:
charge rate = battery max charge rate
And to repeat my point of earlier, multiple arrays/inverters will make this determination of what is the appropriate charge rate harder.
I like the idea of dynamically controlling the charge rate to reduce clipping, but it would also have to be modelled in Predbat to ensure the plan aligns to reality.
So Predbat could use the Solcast estimates to decide what the morning SOC should be, and use a reduced charge rate on that basis (probably based on array_size_kwp - inverter_size_kw as a maximum?) during those peaks.
Would it not be simply the case, for most people, of setting the charge rate to array_size_kwp - inverter_size_kw rather than setting it to zero?
For me, with such intermittent cloud (and then rain, and then lots of rain, followed by more rain), that would have just put too much charge in the battery today and at the height of summer, would fill the battery with power it could export directly, but I could see that being fine for people with HUGE batteries. The Solcast forecast for today for me had a peak prediction of 3.4kW (even the PV90 forecast wasn't any more than 3.5kW).
The concept I'm going to look at tomorrow is to increase the charge rate by 416W every minute until the inverter is no longer at it's maximum rate (so, in theory, maximum of 3 minutes to respond to sunshine), and only wind it back down as the 5 minute peak PV rate reduces, and see how that looks.
I've been looking at this again and making tweaks to the code. Right now it's just monitoring and not actually changing anything, but if anyone else wants to have a look/test that might be useful. Some of the code is pretty crufty as it's just prototyping at the moment. I am just running this to get running data out of the logs:
This code runs every 60 seconds, unless the levels of solar are regularly changing (i.e. intermittent cloud cover) in which case it runs every 30 seconds, and at the moment just looks back through the past 4 minutes of data. It wants to increase the charge_rate as soon as practically possible, but it wont reduce charge rate until that peak has disappeared out of the 4 minutes of data, to avoid too much changing of the settings.
This, in my mind, is different to how predbat itself models its predictions which is currently fine. This script isn't predicting, it is just there to avoid clipping, and avoiding putting more solar in the battery when it's better to export as much solar as possible. (Today, I'm better off exporting as much solar as I can, and then if there's a shortfall for the evening peak, I can charge the battery as 12p later this afternoon. The only solar going in the battery is the 'free' solar which I wouldn't be able to export due to exceeding the inverter capacity).
grep PVCalc /config/appdaemon.log
This is pvcalc.yaml
PVCalc::
module: PVCalc
class: PVCalc
# Sets the prefix for all created entities in HA - only change if you want to run more than once instance
prefix: pvcalc
# Timezone to work in
timezone: Europe/London
This is PVCalc.py:
import appdaemon.plugins.hass.hassapi as hass
import datetime
import time
import statistics
import json
import requests
class PVCalc(hass.Hass):
def initialize(self):
self.rest_api = "http://homeassistant.local:6345"
self.pvgen_entity = "sensor.givtcp_XXXXXXXXXX_pv_power"
self.inverter_entity = "sensor.givtcp_XXXXXXXXXX_invertor_power"
self.forecast_now_entity = "sensor.solcast_pv_forecast_power_now"
self.forecast_next_entity = "sensor.solcast_pv_forecast_power_next_30_mins"
self.current_charge_rate_entity = "number.givtcp_XXXXXXXXXX_battery_charge_rate"
#Adjust as appropriate
self.inverter_maxrate = 3600
self.charge_maxrate = 2600
self.solar_max_kwp = 4900
self.inverter_loss = 0.04
self.charge_loss = 0.067
self.discharge_loss = 0.05
self.charge_rate_steps = 0.04
# Schedule the data collection function to run every 5 minutes
self.frequency = 60
self.collect_sensor_data_handle = None
self.collect_sensor_data()
self.collect_sensor_data_handle = self.run_every(self.collect_sensor_data, datetime.datetime.now() + datetime.timedelta(seconds=5), self.frequency)
def collect_sensor_data(self, kwargs=None):
self.log("--------------- PVCALC START RUN ---------------")
# Calculate the start and end time for the data collection (last 5 minutes)
end_time = datetime.datetime.now()
start_time = end_time - datetime.timedelta(minutes=3)
# Retrieve sensor data from Home Assistant
pvgen_data = self.get_history_async(self.pvgen_entity, start_time, end_time)
inverter_data = self.get_history_async(self.inverter_entity, start_time, end_time)
pvgen_values = [int(state['state']) for states in pvgen_data for state in states]
inverter_values = [int(state['state']) for states in inverter_data for state in states]
forecast_now_value = self.get_state(self.forecast_now_entity)
forecast_next_value = self.get_state(self.forecast_next_entity)
current_charge_rate = int(self.get_state(self.current_charge_rate_entity))
new_charge_rate = current_charge_rate
charge_maxrate = self.charge_maxrate
solar_max_kwp = self.solar_max_kwp
inverter_loss = self.inverter_loss
charge_loss = self.charge_loss
discharge_loss = self.discharge_loss
charge_rate_steps = self.charge_rate_steps
pvgen_min = min(pvgen_values)
pvgen_max = max(pvgen_values)
pvgen_max_last = max(pvgen_values[-1], pvgen_values[-2])
pvgen_avg = int(statistics.mean(pvgen_values))
pvgen_last = pvgen_values[-1]
if len(pvgen_values) >= 2:
pvgen_stddev = int(statistics.stdev(pvgen_values))
else:
pvgen_stddev = None
inverter_min = min(inverter_values)
inverter_max = max(inverter_values)
inverter_max_last = max(inverter_values[-1], inverter_values[-2])
inverter_avg = int(statistics.mean(inverter_values))
inverter_last = inverter_values[-1]
if len(inverter_values) >= 2:
inverter_stddev = int(statistics.stdev(inverter_values))
else:
inverter_stddev = None
# Process collected sensor data (example: printing)
self.log("PV Generation Data Collected from {} to {}: {}".format(start_time, end_time, pvgen_values))
self.log("Inverter Data Collected from {} to {}: {}".format(start_time, end_time, inverter_values))
self.log("PV Generation Statistics - Min: {}, Max: {}, Last: {}, Average: {}, Std Deviation: {}".format(pvgen_min, pvgen_max, pvgen_last, pvgen_avg, pvgen_stddev))
self.log("Inverter Statistics - Min: {}, Max: {}, Last: {}, Average: {}, Std Deviation: {}".format(inverter_min, inverter_max, inverter_last, inverter_avg, inverter_stddev))
self.log("Solcast Forecast now: {}W".format(forecast_now_value))
self.log("Solcast Forecast next: {}W".format(forecast_next_value))
self.log("Current charge rate: {}".format(current_charge_rate))
expected_max_solar = int((self.inverter_maxrate / (1 - inverter_loss)) + current_charge_rate)
self.log("Expected Max Solar: {}".format(expected_max_solar))
if inverter_last < 0:
# AC Charging, charge_rate wants to be what agilebat says it should be here
self.log("Appears to be charging from AC. No change to charge rate.")
new_charge_rate = None
elif inverter_last > pvgen_last:
# Forced Discharge? charge_rate should be what agilebat says.
self.log("Inverter throughput exceeding PV generation. No change to charge rate.")
new_charge_rate = None
else:
if pvgen_max < self.inverter_maxrate * 0.95:
# If PV Generation is lower than inverter max then no point diverting solar to battery
self.log("PV Generation ({}) is below inverter max rate threshold ({}). No point diverting solar to battery.".format(pvgen_max, self.inverter_maxrate * 0.95))
new_charge_rate = 0
else:
expected_max_solar_threshold = int(expected_max_solar * 0.95)
self.log("Expected Max Solar Threshold {}".format(expected_max_solar_threshold))
#If the max being generated sits at around the capacity to deal with it (i.e. inverter + charger) then increase the charge rate
if abs(pvgen_max - expected_max_solar_threshold) < 104:
#Don't bother if the difference is minimal
self.log("Generation ({}) close to threshold ({}), no point adjusting".format(pvgen_max, expected_max_solar_threshold))
elif (pvgen_max > expected_max_solar_threshold):
#Does charge rate need to be increased?
self.log("PV Generation ({}) is near maximum expected ({}). Increase charge rate.".format(pvgen_max, expected_max_solar_threshold))
#new_charge_rate = current_charge_rate + (charge_maxrate * (charge_rate_steps * 2)) + 5
new_charge_rate = min(current_charge_rate + (pvgen_stddev + (charge_maxrate * charge_rate_steps) + 2), 1254)
else:
#Does charge rate need to be reduced?
self.log("PV Generation ({}) is below expected max solar threshold ({}). Reduce charge rate.".format(pvgen_max, expected_max_solar_threshold))
#new_charge_rate = current_charge_rate
new_charge_rate = pvgen_max - self.inverter_maxrate + (charge_maxrate * charge_rate_steps)
if new_charge_rate is not None:
new_charge_rate = int(min(int(new_charge_rate), 1253))
self.log("Proposed new charge rate: {}".format(new_charge_rate))
if new_charge_rate is not None and abs(new_charge_rate - current_charge_rate) > 104:
# Uncomment this to make it actually change the charge rate
#self.set_charge_rate(new_charge_rate)
else:
self.log("Not changing charge rate")
#Change running frequency if it is cloudy (high generation std dev)
if self.collect_sensor_data_handle is not None:
if (pvgen_stddev > 120):
if self.frequency == 60:
self.frequency = 30
self.reschedule_task()
else:
if self.frequency == 30:
self.frequency = 60
self.reschedule_task()
self.log("--------------- PVCALC END RUN ---------------")
def reschedule_task(self):
self.cancel_timer(self.collect_sensor_data_handle)
self.collect_sensor_data_handle = self.run_every(self.collect_sensor_data, datetime.datetime.now() + datetime.timedelta(seconds=5), self.frequency)
async def get_history_async_hook(self, result, entity_id, start_time, end_time):
"""
Async function to get history from HA
"""
if start_time and end_time:
result["data"] = await self.get_history(entity_id=entity_id, start_time=start_time, end_time=end_time)
else:
result["data"] = await self.get_history(entity_id=entity_id)
def get_history_async(self, entity_id, start_time=None, end_time=None):
"""
Async function to get history from HA using Async task
"""
result = {}
task = self.create_task(self.get_history_async_hook(result, entity_id=entity_id, start_time=start_time, end_time=end_time))
cnt = 0
while not task.done() and (cnt < 120):
time.sleep(0.05)
cnt += 0.05
if "data" in result:
return result["data"]
else:
self.log("Failure to fetch history for {}".format(entity_id))
raise ValueError
def set_charge_rate(self, charge_rate=2600):
api = "setChargeRate"
url = self.rest_api + "/" + api
# charge_rate expressed as a percentage of the maximum. (an integer from 0-100)
# Apparently this isn't true,
if not (0 <= charge_rate <= 2600):
self.log("Failed. Charge Rate must be between 0 and 2600")
return None
payload = {
"chargeRate": charge_rate
}
api_payload = json.dumps(payload)
self.log("Setting Charge Rate to {}".format(charge_rate))
try:
response = requests.post(url, data=api_payload)
response.raise_for_status()
self.log("Charge Rate successfully set")
except requests.exceptions.RequestException as e:
self.log("Error setting Charge Rate {}".format(e))
@slopemad I've tried installing it. Copying the pvcalc.yaml and PVCalc.py into /appdaemon/apps. Took several times to get it to load because I'd mis-named the .py as PVcalc.py
I customised the givtcp sensor names, increased the inverter max rate and solar charge kwp.
Anyway, loaded, gave the initial started log then nothing more in the appdaemon.log file that predbat was busy writing to
After a while of waiting for it, I found in the appdaemon add-on Log the following error crash:
2024-03-30 13:00:38.003562 WARNING PVCalc:: ------------------------------------------------------------
2024-03-30 13:00:38.004135 WARNING PVCalc:: Unexpected error running initialize() for PVCalc:
2024-03-30 13:00:38.005442 WARNING PVCalc:: ------------------------------------------------------------
2024-03-30 13:00:38.008490 WARNING PVCalc:: Traceback (most recent call last):
File "/usr/lib/python3.11/site-packages/appdaemon/app_management.py", line 162, in initialize_app
await utils.run_in_executor(self, init)
File "/usr/lib/python3.11/site-packages/appdaemon/utils.py", line 304, in run_in_executor
response = future.result()
^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/homeassistant/appdaemon/apps/PVCalc.py", line 29, in initialize
self.collect_sensor_data()
File "/homeassistant/appdaemon/apps/PVCalc.py", line 42, in collect_sensor_data
pvgen_values = [int(state['state']) for states in pvgen_data for state in states]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/homeassistant/appdaemon/apps/PVCalc.py", line 42, in <listcomp>
pvgen_values = [int(state['state']) for states in pvgen_data for state in states]
^^^^^^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: '0.005'
2024-03-30 13:00:38.009229 WARNING PVCalc:: ------------------------------------------------------------
Not sure what it is rejecting
It looks like the state of the pvgen_entity when being pulled from the history is a floating point number rather than something the code can turn into an integer.
After this line: pvgen_data = self.get_history_async(self.pvgen_entity, start_time, end_time)
Can you add self.log("PVGEN DATA: {}".format(pvgen_data))
i.e.
pvgen_data = self.get_history_async(self.pvgen_entity, start_time, end_time)
self.log("PVGEN DATA: {}".format(pvgen_data))
Then you should at least get a "PVGEN DATA" line in appdaemon.log?
It is indeed a float coming back:
2024-03-30 13:25:52.298833 INFO AppDaemon: Terminating PVCalc:
2024-03-30 13:25:52.302414 INFO AppDaemon: Reloading Module: /homeassistant/appdaemon/apps/PVCalc.py
2024-03-30 13:25:52.306253 INFO AppDaemon: Loading app PVCalc: using class PVCalc from module PVCalc
2024-03-30 13:25:52.310817 INFO AppDaemon: Calling initialize() for PVCalc:
2024-03-30 13:25:52.313573 INFO PVCalc:: --------------- PVCALC START RUN ---------------
2024-03-30 13:25:52.392781 INFO PVCalc:: PVGEN DATA: [[{'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.005', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T13:21:52+00:00', 'last_updated': '2024-03-30T13:21:52+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.559', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T13:25:46.708403+00:00', 'last_updated': '2024-03-30T13:25:46.708403+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}]]
Ah, I know, I have the UoM set to kW not W
Yep, you'll definitely want unit of measurement to be W rather than kW. (When I pull the history, I get mine back in Watts). The script is working in tiny units, like 100W, so it'll struggle to worth with data in kW because 0.005kW = 5000W, just isn't granular enough for this purpose.
I'm not sure where the uom as kW came from?
Spot the massive error ;-). 0.005kW is 5W ;-). And indeed 'state': '0.559' would be 559W.
This might work if your uom is kW...
pvgen_values = [int(float(state['state']) * 1000) for states in pvgen_data for state in states]
You can change the uom of any power based entities between W and kW. I have all mine changed to kW so its easier to read on dashboards, I'm more interested in knowing its generating 2.4kW than 2319W.
It just needs some logic adding to look at the UoM and convert it to Watts (x1000) if that's what you need to deal with. Sloppy programming if you ask me.....
Definite sloppy programming, I hadn't considered uom might not be watts. I'll make the code in this area a bit more robust.
I have power graphs like this which is why its all kW:
I've changed the pvgen_values line as suggested.
Its crashing again a bit further on
2024-03-30 13:43:25.081121 WARNING PVCalc:: ------------------------------------------------------------
2024-03-30 13:43:26.355875 WARNING PVCalc:: ------------------------------------------------------------
2024-03-30 13:43:26.356675 WARNING PVCalc:: Unexpected error running initialize() for PVCalc:
2024-03-30 13:43:26.357041 WARNING PVCalc:: ------------------------------------------------------------
2024-03-30 13:43:26.360055 WARNING PVCalc:: Traceback (most recent call last):
File "/usr/lib/python3.11/site-packages/appdaemon/app_management.py", line 162, in initialize_app
await utils.run_in_executor(self, init)
File "/usr/lib/python3.11/site-packages/appdaemon/utils.py", line 304, in run_in_executor
response = future.result()
^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/homeassistant/appdaemon/apps/PVCalc.py", line 29, in initialize
self.collect_sensor_data()
File "/homeassistant/appdaemon/apps/PVCalc.py", line 69, in collect_sensor_data
inverter_min = min(inverter_values)
^^^^^^^^^^^^^^^^^^^^
ValueError: min() arg is an empty sequence
2024-03-30 13:43:26.360755 WARNING PVCalc:: ------------------------------------------------------------
I think this will be the fix in the code as appropriate:
pvgen_values = []
for states in pvgen_data:
for state in states:
value = state['state']
if state['attributes']['unit_of_measurement'] == 'kW':
value = int(float(value) * 1000)
else:
value = int(value)
pvgen_values.append(value)
And, of course, at this prototyping stage, it is based around my setup, i.e. controlling a single hybrid inverter. I guess it needs to be very different logic when you have seperate string inverters and two different AC coupled chargers.
Adding that code in with extra debug lines shows it is now working. I thought we would have the same issue with inverter data, but its not something I have changed uom on so its working OK (but would need changing for resilience)
2024-03-30 14:42:42.369535 INFO PVCalc:: --------------- PVCALC START RUN ---------------
2024-03-30 14:42:42.439529 INFO PVCalc:: PVGEN DATA b4: [[{'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.367', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:38:42+00:00', 'last_updated': '2024-03-30T14:38:42+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.357', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:39:06.761443+00:00', 'last_updated': '2024-03-30T14:39:06.761443+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.350', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:39:32.788947+00:00', 'last_updated': '2024-03-30T14:39:32.788947+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.342', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:40:15.767755+00:00', 'last_updated': '2024-03-30T14:40:15.767755+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.343', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:40:26.703140+00:00', 'last_updated': '2024-03-30T14:40:26.703140+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.404', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:41:29.642840+00:00', 'last_updated': '2024-03-30T14:41:29.642840+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.417', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:41:56.023090+00:00', 'last_updated': '2024-03-30T14:41:56.023090+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.425', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:42:22.433738+00:00', 'last_updated': '2024-03-30T14:42:22.433738+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}]]
2024-03-30 14:42:42.442766 INFO PVCalc:: PVGEN DATA after: [367, 357, 350, 342, 343, 404, 417, 425]
2024-03-30 14:42:42.503907 INFO PVCalc:: inverter DATA b4: [[{'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '374', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:38:42+00:00', 'last_updated': '2024-03-30T14:38:42+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '366', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:39:06.775309+00:00', 'last_updated': '2024-03-30T14:39:06.775309+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '359', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:39:32.802418+00:00', 'last_updated': '2024-03-30T14:39:32.802418+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '352', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:40:15.813950+00:00', 'last_updated': '2024-03-30T14:40:15.813950+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '406', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:41:29.662628+00:00', 'last_updated': '2024-03-30T14:41:29.662628+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '420', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:41:56.067631+00:00', 'last_updated': '2024-03-30T14:41:56.067631+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '429', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:42:22.435282+00:00', 'last_updated': '2024-03-30T14:42:22.435282+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}]]
2024-03-30 14:42:42.508305 INFO PVCalc:: PV Generation Data Collected from 2024-03-30 14:38:42.369821 to 2024-03-30 14:42:42.369821: [367, 357, 350, 342, 343, 404, 417, 425]
2024-03-30 14:42:42.509221 INFO PVCalc:: Inverter Data Collected from 2024-03-30 14:38:42.369821 to 2024-03-30 14:42:42.369821: [374, 366, 359, 352, 406, 420, 429]
2024-03-30 14:42:42.510084 INFO PVCalc:: PV Generation Statistics - Min: 342, Max: 425, Last: 425, Average: 375, Std Deviation: 34
2024-03-30 14:42:42.510968 INFO PVCalc:: Inverter Statistics - Min: 352, Max: 429, Last: 429, Average: 386, Std Deviation: 31
2024-03-30 14:42:42.511850 INFO PVCalc:: Solcast Forecast now: NoneW
2024-03-30 14:42:42.512721 INFO PVCalc:: Solcast Forecast next: NoneW
2024-03-30 14:42:42.513606 INFO PVCalc:: Current charge rate: 2600
2024-03-30 14:42:42.514512 INFO PVCalc:: Expected Max Solar: 7808
2024-03-30 14:42:42.517816 INFO PVCalc:: Inverter throughput exceeding PV generation. No change to charge rate.
2024-03-30 14:42:42.519270 INFO PVCalc:: Proposed new charge rate: None
2024-03-30 14:42:42.522081 INFO PVCalc:: --------------- PVCALC END RUN ---------------
Suspicious that the forecast now and next is empty: 2024-03-30 14:49:36.702871 INFO PVCalc:: forecast_now_value: None 2024-03-30 14:49:36.704929 INFO PVCalc:: forecast_next_value: None 2024-03-30 14:49:36.706164 INFO PVCalc:: current_charge_rate: 2600
Fixed it, I had renamed my solcast entities as they'd come out something like solcast_solcast_forecast and I'd renamed them to solcast
2024-03-30 14:52:02.114926 INFO PVCalc:: --------------- PVCALC START RUN ---------------
2024-03-30 14:52:02.168593 INFO PVCalc:: PVGEN DATA b4: [[{'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.422', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:48:02+00:00', 'last_updated': '2024-03-30T14:48:02+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.380', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:48:28.033238+00:00', 'last_updated': '2024-03-30T14:48:28.033238+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.416', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:48:54.173035+00:00', 'last_updated': '2024-03-30T14:48:54.173035+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.423', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:49:20.399496+00:00', 'last_updated': '2024-03-30T14:49:20.399496+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.424', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:49:46.572577+00:00', 'last_updated': '2024-03-30T14:49:46.572577+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.005', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:50:38.722514+00:00', 'last_updated': '2024-03-30T14:50:38.722514+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.097', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:51:04.798617+00:00', 'last_updated': '2024-03-30T14:51:04.798617+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.005', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:51:30.845724+00:00', 'last_updated': '2024-03-30T14:51:30.845724+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_pv_power', 'state': '0.004', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'kW', 'device_class': 'power', 'icon': 'mdi:solar-power', 'friendly_name': 'G SD2237G182 Power G PV Power'}, 'last_changed': '2024-03-30T14:51:56.885465+00:00', 'last_updated': '2024-03-30T14:51:56.885465+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}]]
2024-03-30 14:52:02.170657 INFO PVCalc:: PVGEN DATA after: [422, 380, 416, 423, 424, 5, 97, 5, 4]
2024-03-30 14:52:02.225636 INFO PVCalc:: inverter DATA b4: [[{'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '429', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:48:02+00:00', 'last_updated': '2024-03-30T14:48:02+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '390', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:48:28.059470+00:00', 'last_updated': '2024-03-30T14:48:28.059470+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '422', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:48:54.223609+00:00', 'last_updated': '2024-03-30T14:48:54.223609+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '432', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:49:20.404302+00:00', 'last_updated': '2024-03-30T14:49:20.404302+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '434', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:50:12.736460+00:00', 'last_updated': '2024-03-30T14:50:12.736460+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '0', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:50:38.760552+00:00', 'last_updated': '2024-03-30T14:50:38.760552+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '244', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:51:04.808777+00:00', 'last_updated': '2024-03-30T14:51:04.808777+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}, {'entity_id': 'sensor.g_sd2237g182_invertor_power', 'state': '0', 'attributes': {'state_class': 'measurement', 'unit_of_measurement': 'W', 'device_class': 'power', 'friendly_name': 'G SD2237G182 Power G Invertor Power'}, 'last_changed': '2024-03-30T14:51:30.847296+00:00', 'last_updated': '2024-03-30T14:51:30.847296+00:00', 'context': {'id': '01HT54NX2MQD58P2P1KHNK5EBW', 'parent_id': None, 'user_id': None}}]]
2024-03-30 14:52:02.228312 INFO PVCalc:: forecast_now_value: 1812
2024-03-30 14:52:02.234845 INFO PVCalc:: forecast_next_value: 1826
2024-03-30 14:52:02.236282 INFO PVCalc:: current_charge_rate: 2600
2024-03-30 14:52:02.238347 INFO PVCalc:: PV Generation Data Collected from 2024-03-30 14:48:02.115661 to 2024-03-30 14:52:02.115661: [422, 380, 416, 423, 424, 5, 97, 5, 4]
2024-03-30 14:52:02.240825 INFO PVCalc:: Inverter Data Collected from 2024-03-30 14:48:02.115661 to 2024-03-30 14:52:02.115661: [429, 390, 422, 432, 434, 0, 244, 0]
2024-03-30 14:52:02.242214 INFO PVCalc:: PV Generation Statistics - Min: 4, Max: 424, Last: 4, Average: 241, Std Deviation: 205
2024-03-30 14:52:02.244030 INFO PVCalc:: Inverter Statistics - Min: 0, Max: 434, Last: 0, Average: 293, Std Deviation: 191
2024-03-30 14:52:02.245657 INFO PVCalc:: Solcast Forecast now: 1812W
2024-03-30 14:52:02.246996 INFO PVCalc:: Solcast Forecast next: 1826W
2024-03-30 14:52:02.248216 INFO PVCalc:: Current charge rate: 2600
2024-03-30 14:52:02.249778 INFO PVCalc:: Expected Max Solar: 7808
2024-03-30 14:52:02.253318 INFO PVCalc:: PV Generation (424) is below inverter max rate threshold (4750.0). No point diverting solar to battery.
2024-03-30 14:52:02.255383 INFO PVCalc:: Proposed new charge rate: 0
2024-03-30 14:52:02.257651 INFO PVCalc:: --------------- PVCALC END RUN ---------------
The Predbat solcast figure on my plan for this slot is 0.15kWh. Currently off the givenergy two arrays that are configured in solcast I'm getting 0.5kW and 1.8kW, so very much above forecast level!
(I have a 3rd FIT array which is generating a further 2.9kW right now but as this is AC only and not available to my hybrid inverters, I don't configure this in solcast so its "bunce" for predbat)
For me now, on my South facing array, I'm now beyond the point of the day when I'm generating more than my inverter can handle. And whilst it's been cloudy for much of the afternoon, it's now quite clear. So whilst I could be charging from grid at 12p, I can't because there's too much solar (which I can export for 15p). And it's touch and go whether there will be a cheap enough slot this evening to be worth charging from the grid at, when you account for inverter losses.
(1kWh of solar = 960W exported, of course. And I'd need to import 1040W to make up for the 960W exported when it comes to battery charging).
Fortunately one bit my installers got right was using 5kW inverters so at the moment I'm not yet getting a lot of clipping on the G inverter which is 16 panels on the East front of the roof, and on H which is 6 East facing and 6 West I'm well below clipping levels.
But it'll only increase as summer comes on
Sunny day today and looks like my front array was peaking out around 4.8kW so thought I'd see what pvcalc was saying but found it wasn't running.
Had crashed when I restarted HA a day ago:
2024-03-31 02:17:06.207324 WARNING PVCalc:: ------------------------------------------------------------
2024-03-31 02:17:06.207663 WARNING PVCalc:: Unexpected error running initialize() for PVCalc:
2024-03-31 02:17:06.207836 WARNING PVCalc:: ------------------------------------------------------------
2024-03-31 02:17:06.212247 WARNING PVCalc:: Traceback (most recent call last):
File "/usr/lib/python3.11/site-packages/appdaemon/app_management.py", line 162, in initialize_app
await utils.run_in_executor(self, init)
File "/usr/lib/python3.11/site-packages/appdaemon/utils.py", line 304, in run_in_executor
response = future.result()
^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/homeassistant/appdaemon/apps/PVCalc.py", line 29, in initialize
self.collect_sensor_data()
File "/homeassistant/appdaemon/apps/PVCalc.py", line 50, in collect_sensor_data
value = int(float(value) * 1000)
^^^^^^^^^^^^
ValueError: could not convert string to float: 'unavailable'
2024-03-31 02:17:06.212393 WARNING PVCalc:: ------------------------------------------------------------
I'll restart appdaemon, should get it going again
I'll also have a go at deploying and testing this.
I've been using the slow charging feature in predbat as kinda a stopgap for this feature till I discovered it today!
@slopemad just wondering whatever happened to this and whether you got anywhere towards incorporating this into a PR for predbat? I see there are other recent tickets #1206 and #1152 about clipping and what can be done about it in predbat. Definitely something that needs addressing for many users
My two cents worth on this issue. I.m relatively new to home assistant, GivTCP and predbat, so haven't figured out how to properly automate this. but I think the key is to ensure the battery SOC is sufficiently low prior to the time clipping is expected. I target to get it to 10% on a very sunny day, perhaps closer to 50% if intermittently cloudy. I use Solcast forecast to estimate how much excess energy from the excess PV DC I may have to store. (Solcast date actually gives me "clipped" estimates as it takes account of the inverter capacity, so I estimate the excess based on the how long time it predicts PV power will exceed clipping power). As soon as clipping is happening I set an automation to switch off the Eco mode and set the system to Timed Discharge, still with the above mentioned discharge SOC% (this can be done either based on the Solcast forecast, or I triggered it by the first time the actual PV power reaches the clipping point). I find this way the excess PV power is sent to the battery, but when PV production drops below the inverter capacity the battery is discharging again to increase the buffer for more excess PV. I don't think there is a need to adjust the battery charge or discharge rates for this. As long as it can be configured to only send the excess PV to the battery (seems to work fine on my GivEnergy hybrid inverter using timed discharge). The limiting factor is really the battery capacity, so key is to start with an as low as possible battery SOC, and ensure it can discharge to the grid when no clipping is occuring to make space for more excess PV. In order to ensure I have a full battery when the sun sets I could gradually increase the discharge SOC%. But for now I just set my system to revert back to eco mode and disable the discharge schedule once the remaining PV foreceast energy drops close to how much charge I need for the battery, plus estimated consumption and some margin. (I presently set this threshhold arbitrarily to 12 to 15 kWh). This way I'm sure to have a full battery when the sun sets. but I'm sure PredBat could determine this time more accurately.
Two more cents from a slightly different perspective....
With my Gen 3 hybrid inverter and my charge power set to 0 the battery still charges at about 300W from solar.
When Eco mode is turned on and the battery SoC is 100% I see a constant discharge power of approx. 60W.
I haven't gotten around to mapping out the logic yet but I'm planning to try a "simple" automation that has no reliance on load prediction or solar forecast:
-
Ensure the battery is below a certain SoC threshold prior to sunrise (to ensure room for clipped energy)
-
Between sunrise and sunset switch off eco mode (to maximise export) unless the battery SoC is below a certain threshold (e.g. due to no cheap overnight import slots). This is because there is usually an overnight slot cheap enough to make it more cost effective to charge from the grid rather than solar.
-
Constantly (every 20s) check the battery SoC and when it reaches the desired minimum threshold turn off eco mode (to maximise export)
-
Providing the import price is above a certain threshold, constantly (every 20s) compare GivTCP load power to GivTCP PV power and whenever demand is above solar generation switch eco mode back on and then switch it off again when the load falls below PV power (this is the same intent as the person above to account for intermittent clouds or a high load appliance being switched on)
-
Between sunset and sunrise switch on eco mode (to minimise import) unless the import price is below a certain threshold
-
Find the cheapest charge slots overnight and if there below a certain threshold charge the battery (otherwise let the pv do it the following morning) My battery capacity is 9.5kWh and my daily usage is about the same so I can make it through about 3 days with either very little solar or no cheap overnight slots.
I'd need to find a way to slip in a forced export down to 4% every few days somewhere between sunset and sunrise to allow the battery management system to re-calibrate the SoC currently I do this manually when I see a few cheap slots
GivTCP is already polling the inverter every 20s so it shouldn't overload it and the longest I should be importing or exporting when I don't want to would be around 40s (between eco mode switches)
With my Gen 3 hybrid inverter and my charge power set to 0 the battery still charges at about 300W from solar.
When Eco mode is turned on and the battery SoC is 100% I see a constant discharge power of approx. 60W.
If you use battery_pause_mode and set this to Charge Disabled it drops the power consumption quite a bit.
What you described above should work. I would caution that there is a finite time for the inverter to swap mode and start charging or discharging (there's physical relays inside that you can hear clicking on or off). Personally I wouldn't try instructing the inverter every 20 seconds. I'd do it every 5 minutes or so to reduce physical wear and tear and let the inverter catch up.
I'm leaving my inverter on a low (500-900W) charge rate whilst agile rates are bad overnight, to charge up slowly in the day. But if I had a cheaper overnight rate I would turn solar charging off entirely.
Forgive me if I'm missing something obvious here, but shouldn't it be fairly straightforward to set the effective inverter limit based on the battery SoC?
So in its simplest form (using my system as an example, nominal inverter limit = 5kW, max DC input = 7.5kW):
- if SoC >90%, inverter limit = 5kW
- else, inverter limit = 7.5kW
Then wouldn't Predbat's existing cost optimisation logic do the heavy lifting, prioritising max export (and trickle charging the battery with any excess once load is taken care of) until the last possible forecast moment when it can hit full charge before the 4pm Agile peak?
If the hard threshold proves unstable, a tapering lookup table implemented in exactly the same way as the charging curve could do the trick.
So in its simplest form (using my system as an example, nominal inverter limit = 5kW, max DC input = 7.5kW):
if SoC >90%, inverter limit = 5kW else, inverter limit = 7.5kW
I would say no, it needs to be more sophisticated than this.
On a sunny day my batteries will fully charge by 10am if set to the default max charging rate. At which point any generation above the max AC output rate will be clipped and lost.
To maximise generation and export we want to trickle charge the battery as much as possible so that it doesn't fill up too early. At the moment for example Predbat is in read only mode and the battery charge rate is set to 770W so it will take most of the day to charge up but I'll be able to export 5kW during that period alongside the battery charging.
Ideally the charge rate would be determined by predicted solar and predbat would use a combination of pause charge early in the day (when solar generation < inverter AC limit) and low charge rate (when solar generation > inverter AC limit) to maximise export.