diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 77d04aa8..81702319 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,6 +37,9 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Install dependencies + run: sudo apt install libffi-dev libncurses5-dev zlib1g zlib1g-dev libssl-dev libreadline-dev libbz2-dev libsqlite3-dev + shell: bash - name: asdf_install uses: asdf-vm/actions/install@v3 - name: Install Python modules @@ -55,6 +58,9 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Install dependencies + run: sudo apt install libffi-dev libncurses5-dev zlib1g zlib1g-dev libssl-dev libreadline-dev libbz2-dev libsqlite3-dev + shell: bash - name: asdf_install uses: asdf-vm/actions/install@v3 - name: Install Python modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f615d12..00b933fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,45 +1,14 @@ -# [13.3.0-beta.4](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/compare/v13.3.0-beta.3...v13.3.0-beta.4) (2024-12-15) +# [13.3.0](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/compare/v13.2.1...v13.3.0) (2024-12-16) ### Bug Fixes * Fixed issue with downloading diagnostics breaks loading of data (15 minutes dev time) ([4589a6a](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/4589a6ad0565e4289d3de2d89b525e6110e0605b)) -* Fixed zone mode interpretations in zone climate control and exposed target temperature in boost service (30 minutes dev time) ([7eb6755](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/7eb67553119773616097b6cc9360e03ed793fb46)) +* Fixed issue with gas tariff comparison not persisting configured calorific value (1 hour dev time) ([b16cd1d](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/b16cd1dbeeac4224d62c4cdf05b0c845a3ac71dd)) ### Features -* Updated heat pump zones and sensors to not be included if not enabled (5 minutes dev time) ([f762c4d](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/f762c4d214ba635abc8447536a75e4e1402ffea1)) - -# [13.3.0-beta.3](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/compare/v13.3.0-beta.2...v13.3.0-beta.3) (2024-12-14) - - -### Features - -* Created heat pump boosting action to support custom end time (40 minutes dev time) ([bc047f8](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/bc047f866a2047cd3d2cc059cce362d2f56a2ada)) -* Fixed heat pump service calls (30 minutes dev time) ([ff88cd4](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/ff88cd419b626daccb10d236a772b27931a4f08a)) -* Updated heat pump zone min/max temperatures based on zone type (5 minutes dev time) ([2ce5fe6](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/2ce5fe64456434552d581aaaa311030fa0cec8f4)) - -# [13.3.0-beta.2](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/compare/v13.3.0-beta.1...v13.3.0-beta.2) (2024-12-11) - - -### Bug Fixes - -* Fixed heat pump retrieved at parsing (15 minutes dev time) ([cd47109](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/cd471094811f120a56c6fa4a7498d532ad32a3bc)) -* Fixed heat pump retrieved at parsing (15 minutes dev time) ([#1118](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues/1118)) ([aeac537](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/aeac537b1624e1b6bbcb4e8d9089ae44d9ee3f09)) - -# [13.3.0-beta.1](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/compare/v13.2.1...v13.3.0-beta.1) (2024-12-10) - - -### Features - -* Added ability to change zone modes and target temperatures (1 hour dev time) ([56aef85](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/56aef85031be5e4b9a5bb5621b042a5616304fc7)) -* Added climate control for heat pump zone (3 hours dev time) ([5e72161](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/5e72161a180119110be950322dfafeab6b755b27)) -* Added endpoint to get heat pump configuration and status ([1826925](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/1826925513a288bc1b6a4772c305deb7cf3dc6d6)) -* Added heat pump humidity sensor (1 hour dev time) ([b479c89](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/b479c8999256a70273077c8c0b5f001decbdbf84)) -* Added heat pump information to device diagnostics (30 minutes dev time) ([b8468bc](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/b8468bce31a4dff419b3bfe21392911b88f05f0d)) -* Added temperature sensors from heat pumps (2.5 hours dev time) ([c79e5b8](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/c79e5b8bdb50ee974027e2f5d9a7a16360634621)) -* Updated account to include heat pump ids (5 minutes dev time) ([5c411c8](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/5c411c8efafc41ca323ae4d8415f03cb213da282)) * Updated cost tracker entities to be associated with device of tracked entity, if one exists (1 hour dev time) ([9fe69a0](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/9fe69a081b08f1bec3ecfb4ea3b4ed552cc665ef)) ## [13.2.1](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/compare/v13.2.0...v13.2.1) (2024-12-01) diff --git a/custom_components/octopus_energy/__init__.py b/custom_components/octopus_energy/__init__.py index c4946f7f..bb0d6beb 100644 --- a/custom_components/octopus_energy/__init__.py +++ b/custom_components/octopus_energy/__init__.py @@ -161,7 +161,12 @@ async def async_close_connection(_) -> None: # the correct references (e.g. rate coordinators) child_entries = hass.config_entries.async_entries(DOMAIN) for child_entry in child_entries: - if child_entry.data[CONFIG_KIND] != CONFIG_KIND_ACCOUNT and child_entry.data[CONFIG_ACCOUNT_ID] == account_id: + child_entry_config = dict(child_entry.data) + + if child_entry.options: + child_entry_config.update(child_entry.options) + + if child_entry_config[CONFIG_KIND] != CONFIG_KIND_ACCOUNT and child_entry_config[CONFIG_ACCOUNT_ID] == account_id: await hass.config_entries.async_reload(child_entry.entry_id) elif config[CONFIG_KIND] == CONFIG_KIND_TARGET_RATE: @@ -472,6 +477,21 @@ async def options_update_listener(hass, entry): """Handle options update.""" await hass.config_entries.async_reload(entry.entry_id) + if entry.data[CONFIG_KIND] == CONFIG_KIND_ACCOUNT: + account_id = entry.data[CONFIG_ACCOUNT_ID] + + # If the main account has been reloaded, then reload all other entries to make sure they're referencing + # the correct references (e.g. rate coordinators) + child_entries = hass.config_entries.async_entries(DOMAIN) + for child_entry in child_entries: + child_entry_config = dict(child_entry.data) + + if child_entry.options: + child_entry_config.update(child_entry.options) + + if child_entry_config[CONFIG_KIND] != CONFIG_KIND_ACCOUNT and child_entry_config[CONFIG_ACCOUNT_ID] == account_id: + await hass.config_entries.async_reload(child_entry.entry_id) + async def async_unload_entry(hass, entry): """Unload a config entry.""" @@ -483,6 +503,9 @@ async def async_unload_entry(hass, entry): await _async_close_client(hass, account_id) hass.data[DOMAIN].pop(account_id) + elif entry.data[CONFIG_KIND] == CONFIG_KIND_TARIFF_COMPARISON: + unload_ok = await hass.config_entries.async_unload_platforms(entry, TARIFF_COMPARISON_PLATFORMS) + elif entry.data[CONFIG_KIND] == CONFIG_KIND_TARGET_RATE or entry.data[CONFIG_KIND] == CONFIG_KIND_ROLLING_TARGET_RATE: unload_ok = await hass.config_entries.async_unload_platforms(entry, TARGET_RATE_PLATFORMS) diff --git a/custom_components/octopus_energy/api_client_home_pro/__init__.py b/custom_components/octopus_energy/api_client_home_pro/__init__.py index de3c31b0..813fb42c 100644 --- a/custom_components/octopus_energy/api_client_home_pro/__init__.py +++ b/custom_components/octopus_energy/api_client_home_pro/__init__.py @@ -42,9 +42,10 @@ def _create_client_session(self): async def async_ping(self): try: client = self._create_client_session() - url = f'{self._base_url}/get_meter_info?meter_type=elec' + url = f'{self._base_url}:3000/get_meter_consumption' headers = { "Authorization": self._api_key } - async with client.get(url, headers=headers) as response: + data = { "meter_type": "elec" } + async with client.post(url, headers=headers, json=data) as response: response_body = await self.__async_read_response__(response, url) if (response_body is not None and "Status" in response_body): status: str = response_body["Status"] @@ -62,20 +63,23 @@ async def async_get_consumption(self, is_electricity: bool) -> list | None: try: client = self._create_client_session() meter_type = 'elec' if is_electricity else 'gas' - url = f'{self._base_url}/get_meter_consumption?meter_type={meter_type}' + url = f'{self._base_url}:3000/get_meter_consumption' headers = { "Authorization": self._api_key } - async with client.get(url, headers=headers) as response: + data = { "meter_type": meter_type } + async with client.post(url, headers=headers, json=data) as response: response_body = await self.__async_read_response__(response, url) - if (response_body is not None and "meter_consump" in response_body and "consum" in response_body["meter_consump"]): - data = response_body["meter_consump"]["consum"] - divisor = int(data["raw"]["divisor"], 16) - return [{ - "total_consumption": int(data["consumption"]) / divisor if divisor > 0 else None, - "demand": float(data["instdmand"]) if "instdmand" in data else None, - "start": datetime.fromtimestamp(int(response_body["meter_consump"]["time"]), timezone.utc), - "end": datetime.fromtimestamp(int(response_body["meter_consump"]["time"]), timezone.utc), - "is_kwh": data["unit"] == 0 - }] + if (response_body is not None and "meter_consump"): + meter_consump = json.loads(response_body["meter_consump"]) + if "consum" in meter_consump: + data = meter_consump["consum"] + divisor = int(data["raw"]["divisor"], 16) + return [{ + "total_consumption": int(data["consumption"]) / divisor if divisor > 0 else None, + "demand": float(data["instdmand"]) if "instdmand" in data else None, + "start": datetime.fromtimestamp(int(meter_consump["time"]), timezone.utc), + "end": datetime.fromtimestamp(int(meter_consump["time"]), timezone.utc), + "is_kwh": data["unit"] == 0 + }] return None @@ -88,7 +92,7 @@ async def async_set_screen(self, value: str, animation_type: str, type: str, bri try: client = self._create_client_session() - url = f'{self._base_url}/screen' + url = f'{self._base_url}:8000/screen' headers = { "Authorization": self._api_key } payload = { # API doesn't support none or empty string as a valid value @@ -110,6 +114,7 @@ async def __async_read_response__(self, response, url): """Reads the response, logging any json errors""" text = await response.text() + _LOGGER.debug(f"response: {text}") if response.status >= 400: if response.status >= 500: diff --git a/custom_components/octopus_energy/config/main.py b/custom_components/octopus_energy/config/main.py index 6a7fc50e..51bd917b 100644 --- a/custom_components/octopus_energy/config/main.py +++ b/custom_components/octopus_energy/config/main.py @@ -37,6 +37,10 @@ async def async_migrate_main_config(version: int, data: {}): new_data[CONFIG_ACCOUNT_ID] = new_data[CONFIG_MAIN_OLD_ACCOUNT_ID] del new_data[CONFIG_MAIN_OLD_ACCOUNT_ID] + if (version <= 5): + if CONFIG_MAIN_HOME_PRO_ADDRESS in new_data: + new_data[CONFIG_MAIN_HOME_PRO_ADDRESS] = f"{new_data[CONFIG_MAIN_HOME_PRO_ADDRESS]}".replace(":8000", "") + return new_data def merge_main_config(data: dict, options: dict, updated_config: dict = None): diff --git a/custom_components/octopus_energy/const.py b/custom_components/octopus_energy/const.py index 4b7ac463..e216b6e9 100644 --- a/custom_components/octopus_energy/const.py +++ b/custom_components/octopus_energy/const.py @@ -2,7 +2,7 @@ import homeassistant.helpers.config_validation as cv DOMAIN = "octopus_energy" -INTEGRATION_VERSION = "13.3.0-beta.4" +INTEGRATION_VERSION = "13.3.0" REFRESH_RATE_IN_MINUTES_ACCOUNT = 60 REFRESH_RATE_IN_MINUTES_INTELLIGENT = 3 @@ -18,7 +18,7 @@ REFRESH_RATE_IN_MINUTES_HOME_PRO_CONSUMPTION = 0.17 REFRESH_RATE_IN_MINUTES_HEAT_PUMP = 1 -CONFIG_VERSION = 4 +CONFIG_VERSION = 5 CONFIG_KIND = "kind" CONFIG_KIND_ACCOUNT = "account" diff --git a/custom_components/octopus_energy/heat_pump/base.py b/custom_components/octopus_energy/heat_pump/base.py index a09146e1..5d221fb1 100644 --- a/custom_components/octopus_energy/heat_pump/base.py +++ b/custom_components/octopus_energy/heat_pump/base.py @@ -24,6 +24,7 @@ def __init__(self, hass: HomeAssistant, heat_pump_id: str, heat_pump: HeatPump, identifiers={(DOMAIN, f"heat_pump_{heat_pump.serialNumber}")}, name=f"Heat Pump ({heat_pump.serialNumber})", connections=set(), + manufacturer="Octopus" if heat_pump.model is not None and "cosy" in heat_pump.model.lower() else None, model=heat_pump.model, hw_version=heat_pump.hardwareVersion ) @@ -48,6 +49,8 @@ def __init__(self, hass: HomeAssistant, heat_pump_id: str, heat_pump: HeatPump, identifiers={(DOMAIN, f"heat_pump_sensor_{heat_pump.serialNumber}_{sensor.code}")}, name=f"Heat Pump Sensor ({sensor.code})", connections=set(), + manufacturer="Octopus" if heat_pump.model is not None and "cosy" in heat_pump.model.lower() else None, + model=heat_pump.model, sw_version=sensor.firmwareVersion, hw_version=sensor.type ) \ No newline at end of file diff --git a/custom_components/octopus_energy/heat_pump/zone.py b/custom_components/octopus_energy/heat_pump/zone.py index 0e147e78..55152e1c 100644 --- a/custom_components/octopus_energy/heat_pump/zone.py +++ b/custom_components/octopus_energy/heat_pump/zone.py @@ -58,6 +58,7 @@ def __init__(self, hass: HomeAssistant, coordinator, client: OctopusEnergyApiCli self._account_id = account_id self._client = client self._is_mocked = is_mocked + self._end_timestamp = None if zone.configuration.zoneType == "HEAT": self._attr_min_temp = 7 @@ -66,12 +67,6 @@ def __init__(self, hass: HomeAssistant, coordinator, client: OctopusEnergyApiCli self._attr_min_temp = 40 self._attr_max_temp = 60 - # self._attributes = { - # "type": zone.configuration.zoneType, - # "calling_for_heat": zone.configuration.callForHeat, - # "is_enabled": zone.configuration.enabled - # } - # Pass coordinator to base class CoordinatorEntity.__init__(self, coordinator) BaseOctopusEnergyHeatPumpSensor.__init__(self, hass, heat_pump_id, heat_pump, "climate") @@ -93,48 +88,42 @@ def name(self): def _handle_coordinator_update(self) -> None: """Retrieve the previous rate.""" - # self._attributes = { - # "type": self._zone.configuration.zoneType, - # "calling_for_heat": self._zone.configuration.callForHeat, - # "is_enabled": self._zone.configuration.enabled - # } - - # Find the previous rate. We only need to do this every half an hour current = now() result: HeatPumpCoordinatorResult = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None if (result is not None and result.data is not None and - result.data.octoHeatPumpControllerStatus is not None and - result.data.octoHeatPumpControllerStatus.zones): + result.data.octoHeatPumpControllerConfiguration is not None and + result.data.octoHeatPumpControllerConfiguration.zones and + (self._last_updated is None or self._last_updated < result.last_retrieved)): _LOGGER.debug(f"Updating OctopusEnergyHeatPumpZone for '{self._heat_pump_id}/{self._zone.configuration.code}'") - zones: List[Zone] = result.data.octoHeatPumpControllerStatus.zones + zones: List[ConfigurationZone] = result.data.octoHeatPumpControllerConfiguration.zones for zone in zones: - if zone.zone == self._zone.configuration.code and zone.telemetry is not None: + if zone.configuration is not None and zone.configuration.code == self._zone.configuration.code and zone.configuration.currentOperation is not None: - if zone.telemetry.mode == "ON": + if zone.configuration.currentOperation.mode == "ON": self._attr_hvac_mode = HVACMode.HEAT self._attr_preset_mode = PRESET_NONE - elif zone.telemetry.mode == "OFF": + elif zone.configuration.currentOperation.mode == "OFF": self._attr_hvac_mode = HVACMode.OFF self._attr_preset_mode = PRESET_NONE - elif zone.telemetry.mode == "AUTO": + elif zone.configuration.currentOperation.mode == "AUTO": self._attr_hvac_mode = HVACMode.AUTO self._attr_preset_mode = PRESET_NONE - elif zone.telemetry.mode == "BOOST": + elif zone.configuration.currentOperation.mode == "BOOST": + self._attr_hvac_mode = HVACMode.AUTO self._attr_preset_mode = PRESET_BOOST else: - raise Exception(f"Unexpected heat pump mode detected: {zone.telemetry.mode}") + raise Exception(f"Unexpected heat pump mode detected: {zone.configuration.currentOperation.mode}") - self._attr_target_temperature = zone.telemetry.setpointInCelsius + self._attr_target_temperature = zone.configuration.currentOperation.setpointInCelsius + self._end_timestamp = datetime.fromisoformat(zone.configuration.currentOperation.end) if zone.configuration.currentOperation.end is not None else None if (result.data.octoHeatPumpControllerStatus.sensors and self._zone.configuration.primarySensor): sensors: List[Sensor] = result.data.octoHeatPumpControllerStatus.sensors for sensor in sensors: if sensor.code == self._zone.configuration.primarySensor and sensor.telemetry is not None: self._attr_current_temperature = sensor.telemetry.temperatureInCelsius - - self._attributes["retrieved_at"] = datetime.fromisoformat(zone.telemetry.retrievedAt) self._last_updated = current @@ -145,6 +134,7 @@ async def async_set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" try: self._attr_hvac_mode = hvac_mode + self._attr_preset_mode = PRESET_NONE zone_mode = self.get_zone_mode() await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, zone_mode, self._attr_target_temperature) except Exception as e: @@ -158,6 +148,8 @@ async def async_set_hvac_mode(self, hvac_mode): async def async_turn_on(self): """Turn the entity on.""" try: + self._attr_hvac_mode = HVACMode.HEAT + self._attr_preset_mode = PRESET_NONE await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, 'ON', self._attr_target_temperature) except Exception as e: if self._is_mocked: @@ -165,12 +157,13 @@ async def async_turn_on(self): else: raise - self._attr_hvac_mode = HVACMode.HEAT self.async_write_ha_state() async def async_turn_off(self): """Turn the entity off.""" try: + self._attr_hvac_mode = HVACMode.OFF + self._attr_preset_mode = PRESET_NONE await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, 'OFF', None) except Exception as e: if self._is_mocked: @@ -178,7 +171,6 @@ async def async_turn_off(self): else: raise - self._attr_hvac_mode = HVACMode.OFF self.async_write_ha_state() async def async_set_preset_mode(self, preset_mode): @@ -187,9 +179,9 @@ async def async_set_preset_mode(self, preset_mode): self._attr_preset_mode = preset_mode if self._attr_preset_mode == PRESET_BOOST: - current = utcnow() - current += timedelta(hours=1) - await self._client.async_boost_heat_pump_zone(self._account_id, self._heat_pump_id, self._zone.configuration.code, current, self._attr_target_temperature) + self._end_timestamp = utcnow() + self._end_timestamp += timedelta(hours=1) + await self._client.async_boost_heat_pump_zone(self._account_id, self._heat_pump_id, self._zone.configuration.code, self._end_timestamp, self._attr_target_temperature) else: zone_mode = self.get_zone_mode() await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, zone_mode, self._attr_target_temperature) @@ -203,32 +195,22 @@ async def async_set_preset_mode(self, preset_mode): async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" - temperature = kwargs[ATTR_TEMPERATURE] try: - zone_mode = self.get_zone_mode() - await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, zone_mode, temperature) + self._attr_target_temperature = kwargs[ATTR_TEMPERATURE] + if self._attr_preset_mode == PRESET_BOOST: + await self._client.async_boost_heat_pump_zone(self._account_id, self._heat_pump_id, self._zone.configuration.code, self._end_timestamp, self._attr_target_temperature) + else: + zone_mode = self.get_zone_mode() + await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, zone_mode, self._attr_target_temperature) except Exception as e: if self._is_mocked: _LOGGER.warning(f'Suppress async_set_temperature error due to mocking mode: {e}') else: raise - self._attr_target_temperature = temperature self.async_write_ha_state() - def get_zone_mode(self): - if self._attr_preset_mode == PRESET_BOOST: - return "BOOST" - elif self._attr_hvac_mode == HVACMode.HEAT: - return "ON" - elif self._attr_hvac_mode == HVACMode.OFF: - return "OFF" - elif self._attr_hvac_mode == HVACMode.AUTO: - return "AUTO" - else: - raise Exception(f"Unexpected heat pump mode detected: {self._attr_hvac_mode}/{self._attr_preset_mode}") - @callback async def async_boost_heat_pump_zone(self, hours: int, minutes: int, target_temperature: float | None = None): """Boost the heat pump zone""" @@ -244,9 +226,21 @@ async def async_boost_heat_pump_zone(self, hours: int, minutes: int, target_temp }, ) - current = utcnow() - current += timedelta(hours=hours, minutes=minutes) - await self._client.async_boost_heat_pump_zone(self._account_id, self._heat_pump_id, self._zone.configuration.code, current, target_temperature if target_temperature is not None else self._attr_target_temperature) - + self._end_timestamp = utcnow() + self._end_timestamp += timedelta(hours=hours, minutes=minutes) self._attr_preset_mode = PRESET_BOOST - self.async_write_ha_state() \ No newline at end of file + await self._client.async_boost_heat_pump_zone(self._account_id, self._heat_pump_id, self._zone.configuration.code, self._end_timestamp, target_temperature if target_temperature is not None else self._attr_target_temperature) + + self.async_write_ha_state() + + def get_zone_mode(self): + if self._attr_preset_mode == PRESET_BOOST: + return "BOOST" + elif self._attr_hvac_mode == HVACMode.HEAT: + return "ON" + elif self._attr_hvac_mode == HVACMode.OFF: + return "OFF" + elif self._attr_hvac_mode == HVACMode.AUTO: + return "AUTO" + else: + raise Exception(f"Unexpected heat pump mode detected: {self._attr_hvac_mode}/{self._attr_preset_mode}") \ No newline at end of file diff --git a/custom_components/octopus_energy/manifest.json b/custom_components/octopus_energy/manifest.json index 756092e9..38b74745 100644 --- a/custom_components/octopus_energy/manifest.json +++ b/custom_components/octopus_energy/manifest.json @@ -14,7 +14,7 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues", "ssdp": [], - "version": "13.3.0-beta.4", + "version": "13.3.0", "zeroconf": [], "requirements": [ "pydantic" diff --git a/custom_components/octopus_energy/sensor.py b/custom_components/octopus_energy/sensor.py index 1ecdbd26..ac27fa0d 100644 --- a/custom_components/octopus_energy/sensor.py +++ b/custom_components/octopus_energy/sensor.py @@ -633,8 +633,13 @@ async def async_setup_tariff_comparison_sensors(hass: HomeAssistant, entry, conf calorific_value = DEFAULT_CALORIFIC_VALUE config_entries = hass.config_entries.async_entries(DOMAIN) for entry in config_entries: - if entry.data[CONFIG_KIND] == CONFIG_KIND_ACCOUNT and entry.data[CONFIG_ACCOUNT_ID] == account_id and CONFIG_MAIN_CALORIFIC_VALUE in config: - calorific_value = config[CONFIG_MAIN_CALORIFIC_VALUE] + config_entry_data = dict(entry.data) + + if entry.options: + config_entry_data.update(entry.options) + + if config_entry_data[CONFIG_KIND] == CONFIG_KIND_ACCOUNT and config_entry_data[CONFIG_ACCOUNT_ID] == account_id and CONFIG_MAIN_CALORIFIC_VALUE in config_entry_data: + calorific_value = config_entry_data[CONFIG_MAIN_CALORIFIC_VALUE] now = utcnow() for point in account_info["electricity_meter_points"]: diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 917288cd..40cfc6bb 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -1,6 +1,9 @@ +import logging import os from datetime import datetime, timedelta +logging.getLogger().setLevel(logging.DEBUG) + class TestContext: api_key: str account_id: str diff --git a/tests/integration/test_async_get_diagnostics.py b/tests/integration/test_async_get_diagnostics.py index 9bae3a31..29dda6b9 100644 --- a/tests/integration/test_async_get_diagnostics.py +++ b/tests/integration/test_async_get_diagnostics.py @@ -16,12 +16,16 @@ def assert_meter(meter, expected_serial_number: int): assert meter["device_id"] == "**REDACTED**" assert isinstance(meter["latest_consumption"], datetime) - assert meter["device"] is not None - assert isinstance(meter["device"]["total_consumption"], float) - assert isinstance(meter["device"]["consumption"], float) - assert "demand" in meter["device"] - assert isinstance(meter["device"]["start"], datetime) - assert isinstance(meter["device"]["end"], datetime) + if meter["device"] != "Not available": + if meter["device"]["total_consumption"] is not None: + assert isinstance(meter["device"]["total_consumption"], float) + + if meter["device"]["consumption"] is not None: + assert isinstance(meter["device"]["consumption"], float) + + assert "demand" in meter["device"] + assert isinstance(meter["device"]["start"], datetime) + assert isinstance(meter["device"]["end"], datetime) @pytest.mark.asyncio async def test_when_async_get_diagnostics_called_then_account_info_is_returned(): diff --git a/tests/local_integration/__init__.py b/tests/local_integration/__init__.py index 522cc8ef..89e2a852 100644 --- a/tests/local_integration/__init__.py +++ b/tests/local_integration/__init__.py @@ -1,5 +1,8 @@ +import logging import os +logging.getLogger().setLevel(logging.DEBUG) + class TestContext: def __init__(self, api_key: str, base_url: str): self.api_key = api_key diff --git a/tests/local_integration/api_client_home_pro/test_async_get_consumption.py b/tests/local_integration/api_client_home_pro/test_async_get_consumption.py index 45e52ceb..3baab51b 100644 --- a/tests/local_integration/api_client_home_pro/test_async_get_consumption.py +++ b/tests/local_integration/api_client_home_pro/test_async_get_consumption.py @@ -4,27 +4,6 @@ from custom_components.octopus_energy.api_client import AuthenticationException from custom_components.octopus_energy.api_client_home_pro import OctopusEnergyHomeProApiClient -@pytest.mark.asyncio -@pytest.mark.parametrize("is_electricity",[ - (True), - (False) -]) -async def test_when_get_consumption_is_called_and_api_key_is_invalid_then_exception_is_raised(is_electricity: bool): - # Arrange - context = get_test_context() - - client = OctopusEnergyHomeProApiClient(context.base_url, "invalid-api-key") - - # Act - exception_raised = False - try: - await client.async_get_consumption(is_electricity) - except AuthenticationException: - exception_raised = True - - # Assert - assert exception_raised == True - @pytest.mark.asyncio @pytest.mark.parametrize("is_electricity",[ (True), diff --git a/tests/local_integration/api_client_home_pro/test_async_ping.py b/tests/local_integration/api_client_home_pro/test_async_ping.py index 8467eac1..e212d97b 100644 --- a/tests/local_integration/api_client_home_pro/test_async_ping.py +++ b/tests/local_integration/api_client_home_pro/test_async_ping.py @@ -4,23 +4,6 @@ from custom_components.octopus_energy.api_client import AuthenticationException from custom_components.octopus_energy.api_client_home_pro import OctopusEnergyHomeProApiClient -@pytest.mark.asyncio -async def test_when_ping_is_called_and_api_key_is_invalid_then_exception_is_raised(): - # Arrange - context = get_test_context() - - client = OctopusEnergyHomeProApiClient(context.base_url, "invalid-api-key") - - # Act - exception_raised = False - try: - await client.async_ping() - except AuthenticationException: - exception_raised = True - - # Assert - assert exception_raised == True - @pytest.mark.asyncio async def test_when_ping_is_called_then_data_is_returned(): # Arrange