|
4 | 4 |
|
5 | 5 | import asyncio |
6 | 6 | from collections.abc import Callable, Coroutine, Sequence |
7 | | -import dataclasses |
8 | 7 | from datetime import datetime, timedelta |
9 | 8 | from functools import partial |
10 | 9 | import logging |
|
45 | 44 | usb_device_from_path, # noqa: F401 |
46 | 45 | usb_device_from_port, # noqa: F401 |
47 | 46 | usb_device_matches_matcher, |
48 | | - usb_service_info_from_device, # noqa: F401 |
| 47 | + usb_service_info_from_device, |
49 | 48 | usb_unique_id_from_service_info, # noqa: F401 |
50 | 49 | ) |
51 | 50 |
|
|
59 | 58 |
|
60 | 59 | __all__ = [ |
61 | 60 | "USBCallbackMatcher", |
62 | | - "async_is_plugged_in", |
63 | 61 | "async_register_port_event_callback", |
64 | 62 | "async_register_scan_request_callback", |
65 | 63 | ] |
@@ -101,51 +99,6 @@ def async_register_port_event_callback( |
101 | 99 | return discovery.async_register_port_event_callback(callback) |
102 | 100 |
|
103 | 101 |
|
104 | | -@hass_callback |
105 | | -def async_is_plugged_in(hass: HomeAssistant, matcher: USBCallbackMatcher) -> bool: |
106 | | - """Return True is a USB device is present.""" |
107 | | - |
108 | | - vid = matcher.get("vid", "") |
109 | | - pid = matcher.get("pid", "") |
110 | | - serial_number = matcher.get("serial_number", "") |
111 | | - manufacturer = matcher.get("manufacturer", "") |
112 | | - description = matcher.get("description", "") |
113 | | - |
114 | | - if ( |
115 | | - vid != vid.upper() |
116 | | - or pid != pid.upper() |
117 | | - or serial_number != serial_number.lower() |
118 | | - or manufacturer != manufacturer.lower() |
119 | | - or description != description.lower() |
120 | | - ): |
121 | | - raise ValueError( |
122 | | - f"vid and pid must be uppercase, the rest lowercase in matcher {matcher!r}" |
123 | | - ) |
124 | | - |
125 | | - usb_discovery: USBDiscovery = hass.data[DOMAIN] |
126 | | - return any( |
127 | | - usb_device_matches_matcher( |
128 | | - USBDevice( |
129 | | - device=device, |
130 | | - vid=vid, |
131 | | - pid=pid, |
132 | | - serial_number=serial_number, |
133 | | - manufacturer=manufacturer, |
134 | | - description=description, |
135 | | - ), |
136 | | - matcher, |
137 | | - ) |
138 | | - for ( |
139 | | - device, |
140 | | - vid, |
141 | | - pid, |
142 | | - serial_number, |
143 | | - manufacturer, |
144 | | - description, |
145 | | - ) in usb_discovery.seen |
146 | | - ) |
147 | | - |
148 | | - |
149 | 102 | @hass_callback |
150 | 103 | def async_get_usb_matchers_for_device( |
151 | 104 | hass: HomeAssistant, device: USBDevice |
@@ -244,7 +197,6 @@ def __init__( |
244 | 197 | """Init USB Discovery.""" |
245 | 198 | self.hass = hass |
246 | 199 | self.usb = usb |
247 | | - self.seen: set[tuple[str, ...]] = set() |
248 | 200 | self.observer_active = False |
249 | 201 | self._request_debouncer: Debouncer[Coroutine[Any, Any, None]] | None = None |
250 | 202 | self._add_remove_debouncer: Debouncer[Coroutine[Any, Any, None]] | None = None |
@@ -393,37 +345,40 @@ def async_get_usb_matchers_for_device(self, device: USBDevice) -> list[USBMatche |
393 | 345 | async def _async_process_discovered_usb_device(self, device: USBDevice) -> None: |
394 | 346 | """Process a USB discovery.""" |
395 | 347 | _LOGGER.debug("Discovered USB Device: %s", device) |
396 | | - device_tuple = dataclasses.astuple(device) |
397 | | - if device_tuple in self.seen: |
398 | | - return |
399 | | - self.seen.add(device_tuple) |
400 | | - |
401 | 348 | matched = self.async_get_usb_matchers_for_device(device) |
402 | 349 | if not matched: |
403 | 350 | return |
404 | 351 |
|
405 | | - service_info: _UsbServiceInfo | None = None |
| 352 | + service_info = usb_service_info_from_device(device) |
406 | 353 |
|
407 | 354 | for matcher in matched: |
408 | | - if service_info is None: |
409 | | - service_info = _UsbServiceInfo( |
410 | | - device=await self.hass.async_add_executor_job( |
411 | | - get_serial_by_id, device.device |
412 | | - ), |
413 | | - vid=device.vid, |
414 | | - pid=device.pid, |
415 | | - serial_number=device.serial_number, |
416 | | - manufacturer=device.manufacturer, |
417 | | - description=device.description, |
418 | | - ) |
419 | | - |
420 | 355 | discovery_flow.async_create_flow( |
421 | 356 | self.hass, |
422 | 357 | matcher["domain"], |
423 | 358 | {"source": config_entries.SOURCE_USB}, |
424 | 359 | service_info, |
425 | 360 | ) |
426 | 361 |
|
| 362 | + async def _async_process_removed_usb_device(self, device: USBDevice) -> None: |
| 363 | + """Process a USB removal.""" |
| 364 | + _LOGGER.debug("Removed USB Device: %s", device) |
| 365 | + matched = self.async_get_usb_matchers_for_device(device) |
| 366 | + if not matched: |
| 367 | + return |
| 368 | + |
| 369 | + service_info = usb_service_info_from_device(device) |
| 370 | + |
| 371 | + for matcher in matched: |
| 372 | + for flow in self.hass.config_entries.flow.async_progress_by_init_data_type( |
| 373 | + _UsbServiceInfo, |
| 374 | + lambda flow_service_info: flow_service_info == service_info, |
| 375 | + ): |
| 376 | + if matcher["domain"] != flow["handler"]: |
| 377 | + continue |
| 378 | + |
| 379 | + _LOGGER.debug("Aborting existing flow %s", flow["flow_id"]) |
| 380 | + self.hass.config_entries.flow.async_abort(flow["flow_id"]) |
| 381 | + |
427 | 382 | async def _async_process_ports(self, usb_devices: Sequence[USBDevice]) -> None: |
428 | 383 | """Process each discovered port.""" |
429 | 384 | _LOGGER.debug("USB devices: %r", usb_devices) |
@@ -464,7 +419,10 @@ async def _async_process_ports(self, usb_devices: Sequence[USBDevice]) -> None: |
464 | 419 | except Exception: |
465 | 420 | _LOGGER.exception("Error in USB port event callback") |
466 | 421 |
|
467 | | - for usb_device in filtered_usb_devices: |
| 422 | + for usb_device in removed_devices: |
| 423 | + await self._async_process_removed_usb_device(usb_device) |
| 424 | + |
| 425 | + for usb_device in added_devices: |
468 | 426 | await self._async_process_discovered_usb_device(usb_device) |
469 | 427 |
|
470 | 428 | @hass_callback |
|
0 commit comments