Skip to content

Commit

Permalink
fix: Fixed zone mode interpretations in zone climate control and expo…
Browse files Browse the repository at this point in the history
…sed target temperature in boost service (30 minutes dev time)
  • Loading branch information
BottlecapDave committed Dec 15, 2024
1 parent 54d7961 commit 7eb6755
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 23 deletions.
5 changes: 3 additions & 2 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@
octoHeatPumpSetZoneMode(accountNumber: "{account_id}", euid: "{euid}", operationParameters: {{
zone: {zone_id},
mode: BOOST,
setpointInCelsius: "{target_temperature}",
endAt: "{end_at}"
}}) {{
transactionId
Expand Down Expand Up @@ -824,14 +825,14 @@ async def async_set_heat_pump_zone_mode(self, account_id: str, euid: str, zone_i
_LOGGER.warning(f'Failed to connect. Timeout of {self._timeout} exceeded.')
raise TimeoutException()

async def async_boost_heat_pump_zone(self, account_id: str, euid: str, zone_id: str, end_datetime: datetime):
async def async_boost_heat_pump_zone(self, account_id: str, euid: str, zone_id: str, end_datetime: datetime, target_temperature: float):
"""Boost a given heat pump zone"""
await self.async_refresh_token()

try:
client = self._create_client_session()
url = f'{self._base_url}/v1/graphql/'
query = heat_pump_boost_zone_mutation.format(account_id=account_id, euid=euid, zone_id=zone_id, end_at=end_datetime.isoformat(sep="T"))
query = heat_pump_boost_zone_mutation.format(account_id=account_id, euid=euid, zone_id=zone_id, end_at=end_datetime.isoformat(sep="T"), target_temperature=target_temperature)
payload = { "query": query }
headers = { "Authorization": f"JWT {self._graphql_token}" }
async with client.post(url, json=payload, headers=headers) as heat_pump_response:
Expand Down
1 change: 1 addition & 0 deletions custom_components/octopus_energy/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ async def async_setup_default_sensors(hass, config, async_add_entities):
{
vol.Required("hours"): cv.positive_int,
vol.Required("minutes"): cv.positive_int,
vol.Optional("target_temperature"): cv.positive_float,
},
extra=vol.ALLOW_EXTRA,
),
Expand Down
61 changes: 40 additions & 21 deletions custom_components/octopus_energy/heat_pump/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import logging
from typing import List

from custom_components.octopus_energy.const import DOMAIN
from homeassistant.util.dt import (utcnow)
from homeassistant.exceptions import ServiceValidationError

from homeassistant.const import (
UnitOfTemperature,
PRECISION_HALVES,
PRECISION_TENTHS,
ATTR_TEMPERATURE
)
Expand Down Expand Up @@ -47,6 +50,7 @@ class OctopusEnergyHeatPumpZone(CoordinatorEntity, BaseOctopusEnergyHeatPumpSens
_attr_preset_mode = None
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_target_temperature_step = PRECISION_TENTHS
_attr_target_temperature_step = PRECISION_HALVES

def __init__(self, hass: HomeAssistant, coordinator, client: OctopusEnergyApiClient, account_id: str, heat_pump_id: str, heat_pump: HeatPump, zone: ConfigurationZone, is_mocked: bool):
"""Init sensor."""
Expand Down Expand Up @@ -140,20 +144,21 @@ def _handle_coordinator_update(self) -> None:
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
try:
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, hvac_mode, None)
self._attr_hvac_mode = hvac_mode
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_hvac_mode error due to mocking mode: {e}')
else:
raise

self._attr_hvac_mode = hvac_mode
self.async_write_ha_state()

async def async_turn_on(self):
"""Turn the entity on."""
try:
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, 'ON', 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:
_LOGGER.warning(f'Suppress async_turn_on error due to mocking mode: {e}')
Expand All @@ -179,38 +184,29 @@ async def async_turn_off(self):
async def async_set_preset_mode(self, preset_mode):
"""Set new target preset mode."""
try:
if preset_mode == PRESET_BOOST:
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)
await self._client.async_boost_heat_pump_zone(self._account_id, self._heat_pump_id, self._zone.configuration.code, current, self._attr_target_temperature)
else:
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, self._attr_hvac_mode, 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:
if self._is_mocked:
_LOGGER.warning(f'Suppress async_set_preset_mode error due to mocking mode: {e}')
else:
raise

self._attr_preset_mode = preset_mode
self.async_write_ha_state()

async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
temperature = kwargs[ATTR_TEMPERATURE]

zone_mode = None
if self._attr_preset_mode == PRESET_BOOST:
zone_mode = "BOOST"
elif self._attr_hvac_mode == HVACMode.HEAT:
zone_mode = "ON"
elif self._attr_hvac_mode == HVACMode.OFF:
zone_mode = "OFF"
elif self._attr_hvac_mode == HVACMode.AUTO:
zone_mode = "AUTO"
else:
raise Exception(f"Unexpected heat pump mode detected: {self._attr_hvac_mode}/{self._attr_preset_mode}")

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)
except Exception as e:
if self._is_mocked:
Expand All @@ -221,13 +217,36 @@ async def async_set_temperature(self, **kwargs) -> None:
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):
async def async_boost_heat_pump_zone(self, hours: int, minutes: int, target_temperature: float | None = None):
"""Boost the heat pump zone"""

if target_temperature is not None:
if target_temperature < self._attr_min_temp or target_temperature > self._attr_max_temp:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_target_temperature",
translation_placeholders={
"min_temperature": self._attr_min_temp,
"max_temperature": self._attr_max_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)
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._attr_preset_mode = PRESET_BOOST
self.async_write_ha_state()
8 changes: 8 additions & 0 deletions custom_components/octopus_energy/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,12 @@ boost_heat_pump_zone:
step: 15
min: 0
max: 45
mode: box
target_temperature:
name: Target Temperature
description: The optional target temperature to boost to. If not supplied, then the current target temperature will be used
required: false
selector:
number:
step: 0.5
mode: box
3 changes: 3 additions & 0 deletions custom_components/octopus_energy/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@
},
"octoplus_points_maximum_points": {
"message": "You cannot redeem more than {redeemable_points} points"
},
"invalid_target_temperature": {
"message": "Temperature must be equal or between {min_temperature} and {max_temperature}"
}
},
"issues": {
Expand Down

0 comments on commit 7eb6755

Please sign in to comment.