Skip to content

Commit 94fccb9

Browse files
committed
Adding tracking of zeroconf names, various errors patched.
1 parent 3187190 commit 94fccb9

File tree

7 files changed

+179
-162
lines changed

7 files changed

+179
-162
lines changed

custom_components/powersensor/PowersensorEntity.py

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import timedelta
2-
from typing import Callable
2+
from typing import Callable, Union
33

44
from homeassistant.components.sensor import SensorEntity
55
from homeassistant.core import HomeAssistant, callback
@@ -19,7 +19,7 @@
1919
class PowersensorEntity(SensorEntity):
2020
"""Powersensor Plug Class--designed to handle all measurements of the plug--perhaps less expressive"""
2121
def __init__(self, hass: HomeAssistant, mac : str,
22-
get_config_by_measurement_type: Callable[[SensorMeasurements|PlugMeasurements], dict],
22+
input_config: dict[Union[SensorMeasurements|PlugMeasurements], dict],
2323
measurement_type: SensorMeasurements|PlugMeasurements, timeout_seconds: int = 60):
2424
"""Initialize the sensor."""
2525
self.role = None
@@ -28,13 +28,13 @@ def __init__(self, hass: HomeAssistant, mac : str,
2828
self._hass = hass
2929
self._mac = mac
3030
self._model = f"PowersensorDevice"
31-
self._device_name = f'PowersensorDevice MAC address: ({self._mac})'
31+
self._device_name = f'Powersensor Device (MAC address: {self._mac})'
3232
self._measurement_name= None
3333
self._remove_unavailability_tracker = None
3434
self._timeout = timedelta(seconds=timeout_seconds) # Adjust as needed
3535

3636
self.measurement_type = measurement_type
37-
config = get_config_by_measurement_type(measurement_type)
37+
config = input_config[measurement_type]
3838
self._attr_unique_id = f"powersensor_{mac}_{measurement_type}"
3939
self._attr_device_class = config["device_class"]
4040
self._attr_native_unit_of_measurement = config["unit"]
@@ -91,43 +91,41 @@ def _rename_based_on_role(self):
9191
@callback
9292
def _handle_update(self, event, message):
9393
"""handle pushed data."""
94+
95+
# event is not presently used, but is passed to maintain flexibility for future development
96+
9497
name_updated = False
95-
if event =="remove_sensor":
96-
self.async_remove()
97-
elif event =="mark_unavailable":
98-
self._has_recently_received_update_message = False
99-
else:
100-
self._has_recently_received_update_message = True
101-
if not self.role:
102-
if 'role' in message.keys():
103-
self.role = message['role']
104-
name_updated = self._rename_based_on_role()
105-
106-
107-
if self._message_key in message.keys():
108-
if self._message_callback:
109-
self._attr_native_value = self._message_callback( message[self._message_key])
110-
else:
111-
self._attr_native_value = message[self._message_key]
112-
self._schedule_unavailable()
113-
114-
if name_updated:
115-
device_registry = dr.async_get(self.hass)
116-
device = device_registry.async_get_device(
117-
identifiers={(DOMAIN, self._mac)}
98+
self._has_recently_received_update_message = True
99+
if not self.role:
100+
if 'role' in message.keys():
101+
self.role = message['role']
102+
name_updated = self._rename_based_on_role()
103+
104+
105+
if self._message_key in message.keys():
106+
if self._message_callback:
107+
self._attr_native_value = self._message_callback( message[self._message_key])
108+
else:
109+
self._attr_native_value = message[self._message_key]
110+
self._schedule_unavailable()
111+
112+
if name_updated:
113+
device_registry = dr.async_get(self.hass)
114+
device = device_registry.async_get_device(
115+
identifiers={(DOMAIN, self._mac)}
116+
)
117+
118+
if device and device.name != self._device_name:
119+
# Update the device name
120+
device_registry.async_update_device(
121+
device.id,
122+
name=self._device_name
118123
)
119124

120-
if device and device.name != self._device_name:
121-
# Update the device name
122-
device_registry.async_update_device(
123-
device.id,
124-
name=self._device_name
125-
)
126-
127-
entity_registry = er.async_get(self.hass)
128-
entity_registry.async_update_entity(
129-
self.entity_id,
130-
name = self._attr_name
131-
)
132-
self.async_write_ha_state()
125+
entity_registry = er.async_get(self.hass)
126+
entity_registry.async_update_entity(
127+
self.entity_id,
128+
name = self._attr_name
129+
)
130+
self.async_write_ha_state()
133131

custom_components/powersensor/PowersensorMessageDispatacher.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def __init__(self, hass: HomeAssistant, vhh: VirtualHousehold):
1616
self._vhh = vhh
1717
self.plugs = dict()
1818
self._known_plugs = set()
19+
self._known_plug_names = dict()
1920
self.sensors = dict()
2021
self.on_start_sensor_queue = dict()
2122
self._unsubscribe_from_signals = [
@@ -43,7 +44,8 @@ def __init__(self, hass: HomeAssistant, vhh: VirtualHousehold):
4344

4445
async def enqueue_plug_for_adding(self, network_info: dict):
4546
_LOGGER.debug(f"Adding to plug processing queue: {network_info}")
46-
await self._plug_added_queue.add((network_info['mac'], network_info['host'], network_info['port']))
47+
await self._plug_added_queue.add((network_info['mac'], network_info['host'],
48+
network_info['port'], network_info['name']))
4749

4850
async def process_plug_queue(self):
4951
"""Start the background task if not already running."""
@@ -53,22 +55,22 @@ async def process_plug_queue(self):
5355
self._monitor_add_plug_queue = self._hass.async_create_background_task(self._monitor_plug_queue(), name="plug_queue_monitor")
5456
_LOGGER.debug("Background task started")
5557

56-
def _plug_has_been_seen(self, mac_address)->bool:
57-
return mac_address in self.plugs or mac_address in self._known_plugs
58+
def _plug_has_been_seen(self, mac_address, name)->bool:
59+
return mac_address in self.plugs or mac_address in self._known_plugs or name in self._known_plug_names
5860

5961
async def _monitor_plug_queue(self):
6062
"""The actual background task loop."""
6163
try:
6264
while not self._stop_task and self._plug_added_queue:
6365
queue_snapshot = await self._plug_added_queue.copy()
64-
for mac_address, host, port in queue_snapshot:
65-
if not self._plug_has_been_seen(mac_address):
66+
for mac_address, host, port, name in queue_snapshot:
67+
if not self._plug_has_been_seen(mac_address, name):
6668
async_dispatcher_send(self._hass, f"{DOMAIN}_create_plug",
67-
mac_address, host, port)
69+
mac_address, host, port, name)
6870
else:
6971
_LOGGER.debug(f"Plug: {mac_address} has already been created as an entity in Home Assistant."
7072
f" Skipping and flushing from queue.")
71-
await self._plug_added_queue.remove((mac_address, host, port))
73+
await self._plug_added_queue.remove((mac_address, host, port, name))
7274

7375

7476
await asyncio.sleep(5)
@@ -94,11 +96,12 @@ async def stop_processing_plug_queue(self):
9496
_LOGGER.debug("Background task stopped")
9597
self._monitor_add_plug_queue = None
9698

97-
def _create_api(self, mac_address, ip, port):
98-
_LOGGER.info(f"Adding API for mac={mac_address}, ip={ip}, port={port}")
99+
def _create_api(self, mac_address, ip, port, name):
100+
_LOGGER.info(f"Creating API for mac={mac_address}, ip={ip}, port={port}")
99101
api = PlugApi(mac=mac_address, ip=ip, port=port)
100102
self.plugs[mac_address] = api
101103
self._known_plugs.add(mac_address)
104+
self._known_plug_names[name] = mac_address
102105
known_evs = [
103106
'exception',
104107
'average_flow',
@@ -117,7 +120,9 @@ def _create_api(self, mac_address, ip, port):
117120
api.connect()
118121

119122
def add_api(self, network_info):
120-
self._create_api(mac_address=network_info['mac'], ip=network_info['host'], port=network_info['port'])
123+
_LOGGER.debug("Manually adding API, this could cause API's and entities to get out of sync")
124+
self._create_api(mac_address=network_info['mac'], ip=network_info['host'],
125+
port=network_info['port'], name=network_info['name'])
121126

122127

123128
async def handle_message(self, event: str, message: dict):
@@ -152,49 +157,59 @@ async def disconnect(self):
152157
def _acknowledge_sensor_added_to_homeassistant(self,mac, role):
153158
self.sensors[mac] = role
154159

155-
@callback
156-
async def _acknowledge_plug_added_to_homeassistant(self, mac_address, host, port):
157-
self._create_api(mac_address, host, port)
158-
await self._plug_added_queue.remove((mac_address, host, port))
160+
async def _acknowledge_plug_added_to_homeassistant(self, mac_address, host, port, name):
161+
_LOGGER.info(f"Adding new API for mac={mac_address}, ip={host}, port={port}")
162+
self._create_api(mac_address, host, port, name)
163+
await self._plug_added_queue.remove((mac_address, host, port, name))
159164

160-
@callback
161165
async def _plug_added(self, info):
162166
_LOGGER.debug(f" Request to add plug received: {info}")
163167
network_info = dict()
164168
network_info['mac'] = info['properties'][b'id'].decode('utf-8')
165169
network_info['host'] = info['addresses'][0]
166170
network_info['port'] = info['port']
171+
network_info['name'] = info['name']
167172

168173
if self._safe_to_process_plug_queue:
169174
await self.enqueue_plug_for_adding(network_info)
170175
await self.process_plug_queue()
171176
else:
172177
await self.enqueue_plug_for_adding(network_info)
173178

174-
@callback
175179
async def _plug_updated(self, info):
176180
_LOGGER.debug(f" Request to update plug received: {info}")
177181
mac = info['properties'][b'id'].decode('utf-8')
178182
host = info['addresses'][0]
179183
port = info['port']
184+
name = info['name']
185+
180186
if mac in self.plugs:
181-
self.plugs[mac].disconnect()
187+
current_api: PlugApi = self.plugs[mac]
188+
if current_api._listener._ip == host and current_api._listener._port == port:
189+
_LOGGER.info(f"Request to update plug with mac {mac} does not alter ip from existing API."
190+
f"IP still {host} and port is {port}. Skipping update...")
191+
return
192+
await current_api.disconnect()
182193

183194
if mac in self._known_plugs:
184-
self._create_api(mac, host, port)
195+
self._create_api(mac, host, port, name)
185196
else:
186197
network_info = dict()
187198
network_info['mac'] = mac
188199
network_info['host'] = host
189200
network_info['port'] = port
201+
network_info['name'] = name
190202
await self.enqueue_plug_for_adding(network_info)
191203
await self.process_plug_queue()
192204

193-
@callback
194-
def _plug_remove(self,name, info):
205+
async def _plug_remove(self,name, info):
195206
_LOGGER.debug(f" Request to delete plug received: {info}")
196-
mac = info['properties'][b'id'].decode('utf-8')
197-
if mac in self.plugs:
198-
self.plugs[mac].disconnect()
207+
if name in self._known_plug_names:
208+
mac = self._known_plug_names[name]
209+
if mac in self.plugs:
210+
await self.plugs[mac].disconnect()
199211

200-
del self.plugs[mac]
212+
del self.plugs[mac]
213+
else:
214+
_LOGGER.warning(f"Received request to delete api for gateway with name [{name}], but this name"
215+
f"is not associated with an existing PlugAPI. Ignoring...")

custom_components/powersensor/PowersensorPlugEntity.py

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,78 +12,84 @@
1212
_LOGGER = logging.getLogger(__name__)
1313

1414

15-
def get_config(measurement_type: PlugMeasurements)->dict:
16-
_config = {
17-
PlugMeasurements.WATTS: {
18-
"name": "Power",
19-
"device_class": SensorDeviceClass.POWER,
20-
"unit": UnitOfPower.WATT,
21-
"precision": 1,
22-
'event': 'average_power',
23-
'message_key': 'watts'
24-
},
25-
PlugMeasurements.VOLTAGE: {
26-
"name": "Volts",
27-
"device_class": SensorDeviceClass.VOLTAGE,
28-
"unit": UnitOfElectricPotential.VOLT,
29-
"precision": 2,
30-
'event': 'average_power_components',
31-
'message_key': 'volts'
32-
},
33-
PlugMeasurements.APPARENT_CURRENT: {
34-
"name": "Apparent Current",
35-
"device_class": SensorDeviceClass.CURRENT,
36-
"unit": UnitOfElectricCurrent.AMPERE,
37-
"precision": 2,
38-
'event': 'average_power_components',
39-
'message_key': 'apparent_current'
40-
},
41-
PlugMeasurements.ACTIVE_CURRENT: {
42-
"name": "Current", #"Active Current"
43-
"device_class": SensorDeviceClass.CURRENT,
44-
"unit": UnitOfElectricCurrent.AMPERE,
45-
"precision": 2,
46-
'event': 'average_power_components',
47-
'message_key': 'active_current'
48-
},
49-
PlugMeasurements.REACTIVE_CURRENT: {
50-
"name": "Reactive Current",
51-
"device_class": SensorDeviceClass.CURRENT,
52-
"unit": UnitOfElectricCurrent.AMPERE,
53-
"precision": 2,
54-
'event': 'average_power_components',
55-
'message_key': 'reactive_current'
56-
},
57-
PlugMeasurements.SUMMATION_ENERGY: {
58-
"name": "Total Energy",
59-
"device_class": SensorDeviceClass.ENERGY,
60-
"unit": UnitOfEnergy.KILO_WATT_HOUR,
61-
"precision": 2,
62-
"state_class": SensorStateClass.TOTAL,
63-
'event': 'summation_energy',
64-
'message_key': 'summation_joules',
65-
'callback' : lambda v : v/3600000.0
66-
},
67-
}
6815

69-
return _config[measurement_type]
16+
_config = {
17+
PlugMeasurements.WATTS: {
18+
"name": "Power",
19+
"device_class": SensorDeviceClass.POWER,
20+
"unit": UnitOfPower.WATT,
21+
"precision": 1,
22+
'event': 'average_power',
23+
'message_key': 'watts'
24+
},
25+
PlugMeasurements.VOLTAGE: {
26+
"name": "Volts",
27+
"device_class": SensorDeviceClass.VOLTAGE,
28+
"unit": UnitOfElectricPotential.VOLT,
29+
"precision": 2,
30+
'event': 'average_power_components',
31+
'message_key': 'volts'
32+
},
33+
PlugMeasurements.APPARENT_CURRENT: {
34+
"name": "Apparent Current",
35+
"device_class": SensorDeviceClass.CURRENT,
36+
"unit": UnitOfElectricCurrent.AMPERE,
37+
"precision": 2,
38+
'event': 'average_power_components',
39+
'message_key': 'apparent_current'
40+
},
41+
PlugMeasurements.ACTIVE_CURRENT: {
42+
"name": "Current", #"Active Current"
43+
"device_class": SensorDeviceClass.CURRENT,
44+
"unit": UnitOfElectricCurrent.AMPERE,
45+
"precision": 2,
46+
'event': 'average_power_components',
47+
'message_key': 'active_current'
48+
},
49+
PlugMeasurements.REACTIVE_CURRENT: {
50+
"name": "Reactive Current",
51+
"device_class": SensorDeviceClass.CURRENT,
52+
"unit": UnitOfElectricCurrent.AMPERE,
53+
"precision": 2,
54+
'event': 'average_power_components',
55+
'message_key': 'reactive_current'
56+
},
57+
PlugMeasurements.SUMMATION_ENERGY: {
58+
"name": "Total Energy",
59+
"device_class": SensorDeviceClass.ENERGY,
60+
"unit": UnitOfEnergy.KILO_WATT_HOUR,
61+
"precision": 2,
62+
"state_class": SensorStateClass.TOTAL,
63+
'event': 'summation_energy',
64+
'message_key': 'summation_joules',
65+
'callback' : lambda v : v/3600000.0
66+
},
67+
}
68+
69+
7070

7171
class PowersensorPlugEntity(PowersensorEntity):
7272
"""Powersensor Plug Class--designed to handle all measurements of the plug--perhaps less expressive"""
7373
def __init__(self, hass: HomeAssistant, mac_address: str, measurement_type: PlugMeasurements):
7474
"""Initialize the sensor."""
75-
super().__init__(hass, mac_address, get_config, measurement_type)
75+
super().__init__(hass, mac_address, _config, measurement_type)
7676
self._model = f"PowersensorPlug"
7777
self.measurement_type = measurement_type
78-
config = get_config(measurement_type)
79-
self._attr_name = f"🔌 MAC address: ({self._mac}) {config['name']}"
78+
config = _config[measurement_type]
79+
self._device_name = self._default_device_name()
80+
self._attr_name = f"{self._device_name } {config['name']}"
8081

8182
@property
8283
def device_info(self) -> DeviceInfo:
8384
return {
8485
'identifiers': {(DOMAIN, self._mac)},
8586
'manufacturer': "Powersensor",
8687
'model': self._model,
87-
'name': f'🔌 MAC address: ({self._mac})',
88+
'name': self._device_name,
8889
# "via_device": # if we use this, can it be updated dynamically?
8990
}
91+
92+
def _default_device_name(self) -> str:
93+
# f'🔌 MAC address: ({self._mac})'
94+
return f"Powersensor Plug (MAC Address: {self._mac})"
95+

0 commit comments

Comments
 (0)