From c1e5945a42dc8eb29757dc817e4dcf01ad0a5426 Mon Sep 17 00:00:00 2001 From: Jade Mattsson Date: Fri, 17 Oct 2025 13:37:46 +1100 Subject: [PATCH 1/3] Reenable current readings, but hide them (and voltage) by default. Note that this requires a fresh config directory as HomeAssistant doesn't actually forget entities even after they've been deleted. --- .../powersensor/PlugMeasurements.py | 3 +-- .../powersensor/PowersensorEntity.py | 2 ++ .../powersensor/PowersensorPlugEntity.py | 16 +++++++++------- .../powersensor/PowersensorSensorEntity.py | 1 - custom_components/powersensor/sensor.py | 2 ++ 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/custom_components/powersensor/PlugMeasurements.py b/custom_components/powersensor/PlugMeasurements.py index cf2e90e..f663a1d 100644 --- a/custom_components/powersensor/PlugMeasurements.py +++ b/custom_components/powersensor/PlugMeasurements.py @@ -1,10 +1,9 @@ from enum import Enum - class PlugMeasurements(Enum): WATTS = 1 VOLTAGE = 2 APPARENT_CURRENT = 3 ACTIVE_CURRENT = 4 - REACTIVE_CURRENT =5 + REACTIVE_CURRENT = 5 SUMMATION_ENERGY = 6 diff --git a/custom_components/powersensor/PowersensorEntity.py b/custom_components/powersensor/PowersensorEntity.py index c9710e6..d3f6547 100644 --- a/custom_components/powersensor/PowersensorEntity.py +++ b/custom_components/powersensor/PowersensorEntity.py @@ -40,6 +40,8 @@ def __init__(self, hass: HomeAssistant, mac : str, self._attr_native_unit_of_measurement = config["unit"] self._attr_device_info = self.device_info self._attr_suggested_display_precision = config["precision"] + self._attr_entity_registry_visible_default = config['visible'] if 'visible' in config.keys() else True + self._signal = f"{POWER_SENSOR_UPDATE_SIGNAL}_{self._mac}_{config['event']}" if 'state_class' in config.keys(): self._attr_state_class = config['state_class'] diff --git a/custom_components/powersensor/PowersensorPlugEntity.py b/custom_components/powersensor/PowersensorPlugEntity.py index 6048882..f4c0dcc 100644 --- a/custom_components/powersensor/PowersensorPlugEntity.py +++ b/custom_components/powersensor/PowersensorPlugEntity.py @@ -12,7 +12,6 @@ _LOGGER = logging.getLogger(__name__) - _config = { PlugMeasurements.WATTS: { "name": "Power", @@ -28,7 +27,8 @@ "unit": UnitOfElectricPotential.VOLT, "precision": 2, 'event': 'average_power_components', - 'message_key': 'volts' + 'message_key': 'volts', + 'visible': False, }, PlugMeasurements.APPARENT_CURRENT: { "name": "Apparent Current", @@ -36,15 +36,17 @@ "unit": UnitOfElectricCurrent.AMPERE, "precision": 2, 'event': 'average_power_components', - 'message_key': 'apparent_current' + 'message_key': 'apparent_current', + 'visible': False, }, PlugMeasurements.ACTIVE_CURRENT: { - "name": "Current", #"Active Current" + "name": "Active Current", "device_class": SensorDeviceClass.CURRENT, "unit": UnitOfElectricCurrent.AMPERE, "precision": 2, 'event': 'average_power_components', - 'message_key': 'active_current' + 'message_key': 'active_current', + 'visible': False, }, PlugMeasurements.REACTIVE_CURRENT: { "name": "Reactive Current", @@ -52,7 +54,8 @@ "unit": UnitOfElectricCurrent.AMPERE, "precision": 2, 'event': 'average_power_components', - 'message_key': 'reactive_current' + 'message_key': 'reactive_current', + 'visible': False, }, PlugMeasurements.SUMMATION_ENERGY: { "name": "Total Energy", @@ -86,7 +89,6 @@ def device_info(self) -> DeviceInfo: 'manufacturer': "Powersensor", 'model': self._model, 'name': self._device_name, - # "via_device": # if we use this, can it be updated dynamically? } def _default_device_name(self) -> str: diff --git a/custom_components/powersensor/PowersensorSensorEntity.py b/custom_components/powersensor/PowersensorSensorEntity.py index 8654ed8..140e508 100644 --- a/custom_components/powersensor/PowersensorSensorEntity.py +++ b/custom_components/powersensor/PowersensorSensorEntity.py @@ -62,7 +62,6 @@ def device_info(self) -> DeviceInfo: 'manufacturer': "Powersensor", 'model': self._model, 'name': self._device_name , - # "via_device": # if we use this, can it be updated dynamically? } def _ensure_matching_prefix(self): diff --git a/custom_components/powersensor/sensor.py b/custom_components/powersensor/sensor.py index b148758..47ddfe4 100644 --- a/custom_components/powersensor/sensor.py +++ b/custom_components/powersensor/sensor.py @@ -32,7 +32,9 @@ async def async_setup_entry( async def create_plug(plug_mac_address: str): this_plug_sensors = [PowersensorPlugEntity(hass, plug_mac_address, PlugMeasurements.WATTS), PowersensorPlugEntity(hass, plug_mac_address, PlugMeasurements.VOLTAGE), + PowersensorPlugEntity(hass, plug_mac_address, PlugMeasurements.APPARENT_CURRENT), PowersensorPlugEntity(hass, plug_mac_address, PlugMeasurements.ACTIVE_CURRENT), + PowersensorPlugEntity(hass, plug_mac_address, PlugMeasurements.REACTIVE_CURRENT), PowersensorPlugEntity(hass, plug_mac_address, PlugMeasurements.SUMMATION_ENERGY)] async_add_entities(this_plug_sensors, True) From c2cd700d5056752f099b49ae819c74473691c22c Mon Sep 17 00:00:00 2001 From: Jade Mattsson Date: Fri, 17 Oct 2025 14:21:52 +1100 Subject: [PATCH 2/3] Fix persisting with_solar, and use it on startup. --- custom_components/powersensor/__init__.py | 42 ++++++++++++++++++-- custom_components/powersensor/config_flow.py | 9 +++-- custom_components/powersensor/sensor.py | 4 +- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/custom_components/powersensor/__init__.py b/custom_components/powersensor/__init__.py index fb9cd3b..e3fd26c 100644 --- a/custom_components/powersensor/__init__.py +++ b/custom_components/powersensor/__init__.py @@ -10,11 +10,26 @@ from .PowersensorDiscoveryService import PowersensorDiscoveryService from .PowersensorMessageDispatcher import PowersensorMessageDispatcher +from .config_flow import PowersensorConfigFlow from .const import DOMAIN _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [Platform.SENSOR] +# +# config entry.data structure (version 2.1): +# { +# devices = { +# mac = { +# name =, +# display_name =, +# mac =, +# host =, +# port =, +# } +# with_solar =, +# } +# async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up integration from a config entry.""" @@ -30,14 +45,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await zeroconf_service.start() # Establish our virtual household - vhh = VirtualHousehold(False) + vhh = VirtualHousehold(entry.data['with_solar']) - - # TODO: can we move the dispatcher into the entry.runtime_data dict? + # Set up message dispatcher dispatcher = PowersensorMessageDispatcher(hass, vhh) - for mac, network_info in entry.data.items(): + for mac, network_info in entry.data['devices'].items(): await dispatcher.enqueue_plug_for_adding(network_info) + entry.runtime_data = { "vhh": vhh , "dispatcher" : dispatcher, "zeroconf" : zeroconf_service} await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -52,5 +67,24 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if "zeroconf" in entry.runtime_data .keys(): await entry.runtime_data["zeroconf"].stop() + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + +async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Migrate old config entry.""" + _LOGGER.debug("Upgrading config from %s.%s", entry.version, entry.minor_version) + if entry.version > PowersensorConfigFlow.VERSION: + # Downgrade from future version + return False + + if entry.version == 1: + # Move device info into subkey, add with_solar key + devices = { **entry.data } + new_data = { 'devices': devices, 'with_solar': False } + hass.config_entries.async_update_entry(entry, data=new_data, version=2, minor_version=1) + + _LOGGER.debug("Upgrading config to %s.%s", entry.version, entry.minor_version) + return True + diff --git a/custom_components/powersensor/config_flow.py b/custom_components/powersensor/config_flow.py index 95face4..851b0d1 100644 --- a/custom_components/powersensor/config_flow.py +++ b/custom_components/powersensor/config_flow.py @@ -50,7 +50,7 @@ def _extract_device_name(discovery_info) -> str: class PowersensorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow.""" - VERSION = 1 + VERSION = 2 def __init__(self): """Initialize the config flow.""" @@ -142,6 +142,9 @@ async def async_step_discovery_confirm( _LOGGER.debug(self.hass.data[DOMAIN]["discovered_plugs"]) return self.async_create_entry( title="Powersensor", - data=self.hass.data[DOMAIN]["discovered_plugs"] + data={ + 'devices': self.hass.data[DOMAIN]["discovered_plugs"], + 'with_solar': False, + } ) - return self.async_show_form(step_id="discovery_confirm") \ No newline at end of file + return self.async_show_form(step_id="discovery_confirm") diff --git a/custom_components/powersensor/sensor.py b/custom_components/powersensor/sensor.py index 47ddfe4..8db3298 100644 --- a/custom_components/powersensor/sensor.py +++ b/custom_components/powersensor/sensor.py @@ -56,7 +56,9 @@ async def handle_discovered_plug(plug_mac_address: str, host: str, port: int, na async def handle_discovered_sensor(sensor_mac: str, sensor_role: str): if sensor_role == 'solar': - entry.runtime_data["with_solar"] = True # Remember for next time we start + new_data = { **entry.data } + new_data['with_solar'] = True # Remember for next time we start + hass.config_entries.async_update_entry(entry, data=new_data) new_sensors = [ PowersensorSensorEntity(hass, sensor_mac, SensorMeasurements.Battery), From cfb784254a02d2e4603ef758aa5c6c6f43fb007f Mon Sep 17 00:00:00 2001 From: Jade Mattsson Date: Fri, 17 Oct 2025 16:02:25 +1100 Subject: [PATCH 3/3] Updated docs. --- docs/source/data.rst | 48 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/docs/source/data.rst b/docs/source/data.rst index e6e79e8..790a2ba 100644 --- a/docs/source/data.rst +++ b/docs/source/data.rst @@ -7,16 +7,19 @@ household. Virtual Household ------------------ Built on top of the sensors, the API provided by `powersensor_local `_ -provides a ``virtual household``. This view is captures the key data most users want to capture at the household level. +provides a "virtual household". This view captures the key data most users want to capture at the household level. This includes -* Energy imported from the grid * Total home energy usage -* Energy exported to the grid (from solar , if available) +* Energy imported from the grid +* Energy exported to the grid (from solar) * Total solar production as well as the corresponding instantaneous power consumption/production. +For installations lacking solar, the generation related entities will not +be available. + .. note:: Powersensor deals with *net* readings, meaning that the energy import and @@ -36,17 +39,38 @@ The household readings update as sensor data becomes available. Plugs ----- -Each plug exposes 6 entities reflecting the different measurements made by the plug these are +Each plug exposes several entities reflecting the different measuremens +made by the plug. By default only two are made visible: * Power -* Total Energy Consumption -* Current -* Voltage +* Total Energy + +The remaining entities are: -The current displayed in the integration represents active current only, but the Powersensor -api exposes reactive and apparent current as well. Future release plans intend to expose -all available data, with reactive and apparent current being optional during the configuration -step for the integration. +* Volts +* Active Current +* Reactive Current +* Apparent Current + +These are of secondary importance and usefulness, and hence aren't visible +by default. They can be selectively made visible by going to Settings > +Devices & services > Powersensor and selecting the plug, then clicking on +the desired entity and then the gear on the following screen. Toggle the +Visible option there. + +The Volts entity shows the mains voltage as seen at that particular plug. Due +to voltage drop in wires, each plug is likely to show a slightly different +mains voltage. + +Simplified, the Apparent Current entity shows the effective current going +through the plug. This is the current that may lead to a breaker tripping if +it gets excessive. The Active and Reactive measurements are the components +of it, and are likely of little interest outside of curiousity. + +.. note:: + It is more common to hear of Active, Reactive and Apparent *Power*, and + the plug's different current measurements should not be confused for power + measurements. The plug readings typically update every second. @@ -78,4 +102,4 @@ Any of the plug, sensor or virtual household entities can be used in automation workflows. To exercise control of other devices in your household, first install any relevant integrations for those devices. Then follow the usual Home Assistant steps for setting up rules: -Settings->Automations & Scenes and +Create Automation +Settings >Automations & Scenes and +Create Automation