From edf42bab25ae2e3681f3d0e774b7eebdaaa87ce3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 21 Jul 2021 15:04:30 -0700 Subject: [PATCH] Migrate forecast solar to v2 (#53259) --- .../components/forecast_solar/const.py | 18 +++++++++ .../components/forecast_solar/manifest.json | 2 +- .../components/forecast_solar/models.py | 4 ++ .../components/forecast_solar/sensor.py | 8 +++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/forecast_solar/conftest.py | 39 ++++++++++++------- .../components/forecast_solar/test_sensor.py | 16 ++++---- 8 files changed, 64 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/forecast_solar/const.py b/homeassistant/components/forecast_solar/const.py index 12aa1ee5362..7372ac5954d 100644 --- a/homeassistant/components/forecast_solar/const.py +++ b/homeassistant/components/forecast_solar/const.py @@ -1,6 +1,7 @@ """Constants for the Forecast.Solar integration.""" from __future__ import annotations +from datetime import timedelta from typing import Final from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT @@ -27,12 +28,14 @@ SENSORS: list[ForecastSolarSensor] = [ ForecastSolarSensor( key="energy_production_today", name="Estimated Energy Production - Today", + state=lambda estimate: estimate.energy_production_today / 1000, device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=ENERGY_KILO_WATT_HOUR, ), ForecastSolarSensor( key="energy_production_tomorrow", name="Estimated Energy Production - Tomorrow", + state=lambda estimate: estimate.energy_production_tomorrow / 1000, device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=ENERGY_KILO_WATT_HOUR, ), @@ -50,11 +53,16 @@ SENSORS: list[ForecastSolarSensor] = [ key="power_production_now", name="Estimated Power Production - Now", device_class=DEVICE_CLASS_POWER, + state=lambda estimate: estimate.power_production_now / 1000, state_class=STATE_CLASS_MEASUREMENT, unit_of_measurement=POWER_WATT, ), ForecastSolarSensor( key="power_production_next_hour", + state=lambda estimate: estimate.power_production_at_time( + estimate.now() + timedelta(hours=1) + ) + / 1000, name="Estimated Power Production - Next Hour", device_class=DEVICE_CLASS_POWER, entity_registry_enabled_default=False, @@ -62,6 +70,10 @@ SENSORS: list[ForecastSolarSensor] = [ ), ForecastSolarSensor( key="power_production_next_12hours", + state=lambda estimate: estimate.power_production_at_time( + estimate.now() + timedelta(hours=12) + ) + / 1000, name="Estimated Power Production - Next 12 Hours", device_class=DEVICE_CLASS_POWER, entity_registry_enabled_default=False, @@ -69,6 +81,10 @@ SENSORS: list[ForecastSolarSensor] = [ ), ForecastSolarSensor( key="power_production_next_24hours", + state=lambda estimate: estimate.power_production_at_time( + estimate.now() + timedelta(hours=24) + ) + / 1000, name="Estimated Power Production - Next 24 Hours", device_class=DEVICE_CLASS_POWER, entity_registry_enabled_default=False, @@ -77,11 +93,13 @@ SENSORS: list[ForecastSolarSensor] = [ ForecastSolarSensor( key="energy_current_hour", name="Estimated Energy Production - This Hour", + state=lambda estimate: estimate.energy_current_hour / 1000, device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=ENERGY_KILO_WATT_HOUR, ), ForecastSolarSensor( key="energy_next_hour", + state=lambda estimate: estimate.sum_energy_production(1) / 1000, name="Estimated Energy Production - Next Hour", device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=ENERGY_KILO_WATT_HOUR, diff --git a/homeassistant/components/forecast_solar/manifest.json b/homeassistant/components/forecast_solar/manifest.json index c17e8bd51f8..2b57eed84ac 100644 --- a/homeassistant/components/forecast_solar/manifest.json +++ b/homeassistant/components/forecast_solar/manifest.json @@ -3,7 +3,7 @@ "name": "Forecast.Solar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/forecast_solar", - "requirements": ["forecast_solar==1.3.1"], + "requirements": ["forecast_solar==2.0.0"], "codeowners": ["@klaasnicolaas", "@frenck"], "quality_scale": "platinum", "iot_class": "cloud_polling" diff --git a/homeassistant/components/forecast_solar/models.py b/homeassistant/components/forecast_solar/models.py index d01f17fc975..a10f52ebcd3 100644 --- a/homeassistant/components/forecast_solar/models.py +++ b/homeassistant/components/forecast_solar/models.py @@ -2,6 +2,9 @@ from __future__ import annotations from dataclasses import dataclass +from typing import Any, Callable + +from forecast_solar.models import Estimate @dataclass @@ -13,5 +16,6 @@ class ForecastSolarSensor: device_class: str | None = None entity_registry_enabled_default: bool = True + state: Callable[[Estimate], Any] | None = None state_class: str | None = None unit_of_measurement: str | None = None diff --git a/homeassistant/components/forecast_solar/sensor.py b/homeassistant/components/forecast_solar/sensor.py index b32f1f341be..e73b2105b8e 100644 --- a/homeassistant/components/forecast_solar/sensor.py +++ b/homeassistant/components/forecast_solar/sensor.py @@ -66,7 +66,13 @@ class ForecastSolarSensorEntity(CoordinatorEntity, SensorEntity): @property def state(self) -> StateType: """Return the state of the sensor.""" - state: StateType | datetime = getattr(self.coordinator.data, self._sensor.key) + if self._sensor.state is None: + state: StateType | datetime = getattr( + self.coordinator.data, self._sensor.key + ) + else: + state = self._sensor.state(self.coordinator.data) + if isinstance(state, datetime): return state.isoformat() return state diff --git a/requirements_all.txt b/requirements_all.txt index 50ca781cfc8..927a685ded4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -630,7 +630,7 @@ fnvhash==0.1.0 foobot_async==1.0.0 # homeassistant.components.forecast_solar -forecast_solar==1.3.1 +forecast_solar==2.0.0 # homeassistant.components.fortios fortiosapi==1.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 88b251137ed..101e0bbe4b0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -348,7 +348,7 @@ fnvhash==0.1.0 foobot_async==1.0.0 # homeassistant.components.forecast_solar -forecast_solar==1.3.1 +forecast_solar==2.0.0 # homeassistant.components.freebox freebox-api==0.0.10 diff --git a/tests/components/forecast_solar/conftest.py b/tests/components/forecast_solar/conftest.py index c2b5fc08181..88f3bf9d4a4 100644 --- a/tests/components/forecast_solar/conftest.py +++ b/tests/components/forecast_solar/conftest.py @@ -1,9 +1,10 @@ """Fixtures for Forecast.Solar integration tests.""" -import datetime +from datetime import datetime, timedelta from typing import Generator from unittest.mock import MagicMock, patch +from forecast_solar import models import pytest from homeassistant.components.forecast_solar.const import ( @@ -16,6 +17,7 @@ from homeassistant.components.forecast_solar.const import ( from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry @@ -54,24 +56,31 @@ def mock_forecast_solar() -> Generator[None, MagicMock, None]: "homeassistant.components.forecast_solar.ForecastSolar", autospec=True ) as forecast_solar_mock: forecast_solar = forecast_solar_mock.return_value + now = datetime(2021, 6, 27, 6, 0, tzinfo=dt_util.DEFAULT_TIME_ZONE) - estimate = MagicMock() + estimate = MagicMock(spec_set=models.Estimate) + estimate.now.return_value = now estimate.timezone = "Europe/Amsterdam" - estimate.energy_production_today = 100 - estimate.energy_production_tomorrow = 200 - estimate.power_production_now = 300 - estimate.power_highest_peak_time_today = datetime.datetime( - 2021, 6, 27, 13, 0, tzinfo=datetime.timezone.utc + estimate.energy_production_today = 100000 + estimate.energy_production_tomorrow = 200000 + estimate.power_production_now = 300000 + estimate.power_highest_peak_time_today = datetime( + 2021, 6, 27, 13, 0, tzinfo=dt_util.DEFAULT_TIME_ZONE ) - estimate.power_highest_peak_time_tomorrow = datetime.datetime( - 2021, 6, 27, 14, 0, tzinfo=datetime.timezone.utc + estimate.power_highest_peak_time_tomorrow = datetime( + 2021, 6, 27, 14, 0, tzinfo=dt_util.DEFAULT_TIME_ZONE ) - estimate.power_production_next_hour = 400 - estimate.power_production_next_6hours = 500 - estimate.power_production_next_12hours = 600 - estimate.power_production_next_24hours = 700 - estimate.energy_current_hour = 800 - estimate.energy_next_hour = 900 + estimate.energy_current_hour = 800000 + + estimate.power_production_at_time.side_effect = { + now + timedelta(hours=1): 400000, + now + timedelta(hours=12): 600000, + now + timedelta(hours=24): 700000, + }.get + + estimate.sum_energy_production.side_effect = { + 1: 900000, + }.get forecast_solar.estimate.return_value = estimate yield forecast_solar diff --git a/tests/components/forecast_solar/test_sensor.py b/tests/components/forecast_solar/test_sensor.py index 31c367678c1..8b8c1cc933e 100644 --- a/tests/components/forecast_solar/test_sensor.py +++ b/tests/components/forecast_solar/test_sensor.py @@ -40,7 +40,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_energy_production_today" - assert state.state == "100" + assert state.state == "100.0" assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - Today" @@ -55,7 +55,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_energy_production_tomorrow" - assert state.state == "200" + assert state.state == "200.0" assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - Tomorrow" @@ -96,7 +96,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_power_production_now" - assert state.state == "300" + assert state.state == "300.0" assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Power Production - Now" ) @@ -110,7 +110,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_energy_current_hour" - assert state.state == "800" + assert state.state == "800.0" assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - This Hour" @@ -125,7 +125,7 @@ async def test_sensors( assert entry assert state assert entry.unique_id == f"{entry_id}_energy_next_hour" - assert state.state == "900" + assert state.state == "900.0" assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Estimated Energy Production - Next Hour" @@ -175,17 +175,17 @@ async def test_disabled_by_default( ( "power_production_next_12hours", "Estimated Power Production - Next 12 Hours", - "600", + "600.0", ), ( "power_production_next_24hours", "Estimated Power Production - Next 24 Hours", - "700", + "700.0", ), ( "power_production_next_hour", "Estimated Power Production - Next Hour", - "400", + "400.0", ), ], )