Skip to content

Commit 6c44fcd

Browse files
Feature/optional user set role (#7)
enable user assignment of roles to sensors after initial configuration --------- Co-authored-by: Jade Mattsson <github@frozenlogic.org>
1 parent ecac7ce commit 6c44fcd

File tree

6 files changed

+63
-67
lines changed

6 files changed

+63
-67
lines changed

custom_components/powersensor/PowersensorHouseholdEntity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def device_info(self) -> DeviceInfo:
9393
'identifiers': {(DOMAIN, "vhh")},
9494
'manufacturer': "Powersensor",
9595
'model': "Virtual",
96-
'name': "Powersensor Household View",
96+
'name': "Powersensor Household View 🏠",
9797
}
9898

9999
async def async_added_to_hass(self):

custom_components/powersensor/PowersensorSensorEntity.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
from .PowersensorEntity import PowersensorEntity
88
from .SensorMeasurements import SensorMeasurements
9-
from .const import DOMAIN
9+
from .const import DOMAIN, SENSOR_NAME_FORMAT
1010

1111
import logging
1212
_LOGGER = logging.getLogger(__name__)
1313

1414

1515
_config = {
16+
# TODO: change names to translation keys
1617
SensorMeasurements.Battery: {
1718
"name": "Battery Level",
1819
"device_class": SensorDeviceClass.BATTERY,
@@ -77,20 +78,17 @@ def _ensure_matching_prefix(self):
7778

7879
def _rename_based_on_role(self):
7980
if self._device_name == self._default_device_name():
80-
if self.role =='house-net':
81-
self._device_name = "Powersensor Mains Sensor"
82-
self._ensure_matching_prefix()
83-
return True
84-
elif self.role == 'water':
85-
self._device_name = "Powersensor Water Sensor"
86-
self._ensure_matching_prefix()
87-
return True
88-
elif self.role == 'solar':
89-
self._device_name = "Powersensor Solar Sensor"
81+
if self.role =='house-net' or self.role == "water" or self.role == "solar":
82+
role2name = {
83+
"house-net": "Powersensor Mains Sensor ⚡",
84+
"solar": "Powersensor Solar Sensor ⚡",
85+
"water": "Powersensor Water Sensor 💧",
86+
}
87+
self._device_name = role2name[self.role]
9088
self._ensure_matching_prefix()
9189
return True
9290
return False
9391

9492
def _default_device_name(self):
95-
return f'Powersensor Sensor (ID: {self._mac})'
93+
return SENSOR_NAME_FORMAT % self._mac
9694

custom_components/powersensor/config_flow.py

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44
import voluptuous as vol
55
from homeassistant import config_entries
66
from homeassistant.config_entries import ConfigFlowResult
7+
from homeassistant.data_entry_flow import FlowResult
8+
from homeassistant.helpers.dispatcher import async_dispatcher_send
79
from homeassistant.helpers.service_info import zeroconf
8-
from homeassistant.const import CONF_HOST, CONF_PORT
10+
from homeassistant.helpers.selector import selector
911

10-
from .const import DEFAULT_PORT, DOMAIN
12+
from .const import DEFAULT_PORT, DOMAIN, SENSOR_NAME_FORMAT
1113

1214
def _extract_device_name(discovery_info) -> str:
1315
"""Extract a user-friendly device name from zeroconf info."""
1416
properties = discovery_info.properties or {}
1517

1618
if "id" in properties:
17-
return f'🔌 Mac({properties["id"].strip()})'
19+
return f"🔌 Mac({properties["id"].strip()})"
20+
1821
# Fall back to cleaning up the service name
1922
name = discovery_info.name or ""
2023

@@ -39,13 +42,6 @@ def _extract_device_name(discovery_info) -> str:
3942

4043
_LOGGER = logging.getLogger(__name__)
4144

42-
STEP_USER_DATA_SCHEMA = vol.Schema(
43-
{
44-
vol.Required(CONF_HOST): str,
45-
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
46-
}
47-
)
48-
4945

5046
class PowersensorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
5147
"""Handle a config flow."""
@@ -55,38 +51,45 @@ class PowersensorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
5551
def __init__(self):
5652
"""Initialize the config flow."""
5753

58-
# async def async_step_user(self, user_input=None) -> ConfigFlowResult:
59-
# """Handle manual user setup."""
60-
# errors = {}
61-
#
62-
# if user_input is not None:
63-
# # Validate the host/connection here if needed
64-
# try:
65-
# # Set unique ID based on host to prevent duplicates
66-
# await self.async_set_unique_id(user_input[CONF_HOST])
67-
# self._abort_if_unique_id_configured()
68-
#
69-
# return self.async_create_entry(
70-
# title=user_input[CONF_NAME],
71-
# data=user_input
72-
# )
73-
# except Exception as ex:
74-
# _LOGGER.error("Error validating configuration: %s", ex)
75-
# errors["base"] = "cannot_connect"
76-
#
77-
# data_schema = vol.Schema(
78-
# {
79-
# vol.Required(CONF_NAME): cv.string,
80-
# vol.Required(CONF_HOST): cv.string,
81-
# vol.Optional(CONF_PORT, default=80): cv.port,
82-
# }
83-
# )
84-
#
85-
# return self.async_show_form(
86-
# step_id="user",
87-
# data_schema=data_schema,
88-
# errors=errors
89-
# )
54+
async def async_step_reconfigure(self, user_input: dict | None = None)->FlowResult:
55+
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
56+
dispatcher = entry.runtime_data["dispatcher"]
57+
58+
mac2name = { mac: SENSOR_NAME_FORMAT % mac for mac in dispatcher.sensors }
59+
60+
unknown = "<unknown>"
61+
if user_input is not None:
62+
name2mac = { name: mac for mac, name in mac2name.items() }
63+
for name, role in user_input.items():
64+
mac = name2mac.get(name)
65+
if role == unknown:
66+
role = None
67+
_LOGGER.debug(f"Applying {role} to {mac}")
68+
async_dispatcher_send(self.hass, f"{DOMAIN}_update_role", mac, role)
69+
return self.async_abort(reason="Roles successfully applied!")
70+
71+
sensor_roles = {}
72+
for sensor_mac in dispatcher.sensors:
73+
role = entry.data.get('roles', {}).get(sensor_mac, unknown)
74+
sel = selector({
75+
"select": {
76+
"options": [
77+
# Note: these strings are NOT subject to translation
78+
"house-net", "solar", "water", "appliance", unknown
79+
],
80+
"mode": "dropdown",
81+
}
82+
})
83+
sensor_roles[vol.Optional(mac2name[sensor_mac], description={"suggested_value": role})] = sel
84+
85+
return self.async_show_form(
86+
step_id="reconfigure",
87+
data_schema=vol.Schema(sensor_roles),
88+
description_placeholders={
89+
"device_count": str(len(sensor_roles)),
90+
"docs_url" : "https://dius.github.io/homeassistant-powersensor/data.html#virtual-household"
91+
}
92+
)
9093

9194
async def async_step_zeroconf(
9295
self, discovery_info: zeroconf.ZeroconfServiceInfo

custom_components/powersensor/const.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@
55
DEFAULT_PORT = 49476
66
DEFAULT_SCAN_INTERVAL = 30
77
POWER_SENSOR_UPDATE_SIGNAL = f"{DOMAIN}_sensor"
8-
# Configuration keys
9-
CONF_HOST = "host"
10-
CONF_PORT = "port"
8+
9+
SENSOR_NAME_FORMAT = "Powersensor Sensor (ID: %s) ⚡"

custom_components/powersensor/translations/en.json

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@
22
"config": {
33
"title" : "Powersensor",
44
"step": {
5-
"user": {
6-
"description": "Enter the connection details for your device",
7-
"data": {
8-
"host": "Host",
9-
"port": "Port"
10-
}
11-
},
125
"discovery_confirm": {
136
"description": "Do you want to add Powersensor to Home Assistant?",
147
"title": "Powersensor plugs discovered"
8+
},
9+
"reconfigure" : {
10+
"title" : "Update sensor roles",
11+
"description": "Note: Roles provided by sensors will override user settings"
1512
}
1613
},
1714
"error": {
18-
"cannot_connect": "Failed to connect",
1915
"unknown": "Unexpected error"
2016
},
2117
"abort": {

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# Project information
1212
project = "homeassistant-powersensor"
13-
copyright = "2025, DiUS"
13+
copyright = "2025, Powersensor"
1414
author = "Powersensor Team!"
1515

1616
html_favicon = "_static/powersensor-logo.png"

0 commit comments

Comments
 (0)