Skip to content

Commit d696510

Browse files
committed
Added in household view.
Some TODOs left.
1 parent f4b8e60 commit d696510

File tree

3 files changed

+132
-2
lines changed

3 files changed

+132
-2
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from datetime import date, datetime
2+
from dataclasses import dataclass
3+
from enum import Enum
4+
from typing import Callable
5+
6+
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, SensorStateClass
7+
from homeassistant.const import UnitOfPower, UnitOfEnergy
8+
from homeassistant.helpers.device_registry import DeviceInfo
9+
from homeassistant.helpers.typing import StateType
10+
11+
from powersensor_local import VirtualHousehold
12+
13+
from .const import DOMAIN
14+
15+
class HouseholdMeasurements(Enum):
16+
POWER_HOME_USE = 1
17+
POWER_FROM_GRID = 2
18+
POWER_TO_GRID = 3
19+
POWER_SOLAR_GENERATION = 4
20+
ENERGY_HOME_USE = 5
21+
ENERGY_FROM_GRID = 6
22+
ENERGY_TO_GRID = 7
23+
ENERGY_SOLAR_GENERATION = 8
24+
25+
class UnitType(Enum):
26+
WATT = UnitOfPower.WATT
27+
KWH = UnitOfEnergy.KILO_WATT_HOUR
28+
29+
@dataclass
30+
class EntityConfig:
31+
name : str
32+
device_class : SensorDeviceClass
33+
state_class : SensorStateClass | None
34+
unit : UnitType
35+
formatter: Callable
36+
precision: int
37+
event: str
38+
39+
FMT_INT = lambda f: int(f)
40+
FMT_WS_TO_KWH = lambda f: float(f)/3600000
41+
42+
class PowersensorHouseholdEntity(SensorEntity):
43+
"""Powersensor Virtual Household entity"""
44+
45+
should_poll = False
46+
_attr_has_entity_name = True
47+
_attr_available = True
48+
49+
_ENTITY_CONFIGS = {
50+
HouseholdMeasurements.POWER_HOME_USE : EntityConfig(
51+
"Power - Home use", SensorDeviceClass.POWER, None, UnitType.WATT, FMT_INT, 0, "home_usage"),
52+
HouseholdMeasurements.POWER_FROM_GRID : EntityConfig(
53+
"Power - From grid", SensorDeviceClass.POWER, None, UnitType.WATT, FMT_INT, 0, "from_grid"),
54+
HouseholdMeasurements.POWER_TO_GRID : EntityConfig(
55+
"Power - To grid", SensorDeviceClass.POWER, None, UnitType.WATT, FMT_INT, 0, "to_grid"),
56+
HouseholdMeasurements.POWER_SOLAR_GENERATION : EntityConfig(
57+
"Power - Solar generation", SensorDeviceClass.POWER, None, UnitType.WATT, FMT_INT, 0, "solar_generation"),
58+
59+
HouseholdMeasurements.ENERGY_HOME_USE : EntityConfig(
60+
"Energy - Home usage", SensorDeviceClass.ENERGY, SensorStateClass.TOTAL, UnitType.KWH, FMT_WS_TO_KWH, 3, "home_usage_summation"),
61+
HouseholdMeasurements.ENERGY_FROM_GRID : EntityConfig(
62+
"Energy - From grid", SensorDeviceClass.ENERGY, SensorStateClass.TOTAL, UnitType.KWH, FMT_WS_TO_KWH, 3, "from_grid_summation"),
63+
HouseholdMeasurements.ENERGY_TO_GRID : EntityConfig(
64+
"Energy - To grid", SensorDeviceClass.ENERGY, SensorStateClass.TOTAL, UnitType.KWH, FMT_WS_TO_KWH, 3, "to_grid_summation"),
65+
HouseholdMeasurements.ENERGY_SOLAR_GENERATION : EntityConfig(
66+
"Energy - Solar generation", SensorDeviceClass.ENERGY, SensorStateClass.TOTAL, UnitType.KWH, FMT_WS_TO_KWH, 3, "solar_generation_summation"),
67+
}
68+
69+
def __init__(self, vhh: VirtualHousehold, measurement_type: HouseholdMeasurements):
70+
"""Initialize the entity."""
71+
self._vhh = vhh
72+
self._config = self._ENTITY_CONFIGS[measurement_type]
73+
74+
self._attr_name = self._config.name
75+
self._attr_unique_id = f"{DOMAIN}_vhh_{self._config.event}"
76+
self._attr_device_class = self._config.device_class
77+
self._attr_state_class = self._config.state_class
78+
self._attr_native_unit_of_measurement = self._config.unit
79+
self._attr_suggested_display_precision = self._config.precision
80+
81+
@property
82+
def device_info(self) -> DeviceInfo:
83+
return {
84+
'identifiers': {(DOMAIN, "vhh")},
85+
'manufacturer': "Powersensor",
86+
'model': "Virtual",
87+
'name': "Powersensor Household View",
88+
}
89+
90+
async def async_added_to_hass(self):
91+
self._vhh.subscribe(self._config.event, self._on_event)
92+
93+
async def async_will_remove_from_hass(self):
94+
self._vhh.unsubscribe(self._config.event, self._on_event)
95+
96+
async def _on_event(self, _, msg):
97+
val = None
98+
if self._config.unit == UnitType.WATT:
99+
key = "watts"
100+
if key in msg:
101+
val = msg[key]
102+
elif self._config.unit == UnitType.KWH:
103+
key = "summation_joules"
104+
if key in msg:
105+
val = msg[key]
106+
key = "summation_resettime_utc"
107+
if key in msg:
108+
self._attr_last_reset = datetime.fromtimestamp(msg[key])
109+
if val is not None:
110+
self._attr_native_value = self._config.formatter(val)
111+
self.async_write_ha_state()

custom_components/powersensor/coordinator.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from homeassistant.config_entries import ConfigEntry
99
from homeassistant.core import HomeAssistant
1010
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
11-
from powersensor_local import PlugApi
12-
11+
from powersensor_local import PlugApi, VirtualHousehold
1312

1413
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
1514

@@ -84,6 +83,7 @@ def __init__(
8483
PlugMeasurements.SUMMATION_ENERGY: 0.0,
8584
}
8685

86+
self._vhh = VirtualHousehold(True) # FIXME needs to track whether we have seen solar or not
8787

8888
async def stop(self):
8989
"""stop listening to plug"""
@@ -104,6 +104,12 @@ async def _async_update_data(self):
104104
return self.plug_data
105105

106106
async def handle_message(self, event, message):
107+
# Drive household calculation
108+
if event == "average_power":
109+
await self._vhh.process_average_power_event(message)
110+
elif event == "summation_energy":
111+
await self._vhh.process_summation_event(message)
112+
107113
mac = message['mac']
108114
if mac in self.plug_data.keys():
109115
# handle plugs
@@ -141,6 +147,7 @@ async def handle_message(self, event, message):
141147

142148
async def handle_device_discovery(self, message):
143149
if message['mac'] not in self.sensor_data.keys():
150+
# TODO this should really check the device_type in the message
144151
self.sensor_data[message['mac']] = dict({
145152
SensorMeasurements.Battery : 0.0
146153
})

custom_components/powersensor/sensor.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from homeassistant.core import HomeAssistant
88
from homeassistant.helpers.entity_platform import AddEntitiesCallback
99

10+
from .PowersensorHouseholdEntity import HouseholdMeasurements, PowersensorHouseholdEntity
1011
from .PowersensorPlugEntity import PowersensorPlugEntity
1112
from .const import DOMAIN
1213
from .coordinator import PlugMeasurements
@@ -32,6 +33,17 @@ async def async_setup_entry(
3233
PowersensorPlugEntity(hass, plug_update_coordinator, plug, PlugMeasurements.SUMMATION_ENERGY)])
3334

3435
async_add_entities(plug_sensors, True)
36+
37+
# Register household entities
38+
vhh = plug_update_coordinator._vhh # TODO tidy up
39+
household_entities = []
40+
for measurement_type in HouseholdMeasurements:
41+
# TODO: only include to_grid/solar if have solar?
42+
# Should we dynamically register the household only in response to
43+
# getting role:house-net and role:solar messages, maybe?
44+
household_entities.append(PowersensorHouseholdEntity(vhh, measurement_type))
45+
async_add_entities(household_entities)
46+
3547
plug_update_coordinator.async_add_sensor_entities = async_add_entities
3648

3749

0 commit comments

Comments
 (0)