Skip to content

Commit 1d4a271

Browse files
committed
trying this...again...
1 parent d30eee1 commit 1d4a271

File tree

5 files changed

+104
-51
lines changed

5 files changed

+104
-51
lines changed

custom_components/powersensor/PowersensorDiscoveryService.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import asyncio
22
from typing import Optional
3-
import logging
43

54
from homeassistant.core import HomeAssistant
65
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
@@ -9,6 +8,7 @@
98

109
from .const import DOMAIN
1110

11+
import logging
1212
_LOGGER = logging.getLogger(__name__)
1313

1414

@@ -20,6 +20,7 @@ def __init__(self, hass: HomeAssistant, debounce_timeout: float = 60):
2020
self._debounce_seconds = debounce_timeout
2121

2222
def add_service(self, zc, type_, name):
23+
self.cancel_any_pending_removal(name, "request to add")
2324
info = self.__add_plug(zc, type_, name)
2425
if info:
2526
asyncio.run_coroutine_threadsafe(
@@ -30,6 +31,7 @@ def add_service(self, zc, type_, name):
3031
async def _async_service_add(self, *args):
3132
async_dispatcher_send(self._hass, f"{DOMAIN}_zeroconf_add_plug", *args)
3233

34+
3335
async def _async_delayed_remove(self, name):
3436
"""Actually process the removal after delay."""
3537
try:
@@ -57,29 +59,30 @@ def remove_service(self, zc, type_, name):
5759
return
5860

5961
_LOGGER.info(f"Scheduling removal for {name}")
60-
self._pending_removals[name] = asyncio.create_task(self._async_delayed_remove(name))
61-
62+
self._pending_removals[name] = asyncio.run_coroutine_threadsafe(
63+
self._async_delayed_remove(name),
64+
self._hass.loop
65+
)
66+
print(self._pending_removals)
6267

6368
async def _async_service_remove(self, *args):
6469
async_dispatcher_send(self._hass, f"{DOMAIN}_zeroconf_remove_plug", *args)
6570

6671
def update_service(self, zc, type_, name):
72+
self.cancel_any_pending_removal(name, "request to update")
6773
info = self.__add_plug(zc, type_, name)
6874
if info:
6975
asyncio.run_coroutine_threadsafe(
70-
self._async_service_update(self._plugs[name]),
76+
self._async_service_update( self._plugs[name]),
7177
self._hass.loop
7278
)
7379

7480
async def _async_service_update(self, *args):
81+
# remove from pending tasks if update received
7582
async_dispatcher_send(self._hass, f"{DOMAIN}_zeroconf_update_plug", *args)
7683

7784
def __add_plug(self, zc, type_, name):
7885
info = zc.get_service_info(type_, name)
79-
task = self._pending_removals.pop(name, None)
80-
if task:
81-
task.cancel()
82-
_LOGGER.info(f"Cancelled pending removal for {name}")
8386
if info:
8487
self._plugs[name] = {'type': type_,
8588
'name': name,
@@ -90,6 +93,11 @@ def __add_plug(self, zc, type_, name):
9093
}
9194
return info
9295

96+
def cancel_any_pending_removal(self, name, source):
97+
task = self._pending_removals.pop(name, None)
98+
if task:
99+
task.cancel()
100+
_LOGGER.info(f"Cancelled pending removal for {name} by {source}.")
93101

94102
class PowersensorDiscoveryService:
95103
def __init__(self, hass: HomeAssistant, service_type: str = "_powersensor._tcp.local."):

custom_components/powersensor/PowersensorHouseholdEntity.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class HouseholdMeasurements(Enum):
3434
HouseholdMeasurements.ENERGY_TO_GRID,
3535
HouseholdMeasurements.ENERGY_SOLAR_GENERATION
3636
]
37+
3738
@dataclass
3839
class EntityConfig:
3940
name : str

custom_components/powersensor/PowersensorMessageDispatacher.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import datetime
23
import logging
34

45
from homeassistant.core import HomeAssistant, callback
@@ -7,11 +8,11 @@
78
from powersensor_local import PlugApi, VirtualHousehold
89

910
from custom_components.powersensor.AsyncSet import AsyncSet
10-
from custom_components.powersensor.const import POWER_SENSOR_UPDATE_SIGNAL, DOMAIN, DEFAULT_PORT
11+
from custom_components.powersensor.const import POWER_SENSOR_UPDATE_SIGNAL, DOMAIN
1112

1213
_LOGGER = logging.getLogger(__name__)
1314
class PowersensorMessageDispatcher:
14-
def __init__(self, hass: HomeAssistant, vhh: VirtualHousehold,debounce_timeout: float = 60):
15+
def __init__(self, hass: HomeAssistant, vhh: VirtualHousehold, debounce_timeout: float = 60):
1516
self._hass = hass
1617
self._vhh = vhh
1718
self.plugs = dict()
@@ -21,6 +22,11 @@ def __init__(self, hass: HomeAssistant, vhh: VirtualHousehold,debounce_timeout:
2122
self.on_start_sensor_queue = dict()
2223
self._pending_removals = {}
2324
self._debounce_seconds = debounce_timeout
25+
self.has_solar = False
26+
self._virtual_household_has_been_setup = False
27+
self._last_request_to_notify_about_solar = datetime.datetime(1970,1,1,0,0,0)
28+
self._solar_request_limit = datetime.timedelta(seconds = 10)
29+
2430
self._unsubscribe_from_signals = [
2531
async_dispatcher_connect(self._hass,
2632
f"{DOMAIN}_sensor_added_to_homeassistant",
@@ -37,6 +43,9 @@ def __init__(self, hass: HomeAssistant, vhh: VirtualHousehold,debounce_timeout:
3743
async_dispatcher_connect(self._hass,
3844
f"{DOMAIN}_plug_added_to_homeassistant",
3945
self._acknowledge_plug_added_to_homeassistant),
46+
async_dispatcher_connect(self._hass,
47+
f"{DOMAIN}_solar_added_to_virtual_household",
48+
self._acknowledge_solar_added_to_virtual_household),
4049
]
4150

4251
self._monitor_add_plug_queue = None
@@ -98,6 +107,21 @@ async def stop_processing_plug_queue(self):
98107
_LOGGER.debug("Background task stopped")
99108
self._monitor_add_plug_queue = None
100109

110+
111+
async def stop_pending_removal_tasks(self):
112+
"""Stop the background removal tasks."""
113+
for k in range(len(self._pending_removals)):
114+
if self._pending_removals[k] and not self._pending_removals[k].done():
115+
self._pending_removals[k].cancel()
116+
try:
117+
await self._pending_removals[k]
118+
except asyncio.CancelledError:
119+
pass
120+
_LOGGER.debug("Background removal task stopped")
121+
self._pending_removals[k] = None
122+
self._pending_removals = []
123+
124+
101125
def _create_api(self, mac_address, ip, port, name):
102126
_LOGGER.info(f"Creating API for mac={mac_address}, ip={ip}, port={port}")
103127
api = PlugApi(mac=mac_address, ip=ip, port=port)
@@ -126,28 +150,36 @@ def add_api(self, network_info):
126150
self._create_api(mac_address=network_info['mac'], ip=network_info['host'],
127151
port=network_info['port'], name=network_info['name'])
128152

129-
130-
async def handle_message(self, event: str, message: dict):
131-
mac = message['mac']
153+
def cancel_any_pending_removal(self, mac, source):
132154
task = self._pending_removals.pop(mac, None)
133155
if task:
134156
task.cancel()
135-
_LOGGER.info(f"Cancelled pending removal for {mac} by new message received from plug.")
157+
_LOGGER.info(f"Cancelled pending removal for {mac} by {source}.")
158+
159+
async def handle_message(self, event: str, message: dict):
160+
mac = message['mac']
161+
role = message.get('role', None)
162+
self.cancel_any_pending_removal(mac, "new message received from plug")
136163

137164
if mac not in self.plugs.keys():
138165
if mac not in self.sensors:
139-
role = None
140-
if 'role' in message:
166+
if role is not None:
141167
self.on_start_sensor_queue[mac] = role
142-
role = message['role']
143168
async_dispatcher_send(self._hass, f"{DOMAIN}_create_sensor", mac, role)
144169

145170
# Feed the household calculations
146171
if event == 'average_power':
147172
await self._vhh.process_average_power_event(message)
148173
elif event == 'summation_energy':
149174
await self._vhh.process_summation_event(message)
150-
175+
if role == 'solar':
176+
self.has_solar = True
177+
if not self._virtual_household_has_been_setup:
178+
new_time = datetime.datetime.now()
179+
if self._last_request_to_notify_about_solar + self._solar_request_limit <new_time:
180+
self._last_request_to_notify_about_solar = new_time
181+
_LOGGER.debug("Notifying integration that solar is present.")
182+
async_dispatcher_send(self._hass, f"{DOMAIN}_solar_sensor_detected")
151183
async_dispatcher_send(self._hass, f"{POWER_SENSOR_UPDATE_SIGNAL}_{mac}_{event}", event, message)
152184

153185
async def disconnect(self):
@@ -159,11 +191,17 @@ async def disconnect(self):
159191
unsubscribe()
160192

161193
await self.stop_processing_plug_queue()
194+
await self.stop_pending_removal_tasks()
162195

163196
@callback
164197
def _acknowledge_sensor_added_to_homeassistant(self,mac, role):
165198
self.sensors[mac] = role
166199

200+
@callback
201+
def _acknowledge_solar_added_to_virtual_household(self, success):
202+
_LOGGER.debug("Solar has been added to virtual household.")
203+
self._virtual_household_has_been_setup = success
204+
167205
async def _acknowledge_plug_added_to_homeassistant(self, mac_address, host, port, name):
168206
_LOGGER.info(f"Adding new API for mac={mac_address}, ip={host}, port={port}")
169207
self._create_api(mac_address, host, port, name)
@@ -174,15 +212,11 @@ async def _plug_added(self, info):
174212
network_info = dict()
175213
mac = info['properties'][b'id'].decode('utf-8')
176214
network_info['mac'] = mac
215+
self.cancel_any_pending_removal(mac, "request to add plug")
177216
network_info['host'] = info['addresses'][0]
178217
network_info['port'] = info['port']
179218
network_info['name'] = info['name']
180219

181-
task = self._pending_removals.pop(mac, None)
182-
if task:
183-
task.cancel()
184-
_LOGGER.info(f"Cancelled pending removal for {mac} by request to add api.")
185-
186220
if self._safe_to_process_plug_queue:
187221
await self.enqueue_plug_for_adding(network_info)
188222
await self.process_plug_queue()
@@ -192,13 +226,11 @@ async def _plug_added(self, info):
192226
async def _plug_updated(self, info):
193227
_LOGGER.debug(f" Request to update plug received: {info}")
194228
mac = info['properties'][b'id'].decode('utf-8')
229+
self.cancel_any_pending_removal(mac, "request to update plug")
195230
host = info['addresses'][0]
196231
port = info['port']
197232
name = info['name']
198-
task = self._pending_removals.pop(mac, None)
199-
if task:
200-
task.cancel()
201-
_LOGGER.info(f"Cancelled pending removal for {mac} by request to update api.")
233+
202234
if mac in self.plugs:
203235
current_api: PlugApi = self.plugs[mac]
204236
if current_api._listener._ip == host and current_api._listener._port == port:
@@ -218,6 +250,7 @@ async def _plug_updated(self, info):
218250
await self.enqueue_plug_for_adding(network_info)
219251
await self.process_plug_queue()
220252

253+
221254
async def _schedule_plug_removal(self, name, info):
222255
_LOGGER.debug(f" Request to delete plug received: {info}")
223256
if name in self._known_plug_names:
@@ -228,7 +261,10 @@ async def _schedule_plug_removal(self, name, info):
228261
return
229262

230263
_LOGGER.info(f"Scheduling removal for {name}")
231-
self._pending_removals[name] = asyncio.create_task(self._delayed_plug_remove(name,mac))
264+
self._pending_removals[mac] = self._hass.async_create_background_task(
265+
self._delayed_plug_remove(name,mac),
266+
name = f"Removal-Task-For-{name}"
267+
)
232268
else:
233269
_LOGGER.warning(f"Received request to delete api for gateway with name [{name}], but this name"
234270
f"is not associated with an existing PlugAPI. Ignoring...")
@@ -242,8 +278,8 @@ async def _delayed_plug_remove(self, name, mac):
242278
del self.plugs[mac]
243279
del self._known_plug_names[name]
244280
except asyncio.CancelledError:
245-
# Task was cancelled because service came back
246-
_LOGGER.info(f"Request to remove plug {mac} was canceled by request to update, add plug or new message.")
281+
# Task was canceled because service came back
282+
_LOGGER.info(f"Request to remove plug {mac} was cancelled by request to update, add plug or new message.")
247283

248284
# Either way were done with this task
249-
self._pending_removals.pop(name, None)
285+
self._pending_removals.pop(name, None)

custom_components/powersensor/__init__.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2121

2222
hass.data.setdefault(DOMAIN, {})
2323
hass.data[DOMAIN][entry.entry_id] = {}
24-
my_data = hass.data[DOMAIN][entry.entry_id]
2524

2625
integration = await async_get_integration(hass, DOMAIN)
2726
manifest = integration.manifest
2827

2928
# Establish create the zeroconf discovery service
30-
zeroconf_listener = PowersensorDiscoveryService(hass, manifest["zeroconf"][0])
31-
await zeroconf_listener.start()
29+
zeroconf_service= PowersensorDiscoveryService(hass, manifest["zeroconf"][0])
30+
await zeroconf_service.start()
3231

3332
# Establish our virtual household
34-
vhh = VirtualHousehold(my_data["with_solar"] if "with_solar" in my_data else False)
35-
33+
vhh = VirtualHousehold(False)
34+
3635

3736
# TODO: can we move the dispatcher into the entry.runtime_data dict?
3837
dispatcher = PowersensorMessageDispatcher(hass, vhh)
3938
for mac, network_info in entry.data.items():
4039
await dispatcher.enqueue_plug_for_adding(network_info)
41-
entry.runtime_data = {"vhh": vhh, "zeroconf": zeroconf_listener, "dispatcher": dispatcher}
40+
41+
entry.runtime_data = { "vhh": vhh , "dispatcher" : dispatcher, "zeroconf" : zeroconf_service}
4242
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
4343
return True
4444

@@ -47,11 +47,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
4747
"""Unload a config entry."""
4848
_LOGGER.debug("Started unloading for %s", entry.entry_id)
4949
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
50-
if entry.runtime_data:
51-
if "dispatcher" in entry.runtime_data.keys():
52-
await entry.runtime_data["dispatcher"].disconnect()
53-
if "zeroconf" in entry.runtime_data.keys():
54-
await entry.runtime_data["zeroconf"].stop()
50+
if "dispatcher" in entry.runtime_data .keys():
51+
await entry.runtime_data["dispatcher"].disconnect()
52+
if "zeroconf" in entry.runtime_data .keys():
53+
await entry.runtime_data["zeroconf"].stop()
5554

5655
return unload_ok
5756

custom_components/powersensor/sensor.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
from . import PowersensorMessageDispatcher
1212
from .PlugMeasurements import PlugMeasurements
13-
from .PowersensorHouseholdEntity import ConsumptionMeasurements, ProductionMeasurements, PowersensorHouseholdEntity
13+
from .PowersensorHouseholdEntity import HouseholdMeasurements, PowersensorHouseholdEntity, ConsumptionMeasurements, \
14+
ProductionMeasurements
1415
from .PowersensorPlugEntity import PowersensorPlugEntity
1516
from .PowersensorSensorEntity import PowersensorSensorEntity
1617
from .SensorMeasurements import SensorMeasurements
@@ -25,8 +26,7 @@ async def async_setup_entry(
2526
) -> None:
2627
"""Set up the Powersensor sensors."""
2728
vhh = entry.runtime_data["vhh"]
28-
my_data = hass.data[DOMAIN][entry.entry_id]
29-
dispatcher: PowersensorMessageDispatcher = entry.runtime_data['dispatcher']
29+
dispatcher: PowersensorMessageDispatcher = entry.runtime_data['dispatcher']
3030

3131

3232
async def create_plug(plug_mac_address: str):
@@ -54,7 +54,7 @@ async def handle_discovered_plug(plug_mac_address: str, host: str, port: int, na
5454

5555
async def handle_discovered_sensor(sensor_mac: str, sensor_role: str):
5656
if sensor_role == 'solar':
57-
my_data["with_solar"] = True # Remember for next time we start
57+
entry.runtime_data["with_solar"] = True # Remember for next time we start
5858

5959
new_sensors = [
6060
PowersensorSensorEntity(hass, sensor_mac, SensorMeasurements.Battery),
@@ -76,15 +76,24 @@ async def handle_discovered_sensor(sensor_mac: str, sensor_role: str):
7676
for mac, role in dispatcher.on_start_sensor_queue.items():
7777
await handle_discovered_sensor(mac, role)
7878

79+
async def add_solar_to_virtual_household():
80+
_LOGGER.debug("Received request to add solar to virtual household")
81+
solar_household_entities = []
82+
for solar_measurement_type in ProductionMeasurements:
83+
solar_household_entities.append(PowersensorHouseholdEntity(vhh, solar_measurement_type))
84+
85+
async_add_entities(solar_household_entities)
86+
async_dispatcher_send(hass, f"{DOMAIN}_solar_added_to_virtual_household", True)
87+
88+
entry.async_on_unload(
89+
async_dispatcher_connect(
90+
hass, f"{DOMAIN}_solar_sensor_detected", add_solar_to_virtual_household
91+
)
92+
)
7993
# Register the virtual household entities
8094
household_entities = []
8195
for measurement_type in ConsumptionMeasurements:
8296
household_entities.append(PowersensorHouseholdEntity(vhh, measurement_type))
83-
84-
85-
for measurement_type in ProductionMeasurements:
86-
household_entities.append(PowersensorHouseholdEntity(vhh, measurement_type))
87-
8897
async_add_entities(household_entities)
8998

9099
async_dispatcher_send(hass, f"{DOMAIN}_setup_complete", True)

0 commit comments

Comments
 (0)