* feat: add PVForecastHomeAssistant provider
Reads a PV forecast time series directly from a Home Assistant entity
attribute (matching the {"forecast": [{"datetime", "watts"}]} shape
already exposed by common HA PV forecast integrations, e.g. Helios
Forecast and Solcast) and feeds it into pvforecast_ac_power, following
the same self-polling provider pattern as PVForecastVrm.
Closes #1232.
* fix: use MagicMock instead of monkeypatched Response for mypy
requests.Response().json is a typed bound method; reassigning it to a
lambda fails mypy's method-assign check. Use MagicMock(spec=...) instead,
which mocks the response without fighting its static type.
* fix: address PR review on PVForecastHomeAssistant provider
Fixes two issues raised in review on PR #1258:
- _update_data() left stale pvforecast_ac_power values in place when a
refreshed forecast was empty or shorter than a previous one; it now
clears the active forecast window before writing and raises instead
of silently no-op'ing when the response has no usable data.
- pvforecast.homeassistant.entity_id used a "select" widget with no
manual-entry fallback in EOSdash, leaving it unusable in standalone
mode where the entity list can't be resolved without SUPERVISOR_TOKEN;
switched to a plain text field.
Also fills in the config docs and openapi.json for the new
pvforecast.homeassistant.* fields, which were missing from the
original commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: preserve retained forecast history when clearing stale entries
The previous fix cleared pvforecast_ac_power from start-of-day, but
PredictionProvider deliberately retains historical records back to
keep_datetime (prediction.historic_hours). Since Home Assistant
forecasts are future-only, that clear wiped out retained history
between midnight and the EMS start on every refresh.
Narrow the clear to [ems_start_datetime, end_datetime) - the actual
active forecast window, DST-adjusted - instead of the day boundary.
Addresses review feedback from @NormannK on PR #1258.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Mathias <mathias@Mathiass-MacBook-Air.local>
Co-authored-by: Mathias <mathias@Mathiass-Air.localdomain>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
18 KiB
PV Forecast Configuration
:::{table} pvforecast :widths: 10 20 10 5 5 30 :align: left
| Name | Environment Variable | Type | Read-Only | Default | Description |
|---|---|---|---|---|---|
| forecastsolar | EOS_PVFORECAST__FORECASTSOLAR |
PVForecastForecastSolarCommonSettings |
rw |
required |
ForecastSolar provider settings |
| homeassistant | EOS_PVFORECAST__HOMEASSISTANT |
PVForecastHomeAssistantCommonSettings |
rw |
required |
Home Assistant provider settings |
| max_planes | EOS_PVFORECAST__MAX_PLANES |
`int | None` | rw |
0 |
| planes | EOS_PVFORECAST__PLANES |
`list[akkudoktoreos.prediction.pvforecast.PVForecastPlaneSetting] | None` | rw |
None |
| planes_azimuth | List[float] |
ro |
N/A |
Compute a list of the azimuths per active planes. | |
| planes_inverter_paco | Any |
ro |
N/A |
Compute a list of the maximum power rating of the inverter per active planes. | |
| planes_peakpower | List[float] |
ro |
N/A |
Compute a list of the peak power per active planes. | |
| planes_tilt | List[float] |
ro |
N/A |
Compute a list of the tilts per active planes. | |
| planes_userhorizon | Any |
ro |
N/A |
Compute a list of the user horizon per active planes. | |
| provider | EOS_PVFORECAST__PROVIDER |
`str | None` | rw |
None |
| providers | list[str] |
ro |
N/A |
Available PVForecast provider ids. | |
| pvforecastimport | EOS_PVFORECAST__PVFORECASTIMPORT |
PVForecastImportCommonSettings |
rw |
required |
PV forecast import provider settings |
| pvlib | EOS_PVFORECAST__PVLIB |
PVForecastPVLibCommonSettings |
rw |
required |
PVLib provider settings |
| pvnode | EOS_PVFORECAST__PVNODE |
PVForecastPVNodeCommonSettings |
rw |
required |
PVNode provider settings |
| solcast | EOS_PVFORECAST__SOLCAST |
PVForecastSolcastCommonSettings |
rw |
required |
Solcast provider settings |
| vrm | EOS_PVFORECAST__VRM |
PVForecastVrmCommonSettings |
rw |
required |
Victron Remote Management (VRM) provider settings |
| ::: |
Example Input
{
"pvforecast": {
"provider": "PVForecastAkkudoktor",
"pvforecastimport": {
"import_file_path": null,
"import_json": null
},
"vrm": {
"token": "your-token",
"site_id": 12345
},
"homeassistant": {
"entity_id": "sensor.pv_forecast",
"attribute": "forecast",
"datetime_key": "datetime",
"value_key": "watts",
"value_unit": "W",
"base_url": null,
"token": null
},
"pvlib": {},
"pvnode": {
"api_key": "",
"site_id": null,
"forecast_days": 2
},
"forecastsolar": {
"api_key": null
},
"solcast": {
"api_key": "",
"site_id": ""
},
"planes": [
{
"surface_tilt": 10.0,
"surface_azimuth": 180.0,
"userhorizon": [
10.0,
20.0,
30.0
],
"peakpower": 5.0,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14.0,
"trackingtype": 0,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 6000,
"modules_per_string": 20,
"strings_per_inverter": 2
},
{
"surface_tilt": 20.0,
"surface_azimuth": 90.0,
"userhorizon": [
5.0,
15.0,
25.0
],
"peakpower": 3.5,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14.0,
"trackingtype": 1,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 4000,
"modules_per_string": 20,
"strings_per_inverter": 2
}
],
"max_planes": 1
}
}
Example Output
{
"pvforecast": {
"provider": "PVForecastAkkudoktor",
"pvforecastimport": {
"import_file_path": null,
"import_json": null
},
"vrm": {
"token": "your-token",
"site_id": 12345
},
"homeassistant": {
"entity_id": "sensor.pv_forecast",
"attribute": "forecast",
"datetime_key": "datetime",
"value_key": "watts",
"value_unit": "W",
"base_url": null,
"token": null
},
"pvlib": {},
"pvnode": {
"api_key": "",
"site_id": null,
"forecast_days": 2
},
"forecastsolar": {
"api_key": null
},
"solcast": {
"api_key": "",
"site_id": ""
},
"planes": [
{
"surface_tilt": 10.0,
"surface_azimuth": 180.0,
"userhorizon": [
10.0,
20.0,
30.0
],
"peakpower": 5.0,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14.0,
"trackingtype": 0,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 6000,
"modules_per_string": 20,
"strings_per_inverter": 2
},
{
"surface_tilt": 20.0,
"surface_azimuth": 90.0,
"userhorizon": [
5.0,
15.0,
25.0
],
"peakpower": 3.5,
"pvtechchoice": "crystSi",
"mountingplace": "free",
"loss": 14.0,
"trackingtype": 1,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 4000,
"modules_per_string": 20,
"strings_per_inverter": 2
}
],
"max_planes": 1,
"providers": [
"PVForecastAkkudoktor",
"PVForecastForecastSolar",
"PVForecastImport",
"PVForecastPVLib",
"PVForecastPVNode",
"PVForecastSolcast",
"PVForecastVrm"
],
"planes_peakpower": [
5.0,
3.5
],
"planes_azimuth": [
180.0,
90.0
],
"planes_tilt": [
10.0,
20.0
],
"planes_userhorizon": [
[
10.0,
20.0,
30.0
],
[
5.0,
15.0,
25.0
]
],
"planes_inverter_paco": [
6000.0,
4000.0
]
}
}
Common settings for PV forecast VRM API
:::{table} pvforecast::vrm :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| site_id | int |
rw |
12345 |
VRM-Installation-ID |
| token | str |
rw |
your-token |
Access token for connecting to the Victron Remote Management (VRM) API |
| ::: |
Example Input/Output
{
"pvforecast": {
"vrm": {
"token": "your-token",
"site_id": 12345
}
}
}
Common settings for pvforecast data from a Home Assistant entity
:::{table} pvforecast::homeassistant :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| entity_id | str |
rw |
sensor.pv_forecast |
Home Assistant entity providing the PV forecast. |
| attribute | str |
rw |
forecast |
Entity attribute holding the forecast list. |
| datetime_key | str |
rw |
datetime |
Key for the timestamp in each forecast entry. |
| value_key | str |
rw |
watts |
Key for the AC power value in each forecast entry. |
| value_unit | Literal['W', 'kW'] |
rw |
W |
Unit of the forecast value. Converted to W internally. |
| base_url | `str | None` | rw |
None |
| token | `str | None` | rw |
None |
| ::: |
Example Input/Output
{
"pvforecast": {
"homeassistant": {
"entity_id": "sensor.pv_forecast",
"attribute": "forecast",
"datetime_key": "datetime",
"value_key": "watts",
"value_unit": "W",
"base_url": null,
"token": null
}
}
}
Common settings for the Solcast PV forecast provider
:::{table} pvforecast::solcast :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| api_key | str |
rw |
`` | Solcast API key (Bearer auth). Required. |
| site_id | str |
rw |
`` | Solcast rooftop site (resource) id. Required. |
| ::: |
Example Input/Output
{
"pvforecast": {
"solcast": {
"api_key": "your-solcast-key",
"site_id": "abcd-1234-efgh-5678"
}
}
}
Common settings for the pvnode.com PV forecast provider
:::{table} pvforecast::pvnode :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| api_key | str |
rw |
`` | pvnode.com API key (Bearer auth). Required. |
| forecast_days | int |
rw |
2 |
Forecast horizon in days (1-7, capped by the pvnode plan). |
| site_id | `str | None` | rw |
None |
| ::: |
Example Input/Output
{
"pvforecast": {
"pvnode": {
"api_key": "pvn_live_xxxxxxxxxxxxxxxx",
"site_id": "abcd-1234",
"forecast_days": 2
}
}
}
Common settings for pvforecast data calculation with PVLib
:::{table} pvforecast::pvlib :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| ::: |
Example Input/Output
{
"pvforecast": {
"pvlib": {}
}
}
Common settings for pvforecast data import from file or JSON string
:::{table} pvforecast::pvforecastimport :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| import_file_path | `str | pathlib.Path | None` | rw |
| import_json | `str | None` | rw |
None |
| ::: |
Example Input/Output
{
"pvforecast": {
"pvforecastimport": {
"import_file_path": null,
"import_json": "{\"pvforecast_ac_power\": [0, 8.05, 352.91]}"
}
}
}
PV Forecast Plane Configuration
:::{table} pvforecast::planes::list :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| albedo | `float | None` | rw |
0.2 |
| inverter_model | `str | None` | rw |
None |
| inverter_paco | `int | None` | rw |
None |
| loss | `float | None` | rw |
14.0 |
| module_model | `str | None` | rw |
None |
| modules_per_string | `int | None` | rw |
None |
| mountingplace | `str | None` | rw |
building |
| optimal_surface_tilt | `bool | None` | rw |
False |
| optimalangles | `bool | None` | rw |
False |
| peakpower | `float | None` | rw |
None |
| pvtechchoice | `str | None` | rw |
crystSi |
| strings_per_inverter | `int | None` | rw |
None |
| surface_azimuth | `float | None` | rw |
180.0 |
| surface_tilt | `float | None` | rw |
30.0 |
| trackingtype | `int | None` | rw |
None |
| userhorizon | `List[float] | None` | rw |
None |
| ::: |
Example Input/Output
{
"pvforecast": {
"planes": [
{
"surface_tilt": 10.0,
"surface_azimuth": 180.0,
"userhorizon": [
10.0,
20.0,
30.0
],
"peakpower": 5.0,
"pvtechchoice": "crystSi",
"mountingplace": "building",
"loss": 14.0,
"trackingtype": 0,
"optimal_surface_tilt": false,
"optimalangles": false,
"albedo": null,
"module_model": null,
"inverter_model": null,
"inverter_paco": 6000,
"modules_per_string": 20,
"strings_per_inverter": 2
}
]
}
}
Common settings for the Forecast.Solar PV forecast provider
:::{table} pvforecast::forecastsolar :widths: 10 10 5 5 30 :align: left
| Name | Type | Read-Only | Default | Description |
|---|---|---|---|---|
| api_key | `str | None` | rw |
None |
| ::: |
Example Input/Output
{
"pvforecast": {
"forecastsolar": {
"api_key": null
}
}
}