Skip to content

Commit 92293b8

Browse files
committed
Separate control port from regular device ports
1 parent 404546f commit 92293b8

File tree

2 files changed

+157
-121
lines changed

2 files changed

+157
-121
lines changed

qtoggleserver/zigbee2mqtt/client.py

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __init__(
9494
self._bridge_info: Optional[GenericJSONDict] = None
9595
self._pending_requests: dict[str, dict[str, Any]] = {}
9696
self._update_ports_from_device_info_task: Optional[asyncio.Task] = None
97-
self._update_ports_from_device_info_scheduled: set[Union[DevicePort, None]] = set()
97+
self._update_ports_from_device_info_scheduled: set[Union[str, None]] = set()
9898

9999
super().__init__(**kwargs)
100100

@@ -513,31 +513,32 @@ async def make_port_args(self) -> list[dict[str, Any]]:
513513
}
514514
]
515515

516-
def update_ports_from_device_info_asap(self, changed_port: Optional[DevicePort] = None) -> None:
517-
if changed_port:
518-
self.debug('will update ports from device info asap (changed port = %s)', changed_port)
516+
def update_ports_from_device_info_asap(self, changed_friendly_name: Optional[str] = None) -> None:
517+
if changed_friendly_name:
518+
self.debug('will update ports from device info asap (changed friendly name = "%s")', changed_friendly_name)
519519
else:
520520
self.debug('will update ports from device info asap')
521-
self._update_ports_from_device_info_scheduled.add(changed_port)
521+
self._update_ports_from_device_info_scheduled.add(changed_friendly_name)
522522

523523
async def _update_ports_from_device_info_loop(self) -> None:
524524
try:
525525
while True:
526526
try:
527527
if self._update_ports_from_device_info_scheduled:
528+
changed_friendly_names = set(n for n in self._update_ports_from_device_info_scheduled if n)
528529
self._update_ports_from_device_info_scheduled.clear()
529-
await self._update_ports_from_device_info()
530+
await self._update_ports_from_device_info(changed_friendly_names)
530531
except Exception:
531532
self.error('error while updating ports from device info', exc_info=True)
532533

533534
await asyncio.sleep(1)
534535
except asyncio.CancelledError:
535536
self.debug('updating ports from device info task cancelled', exc_info=True)
536537

537-
async def _update_ports_from_device_info(self) -> None:
538+
async def _update_ports_from_device_info(self, changed_friendly_names: set[str]) -> None:
538539
self.debug('updating ports from device info')
539540
port_args_list = self._port_args_from_device_info(self._device_info_by_friendly_name)
540-
ports_by_id = {p.get_initial_id(): p for p in self.get_device_ports()}
541+
ports_by_id = {p.get_initial_id(): p for p in self.get_device_ports() + self.get_control_ports()}
541542
port_args_by_id = {
542543
pa['id']: pa
543544
for pa in port_args_list
@@ -554,11 +555,7 @@ async def _update_ports_from_device_info(self) -> None:
554555
await self.remove_port(existing_id)
555556

556557
# Add all ports that don't yet exist on the server
557-
changed_friendly_names = set(
558-
p.get_device_friendly_name()
559-
for p in self._update_ports_from_device_info_scheduled
560-
if p
561-
)
558+
changed_friendly_names = set(changed_friendly_names)
562559
for new_id, port_args in port_args_by_id.items():
563560
if new_id not in ports_by_id:
564561
self.debug('new port %s detected', new_id)
@@ -661,8 +658,6 @@ def _port_args_from_device_definition(self, friendly_name: str, definition: dict
661658
control_port_args = {
662659
'driver': DeviceControlPort,
663660
'id': safe_friendly_name,
664-
'type': core_ports.TYPE_BOOLEAN,
665-
'writable': True,
666661
'device_friendly_name': friendly_name,
667662
'additional_attrdefs': {},
668663
}
@@ -674,9 +669,9 @@ def _port_args_from_device_definition(self, friendly_name: str, definition: dict
674669
for exposed_item in exposed_items:
675670
path_str = '.'.join(exposed_item['path'])
676671
is_attrdef = (
677-
exposed_item.get('category') in ('config', 'diagnostic')
678-
or exposed_item.get('type') == 'text'
679-
or exposed_item.get('storage') == 'config'
672+
exposed_item.get('category') in ('config', 'diagnostic') or
673+
exposed_item.get('type') == 'text' or
674+
exposed_item.get('storage') == 'config'
680675
)
681676

682677
if is_attrdef and any(fnmatch(path_str, pat) for pat in force_port_properties):
@@ -744,7 +739,6 @@ def _port_args_from_device_definition(self, friendly_name: str, definition: dict
744739
'unit': exposed_item.get('unit'),
745740
'min': exposed_item.get('value_min'),
746741
'max': exposed_item.get('value_max'),
747-
'additional_attrdefs': {},
748742
'device_friendly_name': friendly_name,
749743
'property_path': exposed_item['path'],
750744
'storage': exposed_item['storage'],
@@ -761,46 +755,55 @@ def _port_args_from_device_definition(self, friendly_name: str, definition: dict
761755
return [control_port_args] + list(port_args_by_id.values())
762756

763757
def get_device_ports(self, friendly_name: Optional[str] = None) -> list[DevicePort]:
758+
device_ports = [p for p in self.get_ports() if isinstance(p, DevicePort)]
764759
if friendly_name:
765-
return [
766-
p for p in self.get_ports()
767-
if isinstance(p, DevicePort) and (
768-
p.get_initial_id().startswith(f'{friendly_name}.') or
769-
p.get_initial_id() == friendly_name
770-
)
760+
device_ports = [
761+
p
762+
for p in device_ports
763+
if p.get_initial_id().startswith(f'{friendly_name}.') or p.get_initial_id() == friendly_name
771764
]
772-
else:
773-
return [p for p in self.get_ports() if isinstance(p, DevicePort)]
765+
766+
return device_ports
767+
768+
def get_control_ports(self) -> list[DeviceControlPort]:
769+
return [p for p in self.get_ports() if isinstance(p, DeviceControlPort)]
770+
771+
def get_control_port(self, friendly_name: str) -> Optional[DeviceControlPort]:
772+
safe_friendly_name = self.get_device_safe_friendly_name(friendly_name)
773+
return self.get_port(safe_friendly_name)
774774

775775
async def _maybe_trigger_port_update(self, friendly_name: str, old_properties: dict, new_properties: dict) -> None:
776+
control_port = self.get_control_port(friendly_name)
777+
if not control_port:
778+
return
779+
776780
# Gather all properties that have just changed
777781
changed_properties = (
778-
set(k for k, v in new_properties.items() if v != old_properties.get(k))
779-
| set(k for k, v in old_properties.items() if v != new_properties.get(k))
782+
set(k for k, v in new_properties.items() if v != old_properties.get(k)) |
783+
set(k for k, v in old_properties.items() if v != new_properties.get(k))
780784
)
781785

782786
# Gather all properties that represent port values
783787
device_ports = self.get_device_ports(friendly_name)
784788
value_properties = set()
785789
for port in device_ports:
786-
if not isinstance(port, DeviceControlPort):
787-
value_properties.add(port.get_property_path()[0])
790+
value_properties.add(port.get_property_path()[0])
788791

789792
# If only port value properties have changed, don't trigger unnecessary `port-update` events
790793
only_value_properties_changed = not (changed_properties - value_properties)
791794
if only_value_properties_changed:
792795
return
793796

794-
# Trigger `port-update` on all affected ports
795-
for port in device_ports:
796-
attrdefs = await port.get_additional_attrdefs()
797-
attr_properties = set(a['property_path'][0] for a in attrdefs.values() if a.get('property_path'))
798-
if not (attr_properties & changed_properties):
799-
continue
800-
port.invalidate_attrs()
801-
if port.is_enabled():
802-
await port.trigger_update()
803-
port.save_asap()
797+
attrdefs = await control_port.get_additional_attrdefs()
798+
attr_properties = set(a['property_path'][0] for a in attrdefs.values() if a.get('property_path'))
799+
if not (attr_properties & changed_properties):
800+
return
801+
802+
# Trigger `port-update` on affected control port
803+
control_port.invalidate_attrs()
804+
if control_port.is_enabled():
805+
await control_port.trigger_update()
806+
control_port.save_asap()
804807

805808

806809
from .ports import PermitJoinPort, DeviceControlPort, DevicePort # noqa: E402

0 commit comments

Comments
 (0)