Skip to content

Commit 85dd008

Browse files
BottlecapDavelwis
andauthored
feat: Added heat pump fixed flow temperature sensor (Thanks @lwis)
Co-authored-by: Lewis Juggins <873275+lwis@users.noreply.github.com>
1 parent cb3c619 commit 85dd008

File tree

8 files changed

+154
-11
lines changed

8 files changed

+154
-11
lines changed

_docs/entities/heat_pump.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ This represents the current outdoor temperature as observed by the heat pump.
111111
!!! note
112112
As the integration uses cloud polling this will inherently have a delay.
113113

114+
## Fixed Target Flow Temperature
115+
116+
`sensor.octopus_energy_heat_pump_{{HEAT_PUMP_ID}}_fixed_target_flow_temperature`
117+
118+
This represents the configured fixed target flow temperature for the heat pump, which is used in fixed mode only.
119+
120+
!!! note
121+
This is not the actual current flow temperature, but rather the configured target.
122+
114123
## Services
115124

116125
There are some services available relating to these entities that you might find useful. They can be found in the [services docs](../services.md#heat-pump).

_docs/services.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ Allows you to set the heat pump configuration for fixed and weather compensated
258258
!!! warning
259259
Changing this configuration without a good understanding of heat loss and emitter output can cause cycling, defrosting, or incorrect heat delivery.
260260

261+
!!! note
262+
Corresponding sensors will not update straight away upon calling.
263+
261264
| Attribute | Optional | Description |
262265
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
263266
| `target.entity_id` | `no` | Any climate entity belonging to the heat pump which the configuration should be applied to (e.g. `climate.octopus_energy_heat_pump_{{HEAT_PUMP_ID}}_{{ZONE_CODE}}`). |

custom_components/octopus_energy/heat_pump/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def mock_heat_pump_status_and_configuration():
169169
"minWaterSetpoint": 40,
170170
"heatingFlowTemperature": {
171171
"currentTemperature": {
172-
"value": "56",
172+
"value": 30 + random.randrange(1, 40),
173173
"unit": "DEGREES_CELSIUS"
174174
},
175175
"allowableRange": {
@@ -197,11 +197,11 @@ def mock_heat_pump_status_and_configuration():
197197
},
198198
"currentRange": {
199199
"minimum": {
200-
"value": "45",
200+
"value": 30 + random.randrange(1, 20),
201201
"unit": "DEGREES_CELSIUS"
202202
},
203203
"maximum": {
204-
"value": "55",
204+
"value": 50 + random.randrange(1, 20),
205205
"unit": "DEGREES_CELSIUS"
206206
}
207207
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from datetime import datetime
2+
import logging
3+
from typing import List
4+
5+
from homeassistant.const import (
6+
STATE_UNAVAILABLE,
7+
STATE_UNKNOWN,
8+
UnitOfTemperature
9+
)
10+
from homeassistant.core import HomeAssistant, callback
11+
12+
from homeassistant.util.dt import (now)
13+
from homeassistant.helpers.update_coordinator import (
14+
CoordinatorEntity
15+
)
16+
from homeassistant.components.sensor import (
17+
RestoreSensor,
18+
SensorDeviceClass,
19+
SensorStateClass,
20+
)
21+
22+
from .base import (BaseOctopusEnergyHeatPumpSensor)
23+
from ..utils.attributes import dict_to_typed_dict
24+
from ..api_client.heat_pump import HeatPump
25+
from ..coordinators.heat_pump_configuration_and_status import HeatPumpCoordinatorResult
26+
27+
_LOGGER = logging.getLogger(__name__)
28+
29+
class OctopusEnergyHeatPumpSensorFixedTargetFlowTemperature(CoordinatorEntity, BaseOctopusEnergyHeatPumpSensor, RestoreSensor):
30+
"""Sensor for displaying the live heat output of a heat pump."""
31+
32+
def __init__(self, hass: HomeAssistant, coordinator, heat_pump_id: str, heat_pump: HeatPump):
33+
"""Init sensor."""
34+
# Pass coordinator to base class
35+
CoordinatorEntity.__init__(self, coordinator)
36+
BaseOctopusEnergyHeatPumpSensor.__init__(self, hass, heat_pump_id, heat_pump)
37+
38+
self._state = None
39+
self._last_updated = None
40+
41+
@property
42+
def unique_id(self):
43+
"""The id of the sensor."""
44+
return f"octopus_energy_heat_pump_{self._heat_pump_id}_fixed_target_flow_temperature"
45+
46+
@property
47+
def name(self):
48+
"""Name of the sensor."""
49+
return f"Fixed Target Flow Temperature Heat Pump ({self._heat_pump_id})"
50+
51+
@property
52+
def state_class(self):
53+
"""The state class of sensor"""
54+
return SensorStateClass.MEASUREMENT
55+
56+
@property
57+
def device_class(self):
58+
"""The type of sensor"""
59+
return SensorDeviceClass.TEMPERATURE
60+
61+
@property
62+
def icon(self):
63+
"""Icon of the sensor."""
64+
return "mdi:thermometer"
65+
66+
@property
67+
def native_unit_of_measurement(self):
68+
"""Unit of measurement of the sensor."""
69+
return UnitOfTemperature.CELSIUS
70+
71+
@property
72+
def extra_state_attributes(self):
73+
"""Attributes of the sensor."""
74+
return self._attributes
75+
76+
@property
77+
def native_value(self):
78+
return self._state
79+
80+
@callback
81+
def _handle_coordinator_update(self) -> None:
82+
"""Retrieve the configured fixed target flow temperature for the heat pump."""
83+
current = now()
84+
result: HeatPumpCoordinatorResult = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
85+
86+
if (result is not None
87+
and result.data is not None
88+
and result.data.octoHeatPumpControllerConfiguration is not None
89+
and result.data.octoHeatPumpControllerConfiguration.heatPump is not None
90+
and result.data.octoHeatPumpControllerConfiguration.heatPump.heatingFlowTemperature is not None
91+
and result.data.octoHeatPumpControllerConfiguration.heatPump.heatingFlowTemperature.currentTemperature is not None):
92+
_LOGGER.debug(f"Updating OctopusEnergyHeatPumpSensorFixedTargetFlowTemperature for '{self._heat_pump_id}'")
93+
94+
self._state = float(result.data.octoHeatPumpControllerConfiguration.heatPump.heatingFlowTemperature.currentTemperature.value)
95+
self._last_updated = current
96+
97+
self._attributes = dict_to_typed_dict(self._attributes)
98+
super()._handle_coordinator_update()
99+
100+
async def async_added_to_hass(self):
101+
"""Call when entity about to be added to hass."""
102+
# If not None, we got an initial value.
103+
await super().async_added_to_hass()
104+
state = await self.async_get_last_state()
105+
last_sensor_state = await self.async_get_last_sensor_data()
106+
107+
if state is not None and last_sensor_state is not None and self._state is None:
108+
self._state = None if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) else last_sensor_state.native_value
109+
self._attributes = dict_to_typed_dict(state.attributes, [])
110+
111+
_LOGGER.debug(f'Restored OctopusEnergyHeatPumpSensorFixedTargetFlowTemperature state: {self._state}')

custom_components/octopus_energy/heat_pump/water_heater.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ async def async_set_operation_mode(self, operation_mode: str):
151151
)
152152
else:
153153
zone_mode = self.get_zone_mode()
154-
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)
154+
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, zone_mode, None)
155155
except Exception as e:
156156
if self._is_mocked:
157-
_LOGGER.warning(f'Suppress async_set_preset_mode error due to mocking mode: {e}')
157+
_LOGGER.warning(f'Suppress async_set_operation_mode error due to mocking mode: {e}')
158158
else:
159159
raise
160160

@@ -164,7 +164,7 @@ async def async_turn_on(self):
164164
"""Turn the entity on."""
165165
try:
166166
self._attr_current_operation = OE_TO_HA_STATE["ON"]
167-
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)
167+
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, 'ON', None)
168168
except Exception as e:
169169
if self._is_mocked:
170170
_LOGGER.warning(f'Suppress async_turn_on error due to mocking mode: {e}')

custom_components/octopus_energy/heat_pump/zone.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
HVACMode,
2424
PRESET_NONE,
2525
PRESET_BOOST,
26+
ATTR_HVAC_MODE
2627
)
2728

2829
from .base import (BaseOctopusEnergyHeatPumpSensor)
@@ -161,7 +162,7 @@ async def async_turn_on(self):
161162
try:
162163
self._attr_hvac_mode = HVACMode.HEAT
163164
self._attr_preset_mode = PRESET_NONE
164-
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)
165+
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, 'ON', None)
165166
except Exception as e:
166167
if self._is_mocked:
167168
_LOGGER.warning(f'Suppress async_turn_on error due to mocking mode: {e}')
@@ -206,7 +207,7 @@ async def async_set_preset_mode(self, preset_mode):
206207
)
207208
else:
208209
zone_mode = self.get_zone_mode()
209-
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)
210+
await self._client.async_set_heat_pump_zone_mode(self._account_id, self._heat_pump_id, self._zone.configuration.code, zone_mode, None)
210211
except Exception as e:
211212
if self._is_mocked:
212213
_LOGGER.warning(f'Suppress async_set_preset_mode error due to mocking mode: {e}')
@@ -220,6 +221,12 @@ async def async_set_temperature(self, **kwargs) -> None:
220221

221222
try:
222223
self._attr_target_temperature = kwargs[ATTR_TEMPERATURE]
224+
225+
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
226+
if hvac_mode is not None:
227+
self._attr_hvac_mode = hvac_mode
228+
self._attr_preset_mode = PRESET_NONE
229+
223230
if self._attr_preset_mode == PRESET_BOOST:
224231
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)
225232
else:

custom_components/octopus_energy/number.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22

3+
34
from .utils.debug_overrides import async_get_account_debug_override
45

56
from .intelligent import get_intelligent_features
@@ -27,12 +28,16 @@ async def async_setup_entry(hass, entry, async_add_entities):
2728
if entry.options:
2829
config.update(entry.options)
2930

31+
entities = []
32+
3033
if CONFIG_MAIN_API_KEY in entry.data:
31-
await async_setup_intelligent_sensors(hass, config, async_add_entities)
34+
entities.extend(await async_setup_intelligent_sensors(hass, config))
35+
36+
async_add_entities(entities)
3237

3338
return True
3439

35-
async def async_setup_intelligent_sensors(hass, config, async_add_entities):
40+
async def async_setup_intelligent_sensors(hass, config):
3641
_LOGGER.debug('Setting up intelligent sensors')
3742

3843
entities = []
@@ -50,4 +55,4 @@ async def async_setup_intelligent_sensors(hass, config, async_add_entities):
5055
if intelligent_features.charge_limit_supported == True:
5156
entities.append(OctopusEnergyIntelligentChargeTarget(hass, settings_coordinator, client, intelligent_device, account_id, account_debug_override.mock_intelligent_controls if account_debug_override is not None else False))
5257

53-
async_add_entities(entities)
58+
return entities

custom_components/octopus_energy/sensor.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from .heat_pump.sensor_lifetime_scop import OctopusEnergyHeatPumpSensorLifetimeSCoP
6969
from .heat_pump.sensor_lifetime_heat_output import OctopusEnergyHeatPumpSensorLifetimeHeatOutput
7070
from .heat_pump.sensor_lifetime_energy_input import OctopusEnergyHeatPumpSensorLifetimeEnergyInput
71+
from .heat_pump.sensor_fixed_target_flow_temperature import OctopusEnergyHeatPumpSensorFixedTargetFlowTemperature
7172
from .api_client.intelligent_device import IntelligentDevice
7273
from .intelligent.current_state import OctopusEnergyIntelligentCurrentState
7374
from .intelligent import get_intelligent_features
@@ -598,6 +599,13 @@ def setup_heat_pump_sensors(hass: HomeAssistant, account_id: str, heat_pump_id:
598599
entities.append(OctopusEnergyHeatPumpDataLastRetrieved(hass, coordinator, account_id, heat_pump_id))
599600

600601
if heat_pump_response.octoHeatPumpControllerConfiguration is not None:
602+
entities.append(OctopusEnergyHeatPumpSensorFixedTargetFlowTemperature(
603+
hass,
604+
coordinator,
605+
heat_pump_id,
606+
heat_pump_response.octoHeatPumpControllerConfiguration.heatPump
607+
))
608+
601609
for zone in heat_pump_response.octoHeatPumpControllerConfiguration.zones:
602610
if zone.configuration is not None and zone.configuration.sensors is not None:
603611
if zone.configuration.enabled == False:

0 commit comments

Comments
 (0)