Skip to content

Commit

Permalink
Merge pull request BJReplay#91 from BJReplay/Daily-forecasts-site-bre…
Browse files Browse the repository at this point in the history
…akdown

Post v4.0.38
  • Loading branch information
autoSteve authored Jul 19, 2024
2 parents 6bfd82c + 4d761b6 commit eb950c7
Show file tree
Hide file tree
Showing 15 changed files with 503 additions and 83 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ Here you can change the dampening factor value for any hour. Values from 0.0 - 1

[<img src="https://github.com/BJReplay/ha-solcast-solar/blob/v3/.github/SCREENSHOTS/dampopt.png" width="200">](https://github.com/BJReplay/ha-solcast-solar/blob/v3/.github/SCREENSHOTS/dampopt.png)

## Sensor Attributes Configuration

New in v4.0.39 is the option to turn off many sensor attributes.

There are quite a few sensor attributes that can be used as a data source for template sensors, charts, etc., including a per-site breakdown, estimate 10/50/90 values, and per-hour and half hour detailed breakdown for each forecast day.

Many users will not use these attributes, so to cut the clutter (especially in the UI) and also long-term statistics (LTS) storage all of these can be disabled.

By default, all of them are enabled. (Hourly and half-hourly detail is excluded from being sent to LTS.)

> [!NOTE]
> If you want to implement the sample PV graph below then you'll need to keep half-hourly detail breakdown enabled.
## Key Solcast concepts

Solcast will produce a forecast of your solar PV generation for today, tomorrow, the day after (day 3), ... up to day 7.
Expand Down Expand Up @@ -310,6 +323,8 @@ Click the Forecast option button and select the Solcast Solar option.. Click SAV
> {{ state_attr('sensor.solcast_pv_forecast_peak_forecast_today', 'estimate10') | float(0) }}
> {{ state_attr('sensor.solcast_pv_forecast_peak_forecast_today', 'estimate10-1234-5678-9012-3456') | float(0) }}
> ```
>
> Also see the sample PV graph below for how to chart forecast detail from the detailedForecast attribute.
> [!NOTE]
> The values for `Next Hour` and `Forecast Next X Hours` may be different if the custom X hour setting is 1. This has a simple explanation.
Expand Down Expand Up @@ -476,6 +491,13 @@ None

## Changes

v4.0.39
* Updates to sensor descriptions, and alter some sensor names by @isorin (Potentially breaking for UI/automations/etc. should these these sensors be in use. Power in 30/60 minutes, and custom X hours sensor.)
* Remove dependency on scipy library by @autoSteve
* Add granular configuration options for attributes by @autosteve

Full Changelog: https://github.com/BJReplay/ha-solcast-solar/compare/v4.0.38...v4.0.39

v4.0.38
* Add Solcast key concepts and sample PV generation graph to readme by @gcoan
* Add PCHIP spline to forecast remaining by @autoSteve
Expand Down
58 changes: 43 additions & 15 deletions custom_components/solcast_solar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@
SERVICE_REMOVE_HARD_LIMIT,
SOLCAST_URL,
CUSTOM_HOUR_SENSOR,
KEY_ESTIMATE
KEY_ESTIMATE,
BRK_ESTIMATE,
BRK_ESTIMATE10,
BRK_ESTIMATE90,
BRK_SITE,
BRK_HALFHOURLY,
BRK_HOURLY,
)

from .coordinator import SolcastUpdateCoordinator
Expand Down Expand Up @@ -89,15 +95,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except loader.IntegrationNotFound:
pass

#new in v4.0.16 for the selector of which field to use from the data
if entry.options.get(KEY_ESTIMATE,None) is None:
new = {**entry.options}
new[KEY_ESTIMATE] = "estimate"
hass.config_entries.async_update_entry(entry, options=new, version=7)

optdamp = {}
try:
#if something goes wrong ever with the damp factors just create a blank 1.0
#if something ever goes wrong with the damp factors just create a blank 1.0
for a in range(0,24):
optdamp[str(a)] = entry.options[f"damp{str(a).zfill(2)}"]
except Exception as ex:
Expand All @@ -108,7 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for a in range(0,24):
optdamp[str(a)] = 1.0

# Introduced in 2024.6.0: async_get_time_zone
# Introduced in core 2024.6.0: async_get_time_zone
try:
dt_util.async_get_time_zone
asynctz = True
Expand All @@ -118,15 +118,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
tz = await dt_util.async_get_time_zone(hass.config.time_zone)
else:
tz = dt_util.get_time_zone(hass.config.time_zone)

options = ConnectionOptions(
entry.options[CONF_API_KEY],
SOLCAST_URL,
hass.config.path('/config/solcast.json'),
tz,
optdamp,
entry.options[CUSTOM_HOUR_SENSOR],
entry.options.get(KEY_ESTIMATE,"estimate"),
(entry.options.get(HARD_LIMIT,100000)/1000),
entry.options.get(CUSTOM_HOUR_SENSOR, 1),
entry.options.get(KEY_ESTIMATE, "estimate"),
(entry.options.get(HARD_LIMIT,100000) / 1000),
entry.options.get(BRK_ESTIMATE, True),
entry.options.get(BRK_ESTIMATE10, True),
entry.options.get(BRK_ESTIMATE90, True),
entry.options.get(BRK_SITE, True),
entry.options.get(BRK_HALFHOURLY, True),
entry.options.get(BRK_HOURLY, True),
)

solcast = SolcastApi(aiohttp_client.async_get_clientsession(hass), options)
Expand Down Expand Up @@ -340,7 +347,7 @@ def upgraded():

#new 4.0.8
#dampening factor for each hour
if config_entry.version == 4:
if config_entry.version < 5:
new = {**config_entry.options}
for a in range(0,24):
new[f"damp{str(a).zfill(2)}"] = 1.0
Expand All @@ -357,7 +364,7 @@ def upgraded():

#new 4.0.15
#custom sensor for 'next x hours'
if config_entry.version == 5:
if config_entry.version < 6:
new = {**config_entry.options}
new[CUSTOM_HOUR_SENSOR] = 1
try:
Expand All @@ -373,7 +380,7 @@ def upgraded():

#new 4.0.16
#which estimate value to use for data calcs est,est10,est90
if config_entry.version == 6:
if config_entry.version < 7:
new = {**config_entry.options}
new[KEY_ESTIMATE] = "estimate"
try:
Expand All @@ -387,4 +394,25 @@ def upgraded():
else:
raise

#new 4.0.39
#attributes to include
if config_entry.version < 8:
new = {**config_entry.options}
if new.get(BRK_ESTIMATE) is None: new[BRK_ESTIMATE] = True
if new.get(BRK_ESTIMATE10) is None: new[BRK_ESTIMATE10] = True
if new.get(BRK_ESTIMATE90) is None: new[BRK_ESTIMATE90] = True
if new.get(BRK_SITE) is None: new[BRK_SITE] = True
if new.get(BRK_HALFHOURLY)is None: new[BRK_HALFHOURLY] = True
if new.get(BRK_HOURLY) is None: new[BRK_HOURLY] = True
try:
hass.config_entries.async_update_entry(config_entry, options=new, version=8)
upgraded()
except Exception as e:
if "unexpected keyword argument 'version'" in e:
config_entry.version = 8
hass.config_entries.async_update_entry(config_entry, options=new_options)
upgraded()
else:
raise

return True
66 changes: 64 additions & 2 deletions custom_components/solcast_solar/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
SelectSelectorMode,
)
from homeassistant import config_entries
from .const import DOMAIN, CONFIG_OPTIONS, CUSTOM_HOUR_SENSOR
from .const import DOMAIN, CONFIG_OPTIONS, CUSTOM_HOUR_SENSOR, BRK_ESTIMATE, BRK_ESTIMATE10, BRK_ESTIMATE90, BRK_SITE, BRK_HALFHOURLY, BRK_HOURLY

@config_entries.HANDLERS.register(DOMAIN)
class SolcastSolarFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Solcast Solar."""

VERSION = 6 #v5 started in 4.0.8, #6 started 4.0.15
VERSION = 8 #v5 started in 4.0.8, #6 started 4.0.15, #7 started in 4.0.16, #8 started in 4.0.39

@staticmethod
@callback
Expand Down Expand Up @@ -67,6 +67,12 @@ async def async_step_user(
"damp22":1.0,
"damp23":1.0,
"customhoursensor":1,
BRK_ESTIMATE: True,
BRK_ESTIMATE10: True,
BRK_ESTIMATE90: True,
BRK_SITE: True,
BRK_HALFHOURLY: True,
BRK_HOURLY: True,
},
)

Expand Down Expand Up @@ -99,6 +105,8 @@ async def async_step_init(self, user_input=None):
return await self.async_step_api()
elif nextAction == "configure_customsensor":
return await self.async_step_customsensor()
elif nextAction == "configure_attributes":
return await self.async_step_attributes()
else:
errors["base"] = "incorrect_options_action"

Expand Down Expand Up @@ -328,4 +336,58 @@ async def async_step_customsensor(self, user_input: dict[str, Any] | None = None
}
),
errors=errors,
)

async def async_step_attributes(self, user_input: dict[str, Any] | None = None) -> FlowResult:
"""Manage the attributes present."""

errors = {}

estimateBreakdown = self.config_entry.options[BRK_ESTIMATE]
estimateBreakdown10 = self.config_entry.options[BRK_ESTIMATE10]
estimateBreakdown90 = self.config_entry.options[BRK_ESTIMATE90]
siteBreakdown = self.config_entry.options[BRK_SITE]
halfHourly = self.config_entry.options[BRK_HALFHOURLY]
hourly = self.config_entry.options[BRK_HOURLY]

if user_input is not None:
try:
estimateBreakdown = user_input[BRK_ESTIMATE]
estimateBreakdown10 = user_input[BRK_ESTIMATE10]
estimateBreakdown90 = user_input[BRK_ESTIMATE90]
siteBreakdown = user_input[BRK_SITE]
halfHourly = user_input[BRK_HALFHOURLY]
hourly = user_input[BRK_HOURLY]

allConfigData = {**self.config_entry.options}
allConfigData[BRK_ESTIMATE] = estimateBreakdown
allConfigData[BRK_ESTIMATE10] = estimateBreakdown10
allConfigData[BRK_ESTIMATE90] = estimateBreakdown90
allConfigData[BRK_SITE] = siteBreakdown
allConfigData[BRK_HALFHOURLY] = halfHourly
allConfigData[BRK_HOURLY] = hourly

self.hass.config_entries.async_update_entry(
self.config_entry,
title="Solcast Solar",
options=allConfigData,
)

return self.async_create_entry(title="Solcast Solar", data=None)
except Exception as e:
errors["base"] = "unknown"

return self.async_show_form(
step_id="attributes",
data_schema=vol.Schema(
{
vol.Required(BRK_ESTIMATE10, description={"suggested_value": estimateBreakdown10}): bool,
vol.Required(BRK_ESTIMATE, description={"suggested_value": estimateBreakdown}): bool,
vol.Required(BRK_ESTIMATE90, description={"suggested_value": estimateBreakdown90}): bool,
vol.Required(BRK_SITE, description={"suggested_value": siteBreakdown}): bool,
vol.Required(BRK_HALFHOURLY, description={"suggested_value": halfHourly}): bool,
vol.Required(BRK_HOURLY, description={"suggested_value": hourly}): bool,
}
),
errors=errors,
)
7 changes: 7 additions & 0 deletions custom_components/solcast_solar/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@

CUSTOM_HOUR_SENSOR = "customhoursensor"
KEY_ESTIMATE = "key_estimate"
BRK_ESTIMATE = "attr_brk_estimate"
BRK_ESTIMATE10 = "attr_brk_estimate10"
BRK_ESTIMATE90 = "attr_brk_estimate90"
BRK_SITE = "attr_brk_site"
BRK_HALFHOURLY = "attr_brk_halfhourly"
BRK_HOURLY = "attr_brk_hourly"

SERVICE_UPDATE = "update_forecasts"
SERVICE_CLEAR_DATA = "clear_all_solcast_data"
Expand All @@ -31,4 +37,5 @@
selector.SelectOptionDict(value="configure_api", label="option1"),
selector.SelectOptionDict(value="configure_dampening", label="option2"),
selector.SelectOptionDict(value="configure_customsensor", label="option3"),
selector.SelectOptionDict(value="configure_attributes", label="option4"),
]
14 changes: 7 additions & 7 deletions custom_components/solcast_solar/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,31 +150,31 @@ def get_sensor_extra_attributes(self, key=""):
return self.solcast.get_forecasts_custom_hours(self.solcast._customhoursensor)
case "total_kwh_forecast_today":
ret = self.solcast.get_forecast_day(0)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(0)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(0)}
return ret
case "total_kwh_forecast_tomorrow":
ret = self.solcast.get_forecast_day(1)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(1)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(1)}
return ret
case "total_kwh_forecast_d3":
ret = self.solcast.get_forecast_day(2)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(2)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(2)}
return ret
case "total_kwh_forecast_d4":
ret = self.solcast.get_forecast_day(3)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(3)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(3)}
return ret
case "total_kwh_forecast_d5":
ret = self.solcast.get_forecast_day(4)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(4)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(4)}
return ret
case "total_kwh_forecast_d6":
ret = self.solcast.get_forecast_day(5)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(5)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(5)}
return ret
case "total_kwh_forecast_d7":
ret = self.solcast.get_forecast_day(6)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(6)}
ret = {**ret, **self.solcast.get_sites_total_kwh_forecast_day(6)}
return ret
case "power_now":
return self.solcast.get_sites_power_n_mins(0)
Expand Down
4 changes: 2 additions & 2 deletions custom_components/solcast_solar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/BJReplay/ha-solcast-solar/issues",
"requirements": ["aiohttp>=3.8.5", "aiofiles>=24.1.0", "datetime>=4.3", "isodate>=0.6.1", "scipy>=1.0.0"],
"version": "4.0.38"
"requirements": ["aiohttp>=3.8.5", "aiofiles>=24.1.0", "datetime>=4.3", "isodate>=0.6.1"],
"version": "4.0.39"
}
Loading

0 comments on commit eb950c7

Please sign in to comment.