Skip to content

Commit

Permalink
Additional sensor attributes and logging improvements (BJReplay#71)
Browse files Browse the repository at this point in the history
* Add pv_estimate/10/90 attributes to sensors

* Attribute breakdown for remaining, less debug log chatter

Plus make non-debug logs less "shouty", and add a welcoming startup info message.

* Update README.md

* More logging refinement

* More logging refinement

* Update manifest.json

* Update README.md
  • Loading branch information
autoSteve authored Jul 4, 2024
1 parent b327934 commit a04e707
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 129 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ Click the Forecast option button and select the Solcast Solar option.. Click SAV
| `D7` | number | Y | `kWh` | Total forecast solar production for day + 6 (day 7) |
| `This Hour` | number | Y | `Wh` | Forecasted solar production current hour (attributes contain site breakdown) |
| `Next Hour` | number | Y | `Wh` | Forecasted solar production next hour (attributes contain site breakdown) |
| `Forecast Next X Hours` | number | N | `kWh` | Custom user defined X hour forecast |
| `Remaining Today` | number | N | `kWh` | Predicted remaining solar production today |
| `Forecast Next X Hours` | number | Y | `kWh` | Custom user defined X hour forecast |
| `Remaining Today` | number | Y | `kWh` | Predicted remaining solar production today |
| `Peak Forecast Today` | number | Y | `W` | Highest predicted production within an hour period today (attributes contain site breakdown) |
| `Peak Time Today` | date/time | Y | | Hour of max forecasted production of solar today (attributes contain site breakdown) |
| `Peak Forecast Tomorrow` | number | Y | `W` | Highest predicted production within an hour period tomorrow (attributes contain site breakdown) |
Expand All @@ -287,9 +287,13 @@ Click the Forecast option button and select the Solcast Solar option.. Click SAV
> [!NOTE]
> Where a site breakdown is available as an attribute, the attribute name is the Solcast site resource ID.
>
> Most sensors also include an attribute for `pv_estimate`, `pv_estimate10` and `pv_estimate90`. Template sensors may be created to expose their value, or the `state_attr()` can be used directly in automations.
>
> Access these in a template sensor or automation using something like:
>
> ```{{ state_attr('sensor.solcast_pv_forecast_peak_forecast_today', '1234-5678-9012-3456') | float(0) }}```
> `{{ state_attr('sensor.solcast_pv_forecast_peak_forecast_today', '1234-5678-9012-3456') | float(0) }}`
> `{{ state_attr('sensor.solcast_pv_forecast_peak_forecast_today', 'pv_estimate10') | float(0) }}`
> `{{ state_attr('sensor.solcast_pv_forecast_peak_forecast_today', '1234-5678-9012-3456_pv_estimate10') | float(0) }}`
### Configuration
Expand Down Expand Up @@ -325,6 +329,11 @@ None
## Changes
v4.0.36
* (Enhancement) Additional sensor attributes (estimate/estimate10/estimate90) and logging improvements by @autoSteve
Full Changelog: https://github.com/BJReplay/ha-solcast-solar/compare/v4.0.35...v4.0.36
v4.0.35
* (Enhancement) Breakdown of individual site forecast wattage and time as attributes by @autoSteve
* Do not log options version upgrade if no upgrade is required by @autoSteve
Expand Down
43 changes: 27 additions & 16 deletions custom_components/solcast_solar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

random.seed()

_VERSION = ""
try:
integration = await loader.async_get_integration(hass, DOMAIN)
_VERSION = str(integration.version)
_LOGGER.info(
f"\n{'-'*67}\n"
f"Solcast integration version: {_VERSION}\n\n"
f"This is a custom integration. When troubleshooting a problem, after\n"
f"reviewing open and closed issues, and the discussions, check the\n"
f"required automation is functioning correctly and try enabling debug\n"
f"logging to see more. Troubleshooting tips available at:\n"
f"https://github.com/BJReplay/ha-solcast-solar/discussions/38\n\n"
f"Beta versions may also have addressed some issues so look at those.\n\n"
f"If all else fails, then open an issue and our community will try to\n"
f"help: https://github.com/BJReplay/ha-solcast-solar/issues\n"
f"{'-'*67}")
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}
Expand Down Expand Up @@ -119,14 +138,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

await solcast.load_saved_data()

_VERSION = ""
try:
integration = await loader.async_get_integration(hass, DOMAIN)
_VERSION = str(integration.version)
_LOGGER.info(f"Solcast Integration version number: {_VERSION}")
except loader.IntegrationNotFound:
pass

coordinator = SolcastUpdateCoordinator(hass, solcast, _VERSION)

await coordinator.setup()
Expand All @@ -139,28 +150,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

entry.async_on_unload(entry.add_update_listener(async_update_options))

_LOGGER.info(f"SOLCAST - Solcast API data UTC times are converted to {hass.config.time_zone}")
_LOGGER.info(f"Solcast API data UTC times are converted to {hass.config.time_zone}")

if options.hard_limit < 100:
_LOGGER.info(
f"SOLCAST - Inverter hard limit value has been set. If the forecasts and graphs are not as you expect, try running the service 'solcast_solar.remove_hard_limit' to remove this setting. "
f"Solcast inverter hard limit value has been set. If the forecasts and graphs are not as you expect, try running the service 'solcast_solar.remove_hard_limit' to remove this setting. "
f"This setting is really only for advanced quirky solar setups."
)

async def handle_service_update_forecast(call: ServiceCall):
"""Handle service call"""
_LOGGER.info(f"SOLCAST - Service call: {SERVICE_UPDATE}")
_LOGGER.info(f"Solcast service call: {SERVICE_UPDATE}")
await coordinator.service_event_update()

async def handle_service_clear_solcast_data(call: ServiceCall):
"""Handle service call"""
_LOGGER.info(f"SOLCAST - Service call: {SERVICE_CLEAR_DATA}")
_LOGGER.info(f"Solcast service call: {SERVICE_CLEAR_DATA}")
await coordinator.service_event_delete_old_solcast_json_file()

async def handle_service_get_solcast_data(call: ServiceCall) -> ServiceResponse:
"""Handle service call"""
try:
_LOGGER.info(f"SOLCAST - Service call: {SERVICE_QUERY_FORECAST_DATA}")
_LOGGER.info(f"Solcast service call: {SERVICE_QUERY_FORECAST_DATA}")

start = call.data.get(EVENT_START_DATETIME, dt_util.now())
end = call.data.get(EVENT_END_DATETIME, dt_util.now())
Expand All @@ -177,7 +188,7 @@ async def handle_service_get_solcast_data(call: ServiceCall) -> ServiceResponse:
async def handle_service_set_dampening(call: ServiceCall):
"""Handle service call"""
try:
_LOGGER.info(f"SOLCAST - Service call: {SERVICE_SET_DAMPENING}")
_LOGGER.info(f"Solcast service call: {SERVICE_SET_DAMPENING}")

factors = call.data.get(DAMP_FACTOR, None)

Expand Down Expand Up @@ -213,7 +224,7 @@ async def handle_service_set_dampening(call: ServiceCall):
async def handle_service_set_hard_limit(call: ServiceCall):
"""Handle service call"""
try:
_LOGGER.info(f"SOLCAST - Service call: {SERVICE_SET_HARD_LIMIT}")
_LOGGER.info(f"Solcast service call: {SERVICE_SET_HARD_LIMIT}")

hl = call.data.get(HARD_LIMIT, 100000)

Expand All @@ -238,7 +249,7 @@ async def handle_service_set_hard_limit(call: ServiceCall):
async def handle_service_remove_hard_limit(call: ServiceCall):
"""Handle service call"""
try:
_LOGGER.info(f"SOLCAST - Service call: {SERVICE_REMOVE_HARD_LIMIT}")
_LOGGER.info(f"Solcast service call: {SERVICE_REMOVE_HARD_LIMIT}")

opt = {**entry.options}
opt[HARD_LIMIT] = 100000
Expand Down
43 changes: 32 additions & 11 deletions custom_components/solcast_solar/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def setup(self):
async_track_utc_time_change(self._hass, self.update_utcmidnight_usage_sensor_data, hour=0,minute=0,second=0)
async_track_utc_time_change(self._hass, self.update_integration_listeners, minute=range(0, 60, 5), second=0)
except Exception as error:
_LOGGER.error("SOLCAST - Error coordinator setup: %s", traceback.format_exc())
_LOGGER.error("Solcast exception in coordinator setup: %s", traceback.format_exc())


async def update_integration_listeners(self, *args):
Expand Down Expand Up @@ -88,8 +88,6 @@ def get_energy_tab_data(self):

def get_sensor_value(self, key=""):
match key:
case "total_kwh_forecast_today":
return self.solcast.get_total_kwh_forecast_day(0)
case "peak_w_today":
return self.solcast.get_peak_w_day(0)
case "peak_w_time_today":
Expand All @@ -100,6 +98,8 @@ def get_sensor_value(self, key=""):
return self.solcast.get_forecast_n_hour(1)
case "forecast_custom_hour":
return self.solcast.get_forecast_custom_hours(self.solcast._customhoursensor)
case "total_kwh_forecast_today":
return self.solcast.get_total_kwh_forecast_day(0)
case "total_kwh_forecast_tomorrow":
return self.solcast.get_total_kwh_forecast_day(1)
case "total_kwh_forecast_d3":
Expand Down Expand Up @@ -131,7 +131,6 @@ def get_sensor_value(self, key=""):
case "lastupdated":
return self.solcast.get_last_updated_datetime()
case "hard_limit":
#return self.solcast._hardlimit < 100
return False if self.solcast._hardlimit == 100 else f"{round(self.solcast._hardlimit * 1000)}w"
# case "weather_description":
# return self.solcast.get_weather()
Expand All @@ -140,20 +139,40 @@ def get_sensor_value(self, key=""):

def get_sensor_extra_attributes(self, key=""):
match key:
case "forecast_this_hour":
return self.solcast.get_forecasts_n_hour(0)
case "forecast_next_hour":
return self.solcast.get_forecasts_n_hour(1)
case "forecast_custom_hour":
return self.solcast.get_forecasts_custom_hours(self.solcast._customhoursensor)
case "total_kwh_forecast_today":
return self.solcast.get_forecast_day(0)
ret = self.solcast.get_forecast_day(0)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(0)}
return ret
case "total_kwh_forecast_tomorrow":
return self.solcast.get_forecast_day(1)
ret = self.solcast.get_forecast_day(1)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(1)}
return ret
case "total_kwh_forecast_d3":
return self.solcast.get_forecast_day(2)
ret = self.solcast.get_forecast_day(2)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(2)}
return ret
case "total_kwh_forecast_d4":
return self.solcast.get_forecast_day(3)
ret = self.solcast.get_forecast_day(3)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(3)}
return ret
case "total_kwh_forecast_d5":
return self.solcast.get_forecast_day(4)
ret = self.solcast.get_forecast_day(4)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(4)}
return ret
case "total_kwh_forecast_d6":
return self.solcast.get_forecast_day(5)
ret = self.solcast.get_forecast_day(5)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(5)}
return ret
case "total_kwh_forecast_d7":
return self.solcast.get_forecast_day(6)
ret = self.solcast.get_forecast_day(6)
ret = {**ret, **self.solcast.get_total_kwh_forecasts_day(6)}
return ret
case "power_now":
return self.solcast.get_sites_power_n_mins(0)
case "power_now_30m":
Expand All @@ -168,6 +187,8 @@ def get_sensor_extra_attributes(self, key=""):
return self.solcast.get_sites_peak_w_day(1)
case "peak_w_time_tomorrow":
return self.solcast.get_sites_peak_w_time_day(1)
case "get_remaining_today":
return self.solcast.get_forecasts_remaining_today()
case _:
return None

Expand Down
2 changes: 1 addition & 1 deletion custom_components/solcast_solar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/BJReplay/ha-solcast-solar/issues",
"requirements": ["aiohttp>=3.8.5", "datetime>=4.3", "isodate>=0.6.1"],
"version": "4.0.35"
"version": "4.0.36"
}
12 changes: 6 additions & 6 deletions custom_components/solcast_solar/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def __init__(
self._sensor_data = coordinator.get_sensor_value(entity_description.key)
except Exception as ex:
_LOGGER.error(
f"SOLCAST - unable to get sensor value {ex} %s", traceback.format_exc()
f"Unable to get sensor value {ex} %s", traceback.format_exc()
)
self._sensor_data = None

Expand Down Expand Up @@ -347,7 +347,7 @@ def extra_state_attributes(self):
)
except Exception as ex:
_LOGGER.error(
f"SOLCAST - unable to get sensor value {ex} %s", traceback.format_exc()
f"Unable to get sensor value {ex} %s", traceback.format_exc()
)
return None

Expand Down Expand Up @@ -379,7 +379,7 @@ def _handle_coordinator_update(self) -> None:
)
except Exception as ex:
_LOGGER.error(
f"SOLCAST - unable to get sensor value {ex} %s", traceback.format_exc()
f"Unable to get sensor value {ex} %s", traceback.format_exc()
)
self._sensor_data = None

Expand Down Expand Up @@ -423,7 +423,7 @@ def __init__(
self._sensor_data = coordinator.get_site_sensor_value(self.rooftop_id, key)
except Exception as ex:
_LOGGER.error(
f"SOLCAST - unable to get sensor value {ex} %s", traceback.format_exc()
f"Unable to get sensor value {ex} %s", traceback.format_exc()
)
self._sensor_data = None

Expand Down Expand Up @@ -464,7 +464,7 @@ def extra_state_attributes(self):
)
except Exception as ex:
_LOGGER.error(
f"SOLCAST - unable to get sensor value {ex} %s", traceback.format_exc()
f"Unable to get sensor value {ex} %s", traceback.format_exc()
)
return None

Expand Down Expand Up @@ -495,7 +495,7 @@ def _handle_coordinator_update(self) -> None:
)
except Exception as ex:
_LOGGER.error(
f"SOLCAST - unable to get sensor value {ex} %s", traceback.format_exc()
f"Unable to get sensor value {ex} %s", traceback.format_exc()
)
self._sensor_data = None
self.async_write_ha_state()
Loading

0 comments on commit a04e707

Please sign in to comment.