Skip to content

Commit 236b92f

Browse files
authored
Feature/add async step user (#14)
* add async_step_user to support kits that don't discover powersensor * Virtual household will add only when mains is present (consumption only if mains only and consumption and production if solar/mains)--no virtual household with solar only * Added a lock/status to prevent double firing of solar * Added a lock/status to prevent double firing of mains --------- Authored-by: Lake Bookman <lbookman@dius.com.au>
1 parent 1288a2b commit 236b92f

File tree

5 files changed

+116
-44
lines changed

5 files changed

+116
-44
lines changed

custom_components/powersensor/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
8585
if entry.version == 1:
8686
# Move device info into subkey, add with_solar key
8787
devices = { **entry.data }
88-
new_data = { 'devices': devices, 'with_solar': False, 'roles': {} }
88+
new_data = { 'devices': devices, 'with_solar': False, 'with_mains': False, 'roles': {} }
8989
hass.config_entries.async_update_entry(entry, data=new_data, version=2, minor_version=1)
9090

9191
_LOGGER.debug("Upgrading config to %s.%s", entry.version, entry.minor_version)

custom_components/powersensor/config_flow.py

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,35 +91,14 @@ async def async_step_reconfigure(self, user_input: dict | None = None)->FlowResu
9191
}
9292
)
9393

94-
async def async_step_zeroconf(
95-
self, discovery_info: zeroconf.ZeroconfServiceInfo
96-
) -> ConfigFlowResult:
97-
"""Handle zeroconf discovery."""
98-
host = discovery_info.host
99-
port = discovery_info.port or DEFAULT_PORT
100-
display_name = _extract_device_name(discovery_info) or ""
101-
properties = discovery_info.properties or {}
102-
mac = None
103-
if "id" in properties:
104-
mac = properties["id"].strip()
105-
106-
plug_data = {'host' : host,'port' : port, 'display_name' : display_name,
107-
'mac': mac, 'name': discovery_info.name}
108-
94+
async def _common_setup(self):
10995
if DOMAIN not in self.hass.data:
11096
self.hass.data[DOMAIN] = {}
11197

11298
discovered_plugs_key = "discovered_plugs"
11399
if discovered_plugs_key not in self.hass.data[DOMAIN]:
114100
self.hass.data[DOMAIN][discovered_plugs_key] = {}
115101

116-
if mac in self.hass.data[DOMAIN][discovered_plugs_key].keys():
117-
_LOGGER.debug("Mac found existing in data!")
118-
else:
119-
self.hass.data[DOMAIN][discovered_plugs_key][mac] = plug_data
120-
121-
122-
123102
# register a unique id for the single power sensor entry
124103
await self.async_set_unique_id(DOMAIN)
125104

@@ -128,19 +107,48 @@ async def async_step_zeroconf(
128107
_LOGGER.warning("Aborting - found existing entry!")
129108
return self.async_abort(reason="already_configured")
130109

110+
131111
display_name = f"⚡ Powersensor 🔌\n"
132112
self.context.update({
133113
"title_placeholders": {
134114
"name": display_name
135115
}
136116
})
137-
return await self.async_step_discovery_confirm()
138117

139-
async def async_step_discovery_confirm(
118+
async def async_step_user(
140119
self, user_input: dict[str, Any] | None = None
141120
) -> ConfigFlowResult:
121+
"""Handle zeroconf discovery."""
122+
await self._common_setup()
123+
return await self.async_step_manual_confirm()
124+
125+
126+
async def async_step_zeroconf(
127+
self, discovery_info: zeroconf.ZeroconfServiceInfo
128+
) -> ConfigFlowResult:
129+
"""Handle zeroconf discovery."""
130+
await self._common_setup()
131+
discovered_plugs_key = "discovered_plugs"
132+
host = discovery_info.host
133+
port = discovery_info.port or DEFAULT_PORT
134+
display_name = _extract_device_name(discovery_info) or ""
135+
properties = discovery_info.properties or {}
136+
mac = None
137+
if "id" in properties:
138+
mac = properties["id"].strip()
139+
140+
plug_data = {'host' : host,'port' : port, 'display_name' : display_name,
141+
'mac': mac, 'name': discovery_info.name}
142+
143+
144+
if mac in self.hass.data[DOMAIN][discovered_plugs_key].keys():
145+
_LOGGER.debug("Mac found existing in data!")
146+
else:
147+
self.hass.data[DOMAIN][discovered_plugs_key][mac] = plug_data
142148

143-
"""Confirm discovery."""
149+
return await self.async_step_discovery_confirm()
150+
151+
async def async_step_confirm(self, step_id :str, user_input: dict[str, Any] | None = None):
144152
if user_input is not None:
145153
_LOGGER.debug(self.hass.data[DOMAIN]["discovered_plugs"])
146154
return self.async_create_entry(
@@ -151,4 +159,16 @@ async def async_step_discovery_confirm(
151159
'roles': {},
152160
}
153161
)
154-
return self.async_show_form(step_id="discovery_confirm")
162+
return self.async_show_form(step_id=step_id)
163+
164+
async def async_step_discovery_confirm(
165+
self, user_input: dict[str, Any] | None = None
166+
) -> ConfigFlowResult:
167+
return await self.async_step_confirm(step_id="discovery_confirm", user_input=user_input)
168+
169+
170+
async def async_step_manual_confirm(
171+
self, user_input: dict[str, Any] | None = None
172+
) -> ConfigFlowResult:
173+
174+
return await self.async_step_confirm(step_id="manual_confirm", user_input=user_input)

custom_components/powersensor/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
CREATE_SENSOR_SIGNAL = f"{DOMAIN}_create_sensor"
1010
DATA_UPDATE_SIGNAL_FMT_MAC_EVENT = f"{DOMAIN}_data_update_%s_%s"
1111
HAVE_SOLAR_SENSOR_SIGNAL = f"{DOMAIN}_have_solar_sensor"
12+
HAVE_MAINS_SENSOR_SIGNAL = f"{DOMAIN}_have_mains_sensor"
1213
ROLE_UPDATE_SIGNAL = f"{DOMAIN}_update_role"
1314
PLUG_ADDED_TO_HA_SIGNAL = f"{DOMAIN}_plug_added_to_homeassistant"
1415
SENSOR_ADDED_TO_HA_SIGNAL = f"{DOMAIN}_sensor_added_to_homeassistant"

custom_components/powersensor/sensor.py

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Sensor platform for the integration."""
22
from __future__ import annotations
33

4+
import asyncio
45
import copy
56
import logging
67

@@ -11,18 +12,24 @@
1112

1213
from . import PowersensorMessageDispatcher
1314
from .PlugMeasurements import PlugMeasurements
14-
from .PowersensorHouseholdEntity import HouseholdMeasurements, PowersensorHouseholdEntity, ConsumptionMeasurements, \
15-
ProductionMeasurements
15+
from .PowersensorHouseholdEntity import (
16+
HouseholdMeasurements,
17+
PowersensorHouseholdEntity,
18+
ConsumptionMeasurements,
19+
ProductionMeasurements,
20+
)
1621
from .PowersensorPlugEntity import PowersensorPlugEntity
1722
from .PowersensorSensorEntity import PowersensorSensorEntity
1823
from .SensorMeasurements import SensorMeasurements
19-
from .const import (CREATE_PLUG_SIGNAL,
24+
from .const import (
25+
CREATE_PLUG_SIGNAL,
2026
CREATE_SENSOR_SIGNAL,
2127
DATA_UPDATE_SIGNAL_FMT_MAC_EVENT,
2228
HAVE_SOLAR_SENSOR_SIGNAL,
2329
PLUG_ADDED_TO_HA_SIGNAL,
2430
ROLE_UPDATE_SIGNAL,
2531
SENSOR_ADDED_TO_HA_SIGNAL,
32+
HAVE_MAINS_SENSOR_SIGNAL,
2633
)
2734

2835
_LOGGER = logging.getLogger(__name__)
@@ -37,6 +44,10 @@ async def async_setup_entry(
3744
vhh = entry.runtime_data["vhh"]
3845
dispatcher: PowersensorMessageDispatcher = entry.runtime_data['dispatcher']
3946

47+
entry.runtime_data['vhh_solar_create_lock'] = asyncio.Lock()
48+
entry.runtime_data['vhh_solar_create_finished'] = False
49+
entry.runtime_data['vhh_mains_create_lock'] = asyncio.Lock()
50+
entry.runtime_data['vhh_mains_create_finished'] = False
4051

4152
plug_role = "appliance"
4253

@@ -77,9 +88,17 @@ async def handle_role_update(mac_address: str, new_role: str):
7788
if new_role == 'solar':
7889
new_data['with_solar'] = True # Remember for next time we start
7990
persist_entry = True
80-
async_dispatcher_send(hass, HAVE_SOLAR_SENSOR_SIGNAL)
91+
if new_data.get('with_mains', False):
92+
async_dispatcher_send(hass, HAVE_SOLAR_SENSOR_SIGNAL)
93+
94+
if new_role == 'house-net':
95+
new_data['with_mains'] = True # Remember for next time we start
96+
persist_entry = True
97+
async_dispatcher_send(hass, HAVE_MAINS_SENSOR_SIGNAL)
98+
if new_data.get('with_solar', False):
99+
async_dispatcher_send(hass, HAVE_SOLAR_SENSOR_SIGNAL)
81100

82-
# TODO: for house-net/solar <-> we'd need to change the entities too
101+
# TODO: for house-net/solar/appliance <-> water we'd need to change the entities too
83102

84103
if persist_entry:
85104
hass.config_entries.async_update_entry(entry, data=new_data)
@@ -114,10 +133,13 @@ async def handle_discovered_sensor(sensor_mac: str, sensor_role: str):
114133
]
115134
async_add_entities(new_sensors, True)
116135
async_dispatcher_send(hass, SENSOR_ADDED_TO_HA_SIGNAL, sensor_mac, sensor_role)
117-
118-
if sensor_role == "solar":
136+
mains_present = entry.data.get('with_mains', False)
137+
if sensor_role == "solar" and mains_present:
119138
async_dispatcher_send(hass, HAVE_SOLAR_SENSOR_SIGNAL)
120139

140+
if sensor_role == "mains":
141+
async_dispatcher_send(hass, HAVE_MAINS_SENSOR_SIGNAL)
142+
121143
entry.async_on_unload(
122144
async_dispatcher_connect(
123145
hass, CREATE_SENSOR_SIGNAL, handle_discovered_sensor
@@ -131,21 +153,46 @@ async def handle_discovered_sensor(sensor_mac: str, sensor_role: str):
131153
await handle_discovered_sensor(mac, role)
132154

133155
# Register the virtual household entities
134-
household_entities = []
135-
for measurement_type in ConsumptionMeasurements:
136-
household_entities.append(PowersensorHouseholdEntity(vhh, measurement_type))
137-
async_add_entities(household_entities)
156+
157+
158+
async def add_mains_to_virtual_household():
159+
async with entry.runtime_data['vhh_mains_create_lock']:
160+
if entry.runtime_data['vhh_mains_create_finished']:
161+
return
162+
_LOGGER.debug("Enabling mains components in virtual household")
163+
household_entities = []
164+
for measurement_type in ConsumptionMeasurements:
165+
household_entities.append(PowersensorHouseholdEntity(vhh, measurement_type))
166+
async_add_entities(household_entities)
167+
entry.runtime_data['vhh_mains_create_finished'] = True
168+
169+
with_mains = entry.data.get('with_mains', False)
170+
if with_mains:
171+
await add_mains_to_virtual_household()
172+
else:
173+
entry.async_on_unload(
174+
async_dispatcher_connect(
175+
hass, HAVE_MAINS_SENSOR_SIGNAL, add_mains_to_virtual_household
176+
)
177+
)
138178

139179
async def add_solar_to_virtual_household():
140-
_LOGGER.debug("Enabling solar components in virtual household")
141-
solar_household_entities = []
142-
for solar_measurement_type in ProductionMeasurements:
143-
solar_household_entities.append(PowersensorHouseholdEntity(vhh, solar_measurement_type))
180+
async with entry.runtime_data['vhh_solar_create_lock']:
181+
if entry.runtime_data['vhh_solar_create_finished']:
182+
return
183+
184+
_LOGGER.debug("Enabling solar components in virtual household")
185+
solar_household_entities = []
186+
for solar_measurement_type in ProductionMeasurements:
187+
solar_household_entities.append(PowersensorHouseholdEntity(vhh, solar_measurement_type))
188+
189+
async_add_entities(solar_household_entities)
190+
entry.runtime_data['vhh_solar_create_finished'] = True
144191

145-
async_add_entities(solar_household_entities)
146192

147193
with_solar = entry.data.get('with_solar', False)
148-
if with_solar:
194+
with_mains = entry.data.get('with_mains', False)
195+
if with_solar and with_mains:
149196
await add_solar_to_virtual_household()
150197
else:
151198
entry.async_on_unload(

custom_components/powersensor/translations/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
"description": "Do you want to add Powersensor to Home Assistant?",
77
"title": "Powersensor plugs discovered"
88
},
9+
"manual_confirm": {
10+
"description": "No Powersensor plugs were discovered on your network. You can still add the integration and if plugs are later discovered they will be added to Home Assistant. Do you want to add Powersensor to Home Assistant?",
11+
"title": "No Powersensor plugs discovered"
12+
},
913
"reconfigure" : {
1014
"title" : "Update sensor roles",
1115
"description": "Note: Roles provided by sensors will override user settings"

0 commit comments

Comments
 (0)