Skip to content

Commit

Permalink
feat: Added sensors for displaying current total consumption (40 minu…
Browse files Browse the repository at this point in the history
…tes dev)
  • Loading branch information
BottlecapDave committed Jun 13, 2024
1 parent 0de0bb3 commit dbb2b5d
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 1 deletion.
19 changes: 19 additions & 0 deletions _docs/entities/electricity.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,25 @@ Each charge item has the following attributes
| `end` | `datetime` | The date/time when the consumption ends |
| `consumption` | `float` | The consumption value of the specified period |

### Current Total Consumption

`sensor.octopus_energy_electricity_{{METER_SERIAL_NUMBER}}_{{MPAN_NUMBER}}_current_total_consumption`

!!! warning
This will only be available if you have specified you have a [Octopus Home Mini](../setup/account.md#home-mini). Do not set unless you have one

!!! info
An export equivalent of this sensor does not exist because the data is not available

The total consumption reported by the meter for all time.

| Attribute | Type | Description |
|-----------|------|-------------|
| `mpan` | `string` | The mpan for the associated meter |
| `serial_number` | `string` | The serial for the associated meter |
| `is_export` | `boolean` | Determines if the meter exports energy rather than imports |
| `is_smart_meter` | `boolean` | Determines if the meter is considered smart by Octopus Energy |

#### Variants

The following variants of the [Current Accumulative Consumption](#current-accumulative-consumption) are available.
Expand Down
28 changes: 28 additions & 0 deletions _docs/entities/gas.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,34 @@ Each charge item has the following attributes
| `end` | `datetime` | The date/time when the consumption ends |
| `consumption` | `float` | The consumption value of the specified period |

### Current Total Consumption (m3)

`sensor.octopus_energy_gas_{{METER_SERIAL_NUMBER}}_{{MPRN_NUMBER}}_current_total_consumption_m3`

The total consumption reported by the meter for for all time in m3. This is calculated/estimated using your set [calorific value](../setup/account.md#calorific-value) from the kWh data reported by Octopus Energy.

| Attribute | Type | Description |
|-----------|------|-------------|
| `mprn` | `string` | The mprn for the associated meter |
| `serial_number` | `string` | The serial for the associated meter |
| `last_evaluated` | `datetime` | The timestamp determining when the consumption was last calculated. |
| `calorific_value` | `float` | The calorific value used for the calculations, as set in your [account](../setup/account.md#calorific-value). |
| `data_last_retrieved` | `datetime` | The timestamp when the underlying data was last refreshed from the OE servers |

### Current Total Consumption (kWh)

`sensor.octopus_energy_gas_{{METER_SERIAL_NUMBER}}_{{MPRN_NUMBER}}_current_total_consumption_kwh`

The total consumption reported by the meter for for all time in kWh. This is natively reported by Octopus Energy.

| Attribute | Type | Description |
|-----------|------|-------------|
| `mprn` | `string` | The mprn for the associated meter |
| `serial_number` | `string` | The serial for the associated meter |
| `last_evaluated` | `datetime` | The timestamp determining when the consumption was last calculated. |
| `calorific_value` | `float` | The calorific value used for the calculations, as set in your [account](../setup/account.md#calorific-value). |
| `data_last_retrieved` | `datetime` | The timestamp when the underlying data was last refreshed from the OE servers |

### Current Accumulative Cost

`sensor.octopus_energy_gas_{{METER_SERIAL_NUMBER}}_{{MPRN_NUMBER}}_current_accumulative_cost`
Expand Down
2 changes: 2 additions & 0 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
end: "{period_to}"
) {{
readAt
consumption
consumptionDelta
demand
}}
Expand Down Expand Up @@ -692,6 +693,7 @@ async def async_get_smart_meter_consumption(self, device_id: str, period_from: d

if (response_body is not None and "data" in response_body and "smartMeterTelemetry" in response_body["data"] and response_body["data"]["smartMeterTelemetry"] is not None and len(response_body["data"]["smartMeterTelemetry"]) > 0):
return list(map(lambda mp: {
"total_consumption": float(mp["consumption"]) / 1000 if "consumption" in mp and mp["consumption"] is not None else None,
"consumption": float(mp["consumptionDelta"]) / 1000 if "consumptionDelta" in mp and mp["consumptionDelta"] is not None else 0,
"demand": float(mp["demand"]) if "demand" in mp and mp["demand"] is not None else None,
"start": parse_datetime(mp["readAt"]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import logging

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

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)

from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import (
UnitOfEnergy
)

from homeassistant.util.dt import (now)

from ..coordinators.current_consumption import CurrentConsumptionCoordinatorResult
from .base import (OctopusEnergyElectricitySensor)
from ..utils.attributes import dict_to_typed_dict

_LOGGER = logging.getLogger(__name__)

class OctopusEnergyCurrentTotalElectricityConsumption(CoordinatorEntity, OctopusEnergyElectricitySensor, RestoreSensor):
"""Sensor for displaying the current total electricity consumption."""

def __init__(self, hass: HomeAssistant, coordinator, meter, point):
"""Init sensor."""
CoordinatorEntity.__init__(self, coordinator)

self._state = None
self._last_reset = None

OctopusEnergyElectricitySensor.__init__(self, hass, meter, point)

@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added.
This only applies when fist added to the entity registry.
"""
return self._is_smart_meter

@property
def unique_id(self):
"""The id of the sensor."""
return f"octopus_energy_electricity_{self._serial_number}_{self._mpan}_current_total_consumption"

@property
def name(self):
"""Name of the sensor."""
return f"Current Total Consumption Electricity ({self._serial_number}/{self._mpan})"

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

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

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

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

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

@property
def last_reset(self):
"""Return the time when the sensor was last reset, if any."""
return self._last_reset

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

@callback
def _handle_coordinator_update(self) -> None:
"""Retrieve the current days accumulative consumption"""
current = now()
consumption_result: CurrentConsumptionCoordinatorResult = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
consumption_data = consumption_result.data if consumption_result is not None else None

if (consumption_data is not None and len(consumption_data) > 0):
_LOGGER.debug(f"Calculated total electricity consumption for '{self._mpan}/{self._serial_number}'...")

self._state = consumption_data[-1]["total_consumption"]
self._last_reset = current

self._attributes = {
"mpan": self._mpan,
"serial_number": self._serial_number,
"is_export": self._is_export,
"is_smart_meter": self._is_smart_meter,
"last_evaluated": current,
"data_last_retrieved": consumption_result.last_retrieved if consumption_result is not None else None
}

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 state: {self._state}')
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import logging

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

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)

from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import (
UnitOfVolume
)

from homeassistant.util.dt import (now)

from ..coordinators.current_consumption import CurrentConsumptionCoordinatorResult
from .base import (OctopusEnergyGasSensor)
from ..utils.attributes import dict_to_typed_dict
from . import convert_kwh_to_m3

_LOGGER = logging.getLogger(__name__)

class OctopusEnergyCurrentTotalGasConsumptionCubicMeters(CoordinatorEntity, OctopusEnergyGasSensor, RestoreSensor):
"""Sensor for displaying the current total gas consumption in m3."""

def __init__(self, hass: HomeAssistant, coordinator, meter, point, calorific_value: float):
"""Init sensor."""
CoordinatorEntity.__init__(self, coordinator)

self._state = None
self._last_reset = None
self._calorific_value = calorific_value

OctopusEnergyGasSensor.__init__(self, hass, meter, point)

@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added.
This only applies when fist added to the entity registry.
"""
return self._is_smart_meter

@property
def unique_id(self):
"""The id of the sensor."""
return f"octopus_energy_gas_{self._serial_number}_{self._mprn}_current_total_consumption_m3"

@property
def name(self):
"""Name of the sensor."""
return f"Current Total Consumption (m3) Gas ({self._serial_number}/{self._mprn})"

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

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

@property
def native_unit_of_measurement(self):
"""The unit of measurement of sensor"""
return UnitOfVolume.CUBIC_METERS

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

@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 current days accumulative consumption"""
current = now()
consumption_result: CurrentConsumptionCoordinatorResult = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
consumption_data = consumption_result.data if consumption_result is not None else None

if (consumption_data is not None and len(consumption_data) > 0):
_LOGGER.debug(f"Calculated total gas consumption for '{self._mprn}/{self._serial_number}'...")

self._state = convert_kwh_to_m3(consumption_data[-1]["total_consumption"], self._calorific_value) if consumption_data[-1]["total_consumption"] is not None else None

self._attributes = {
"mprn": self._mprn,
"serial_number": self._serial_number,
"is_smart_meter": self._is_smart_meter,
"last_evaluated": current,
"data_last_retrieved": consumption_result.last_retrieved if consumption_result is not None else None
}

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 state: {self._state}')

0 comments on commit dbb2b5d

Please sign in to comment.