Skip to content

Commit

Permalink
Add lifetime sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
lwis committed Dec 26, 2024
1 parent 2a02de3 commit 4650ff4
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 20 deletions.
32 changes: 14 additions & 18 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,18 @@
}}
readAt
}}
octoHeatPumpLifetimePerformance(euid: "{euid}") {{
seasonalCoefficientOfPerformance
heatOutput {{
value
unit
}}
energyInput {{
value
unit
}}
readAt
}}
}}
'''

Expand All @@ -551,23 +563,6 @@
}}
'''

heat_pump_lifetime_performance = '''
query {{
octoHeatPumpLifetimePerformance(euid: "{euid}") {{
seasonalCoefficientOfPerformance
heatOutput {{
value
unit
}}
energyInput {{
value
unit
}}
}}
readAt
}}
'''


user_agent_value = "bottlecapdave-ha-octopus-energy"

Expand Down Expand Up @@ -875,7 +870,8 @@ async def async_get_heat_pump_configuration_and_status(self, account_id: str, eu
and "data" in response
and "octoHeatPumpControllerConfiguration" in response["data"]
and "octoHeatPumpControllerStatus" in response["data"]
and "octoHeatPumpLivePerformance" in response["data"]):
and "octoHeatPumpLivePerformance" in response["data"]
and "octoHeatPumpLifetimePerformance" in response["data"]):
return HeatPumpResponse.parse_obj(response["data"])

return None
Expand Down
4 changes: 3 additions & 1 deletion custom_components/octopus_energy/api_client/heat_pump.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class OctoHeatPumpLifetimePerformance(BaseModel):
energyInput: ValueAndUnit
readAt: str


class OctoHeatPumpTimeRangedPerformance(BaseModel):
coefficientOfPerformance: str
energyOutput: ValueAndUnit
Expand All @@ -145,4 +146,5 @@ class OctoHeatPumpTimeRangedPerformance(BaseModel):
class HeatPumpResponse(BaseModel):
octoHeatPumpControllerStatus: OctoHeatPumpControllerStatus
octoHeatPumpControllerConfiguration: OctoHeatPumpControllerConfiguration
octoHeatPumpLivePerformance: OctoHeatPumpLivePerformance
octoHeatPumpLivePerformance: OctoHeatPumpLivePerformance
octoHeatPumpLifetimePerformance: OctoHeatPumpLifetimePerformance
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from datetime import datetime
import logging
from typing import List

from homeassistant.const import (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfEnergy
)
from homeassistant.core import HomeAssistant, callback

from homeassistant.util.dt import (now)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity
)
from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorStateClass,
)

from .base import (BaseOctopusEnergyHeatPumpSensor)
from ..utils.attributes import dict_to_typed_dict
from ..api_client.heat_pump import HeatPump
from ..coordinators.heat_pump_configuration_and_status import HeatPumpCoordinatorResult

_LOGGER = logging.getLogger(__name__)

class OctopusEnergyHeatPumpSensorLifetimeEnergyInput(CoordinatorEntity, BaseOctopusEnergyHeatPumpSensor, RestoreSensor):
"""Sensor for displaying the lifetime energy input of a heat pump."""

def __init__(self, hass: HomeAssistant, coordinator, heat_pump_id: str, heat_pump: HeatPump):
"""Init sensor."""
# Pass coordinator to base class
CoordinatorEntity.__init__(self, coordinator)
BaseOctopusEnergyHeatPumpSensor.__init__(self, hass, heat_pump_id, heat_pump)

self._state = None
self._last_updated = None

@property
def unique_id(self):
"""The id of the sensor."""
return f"octopus_energy_heat_pump_{self._heat_pump_id}_lifetime_energy_input"

@property
def name(self):
"""Name of the sensor."""
return f"Lifetime Energy Input ({self._heat_pump_id})"

@property
def state_class(self):
"""The state class of sensor"""
return SensorStateClass.TOTAL_INCREASING

@property
def device_class(self):
"""The type of sensor"""
return SensorDeviceClass.ENERGY

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:flash"

@property
def native_unit_of_measurement(self):
"""Unit of measurement of the sensor."""
return UnitOfEnergy.KILO_WATT_HOUR

@property
def extra_state_attributes(self):
"""Attributes of the sensor."""
return self._attributes

@property
def native_value(self):
return self._state

@callback
def _handle_coordinator_update(self) -> None:
"""Retrieve the live CoP for the heat pump."""
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.octoHeatPumpLifetimePerformance is not None):
_LOGGER.debug(f"Updating OctopusEnergyHeatPumpSensorLifetimeEnergyInput for '{self._heat_pump_id}'")

self._state = float(result.data.octoHeatPumpLifetimePerformance.energyInput.value)
self._attributes["read_at"] = datetime.fromisoformat(result.data.octoHeatPumpLifetimePerformance.readAt)
self._last_updated = current

self._attributes = dict_to_typed_dict(self._attributes)
super()._handle_coordinator_update()

async def async_added_to_hass(self):
"""Call when entity about to be added to hass."""
# If not None, we got an initial value.
await super().async_added_to_hass()
state = await self.async_get_last_state()

if state is not None and self._state is None:
self._state = None if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) else state.state
self._attributes = dict_to_typed_dict(state.attributes, [])

_LOGGER.debug(f'Restored OctopusEnergyHeatPumpSensorLifetimeEnergyInput state: {self._state}')
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from datetime import datetime
import logging
from typing import List

from homeassistant.const import (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfEnergy
)
from homeassistant.core import HomeAssistant, callback

from homeassistant.util.dt import (now)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity
)
from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorStateClass,
)

from .base import (BaseOctopusEnergyHeatPumpSensor)
from ..utils.attributes import dict_to_typed_dict
from ..api_client.heat_pump import HeatPump
from ..coordinators.heat_pump_configuration_and_status import HeatPumpCoordinatorResult

_LOGGER = logging.getLogger(__name__)

class OctopusEnergyHeatPumpSensorLifetimeHeatOutput(CoordinatorEntity, BaseOctopusEnergyHeatPumpSensor, RestoreSensor):
"""Sensor for displaying the lifetime heat output of a heat pump."""

def __init__(self, hass: HomeAssistant, coordinator, heat_pump_id: str, heat_pump: HeatPump):
"""Init sensor."""
# Pass coordinator to base class
CoordinatorEntity.__init__(self, coordinator)
BaseOctopusEnergyHeatPumpSensor.__init__(self, hass, heat_pump_id, heat_pump)

self._state = None
self._last_updated = None

@property
def unique_id(self):
"""The id of the sensor."""
return f"octopus_energy_heat_pump_{self._heat_pump_id}_lifetime_heat_output"

@property
def name(self):
"""Name of the sensor."""
return f"Lifetime Heat Output ({self._heat_pump_id})"

@property
def state_class(self):
"""The state class of sensor"""
return SensorStateClass.TOTAL_INCREASING

@property
def device_class(self):
"""The type of sensor"""
return SensorDeviceClass.ENERGY

@property
def icon(self):
"""Icon of the sensor."""
return "mdi:flash"

@property
def native_unit_of_measurement(self):
"""Unit of measurement of the sensor."""
return UnitOfEnergy.KILO_WATT_HOUR

@property
def extra_state_attributes(self):
"""Attributes of the sensor."""
return self._attributes

@property
def native_value(self):
return self._state

@callback
def _handle_coordinator_update(self) -> None:
"""Retrieve the lifeime heat output for the heat pump."""
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.octoHeatPumpLifetimePerformance is not None):
_LOGGER.debug(f"Updating OctopusEnergyHeatPumpSensorLifetimeHeatOutput for '{self._heat_pump_id}'")

self._state = float(result.data.octoHeatPumpLifetimePerformance.heatOutput.value)
self._attributes["read_at"] = datetime.fromisoformat(result.data.octoHeatPumpLifetimePerformance.readAt)
self._last_updated = current

self._attributes = dict_to_typed_dict(self._attributes)
super()._handle_coordinator_update()

async def async_added_to_hass(self):
"""Call when entity about to be added to hass."""
# If not None, we got an initial value.
await super().async_added_to_hass()
state = await self.async_get_last_state()

if state is not None and self._state is None:
self._state = None if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) else state.state
self._attributes = dict_to_typed_dict(state.attributes, [])

_LOGGER.debug(f'Restored OctopusEnergyHeatPumpSensorLifetimeHeatOutput state: {self._state}')
93 changes: 93 additions & 0 deletions custom_components/octopus_energy/heat_pump/sensor_lifetime_scop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from datetime import datetime
import logging
from typing import List

from homeassistant.const import (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfPower
)
from homeassistant.core import HomeAssistant, callback

from homeassistant.util.dt import (now)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity
)
from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorStateClass,
)

from .base import (BaseOctopusEnergyHeatPumpSensor)
from ..utils.attributes import dict_to_typed_dict
from ..api_client.heat_pump import HeatPump
from ..coordinators.heat_pump_configuration_and_status import HeatPumpCoordinatorResult

_LOGGER = logging.getLogger(__name__)

class OctopusEnergyHeatPumpSensorLifetimeSCoP(CoordinatorEntity, BaseOctopusEnergyHeatPumpSensor, RestoreSensor):
"""Sensor for displaying the lifetime SCoP of a heat pump."""

def __init__(self, hass: HomeAssistant, coordinator, heat_pump_id: str, heat_pump: HeatPump):
"""Init sensor."""
# Pass coordinator to base class
CoordinatorEntity.__init__(self, coordinator)
BaseOctopusEnergyHeatPumpSensor.__init__(self, hass, heat_pump_id, heat_pump)

self._state = None
self._last_updated = None

@property
def unique_id(self):
"""The id of the sensor."""
return f"octopus_energy_heat_pump_{self._heat_pump_id}_lifetime_scop"

@property
def name(self):
"""Name of the sensor."""
return f"Lifetime SCoP ({self._heat_pump_id})"

@property
def state_class(self):
"""The state class of sensor"""
return SensorStateClass.MEASUREMENT

@property
def extra_state_attributes(self):
"""Attributes of the sensor."""
return self._attributes

@property
def native_value(self):
return self._state

@callback
def _handle_coordinator_update(self) -> None:
"""Retrieve the lifetime SCoP for the heat pump."""
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.octoHeatPumpLifetimePerformance is not None):
_LOGGER.debug(f"Updating OctopusEnergyHeatPumpSensorLifetimeSCoP for '{self._heat_pump_id}'")

self._state = float(result.data.octoHeatPumpLifetimePerformance.seasonalCoefficientOfPerformance)
self._attributes["read_at"] = datetime.fromisoformat(result.data.octoHeatPumpLifetimePerformance.readAt)
self._last_updated = current

self._attributes = dict_to_typed_dict(self._attributes)
super()._handle_coordinator_update()

async def async_added_to_hass(self):
"""Call when entity about to be added to hass."""
# If not None, we got an initial value.
await super().async_added_to_hass()
state = await self.async_get_last_state()

if state is not None and self._state is None:
self._state = None if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN) else state.state
self._attributes = dict_to_typed_dict(state.attributes, [])

_LOGGER.debug(f'Restored OctopusEnergyHeatPumpSensorLifetimeSCoP state: {self._state}')
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ def _handle_coordinator_update(self) -> None:
and result.data.octoHeatPumpLivePerformance is not None):
_LOGGER.debug(f"Updating OctopusEnergyHeatPumpSensorLiveCoP for '{self._heat_pump_id}'")

self._state = float(result.data.octoHeatPumpLivePerformance.coefficientOfPerformance)
self._state = 0
# Only update the CoP if active
if float(result.data.octoHeatPumpLivePerformance.powerInput.value) != 0:
self._state = float(result.data.octoHeatPumpLivePerformance.coefficientOfPerformance)

self._attributes["read_at"] = datetime.fromisoformat(result.data.octoHeatPumpLivePerformance.readAt)
self._last_updated = current

Expand Down
Loading

0 comments on commit 4650ff4

Please sign in to comment.