././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1731838012.4680002
usb_monitor-1.23/ 0000755 0000765 0000024 00000000000 14716340074 012667 5 ustar 00haru staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717225473.0
usb_monitor-1.23/LICENSE 0000644 0000765 0000024 00000002054 14626544001 013671 0 ustar 00haru staff MIT License
Copyright (c) 2023 Eric CaƱas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1731838012.4676502
usb_monitor-1.23/PKG-INFO 0000644 0000765 0000024 00000027631 14716340074 013775 0 ustar 00haru staff Metadata-Version: 2.1
Name: usb-monitor
Version: 1.23
Summary: USBMonitor is an easy-to-use cross-platform library for USB device monitoring that simplifies tracking of connections, disconnections, and examination of connected device attributes on Windows, Linux and MacOs, freeing the user from platform-specific details or incompatibilities.
Home-page: https://github.com/Eric-Canas/USBMonitor
Author: Eric-Canas
Author-email: eric@ericcanas.com
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Intended Audience :: Developers
Classifier: Topic :: System :: Hardware
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Operating System
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyudev; platform_system == "Linux"
Requires-Dist: pywin32; platform_system == "Windows"
Requires-Dist: wmi; platform_system == "Windows"
# USBMonitor
**USBMonitor** is a versatile **cross-platform** library that simplifies **USB device monitoring** for _Windows_, _Linux_ and _MacOS_ systems. It enables developers to effortlessly track device **connections**, **disconnections**, and access to all connected device **attributes**.
With **USBMonitor**, developers can stay up-to-date with any changes in the connected USB devices, allowing them to **trigger specific actions** whenever a USB device is connected or disconnected. By ensuring **consistent functionality across various operating systems**, **USBMonitor** removes the need to address platform-specific quirks, inconsistencies, or incompatibilities, resulting in a smooth and efficient USB device management experience. The uniformity in functionality significantly enhances **code compatibility**, minimizing the risk of **code issues** or **unexpected breaks** when moving between platforms.
At its core, **USBMonitor** utilizes pyudev (for Linux environments), WMI (for Windows environments), and the I/O Registry (for MacOs environments). Handling all the low-level intricacies and translating OS-specific information to ensure consistency across all systems.
## Installation
To install **USBMonitor**, simply run:
```bash
pip install usb-monitor
```
## Usage
Using **USBMonitor** is both simple and straight-forward. In most cases, you'll just want to start the [monitoring _Daemon_](#usbmonitorstart_monitoringon_connect--none-on_disconnect--none-check_every_seconds--05), defining the `on_connect` and `on_disconnect` callback functions to manage events when a USB device connects or disconnects. Here's a basic example:
```python
from usbmonitor import USBMonitor
from usbmonitor.attributes import ID_MODEL, ID_MODEL_ID, ID_VENDOR_ID
device_info_str = lambda device_info: f"{device_info[ID_MODEL]} ({device_info[ID_MODEL_ID]} - {device_info[ID_VENDOR_ID]})"
# Define the `on_connect` and `on_disconnect` callbacks
on_connect = lambda device_id, device_info: print(f"Connected: {device_info_str(device_info=device_info)}")
on_disconnect = lambda device_id, device_info: print(f"Disconnected: {device_info_str(device_info=device_info)}")
# Create the USBMonitor instance
monitor = USBMonitor()
# Start the daemon
monitor.start_monitoring(on_connect=on_connect, on_disconnect=on_disconnect)
# ... Rest of your code ...
# If you don't need it anymore stop the daemon
monitor.stop_monitoring()
```
Output
Linux | Windows
:---: | :---:
 | 
Sometimes, when initializing your software, you may seek to confirm which USB devices are indeed connected.
```python
from usbmonitor import USBMonitor
from usbmonitor.attributes import ID_MODEL, ID_MODEL_ID, ID_VENDOR_ID
# Create the USBMonitor instance
monitor = USBMonitor()
# Get the current devices
devices_dict = monitor.get_available_devices()
# Print them
for device_id, device_info in devices_dict.items():
print(f"{device_id} -- {device_info[ID_MODEL]} ({device_info[ID_MODEL_ID]} - {device_info[ID_VENDOR_ID]})")
```
Output
```bash
/dev/bus/usb/001/001 -- xHCI_Host_Controller (0002 - 1d6b)
/dev/bus/usb/001/002 -- USB2.0_Hub (3431 - 2109)
/dev/bus/usb/001/003 -- USB_Optical_Mouse (c077 - 046d)
/dev/bus/usb/001/004 -- USB_Compliant_Keypad (9881 - 05a4)
/dev/bus/usb/002/001 -- xHCI_Host_Controller (0003 - 1d6b)
```
## API Reference
### USBMonitor(filter_devices = None)
Initialize the USBMonitor instance. It will allow to inspect and monitor connected devices
- `filter_devices`: **tuple[dict[str, str]] | None**. A tuple of dictionaries containing the device attributes to filter. If passed, it will only return and monitor devices that match any of the specified filters. For example, if you want to only retrieve and track devices with 'ID_VENDOR_FROM_DATABASE' = 'Realtek' or the device with 'ID_VENDOR_ID' = '1234' and 'ID_MODEL_ID' = '1A2B' you should instantiate with: `USBMonitor(filter_devices=({'ID_VENDOR_FROM_DATABASE': 'Realtek'}, {'ID_VENDOR_ID': '1234', 'ID_MODEL_ID': '1A2B'}))`. Default value is None.
### USBMonitor.start_monitoring(on_connect = None, on_disconnect = None, check_every_seconds = 0.5)
Starts a daemon that continuously monitors the connected USB devices in order to detect new connections or disconnections. When a device is disconnected, the `on_disconnect` callback function is invoked with the Device ID as the first argument and the [dictionary of device information](#device-properties) as the second argument. Similarly, when a new device is connected, the `on_connect` callback function is called with the same arguments. This allows developers to promptly respond to any changes in the connected USB devices and perform necessary actions.
- `on_connect`: **callable | None**. The function to call every time a device is **added**. It is expected to have the following format `on_connect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `on_disconnect`: **callable | None**. The function to call every time a device is **removed**. It is expected to have the following format `on_disconnect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `check_every_seconds`: **int | float**. Seconds to wait between each check for changes in the USB devices. Default value is 0.5 seconds.
### USBMonitor.stop_monitoring(warn_if_was_stopped=True)
Stops the monitoring of USB devices. This function will **stop** the daemon launched by `USBMonitor.start_monitoring`
- `warn_if_was_stopped`: **bool**. If set to `True`, this function will issue a warning if the monitoring of USB devices was already stopped (the daemon was not running).
### USBMonitor.get_available_devices()
Returns a dictionary of the currently available devices, where the key is the `Device ID` and the value is a [dictionary containing the device's information](#device-properties). All the keys of this dictionary can be found at `attributes.DEVICE_ATTRIBUTES`. They always correspond with the default Linux device properties (independently of the OS where the library is running).
- Returns: **dict[str, dict[str, str|tuple[str, ...]]]**: A dictionary containing the currently available devices. All values are strings except for `ID_USB_INTERFACES`, which is a `tuple` of `string`
### USBMonitor.changes_from_last_check(update_last_check_devices = True)
Returns a tuple of two dictionaries, one containing the devices that have been *removed* since the last check, and another one containing the devices that have been *added*. Both dictionaries will have the `Device ID` as key and all the device information as value. Remember that all the [keys of this dictionary](#device-properties) can be found at can be found at `attributes.DEVICE_ATTRIBUTES`.
- `update_last_check_devices`: **bool**. If `True` it will update the internal `USBMonitor.last_check_devices` attribute. So the next time you'll call this method, it will check for differences against the devices found in that current call. If `False` it won't update the `USBMonitor.last_check_devices` attribute.
- Returns: **tuple[dict[str, dict[str, str|tuple[str, ...]]], dict[str, dict[str, str|tuple[str, ...]]]]**: A `tuple` containing two `dictionaries`. The first `dictionary` contains the information of the devices that were **removed** since the last check and the second dictionary contains the information of the new **added** devices. All values are `strings` except for `ID_USB_INTERFACES`, which is a `tuple` of `string`.
### USBMonitor.check_changes(on_connect = None, on_disconnect = None, update_last_check_devices = True)
Checks for any new connections or disconnections of USB devices since the last check. If a device has been removed, the `on_disconnect` function will be called with the `Device ID` as the first argument and the [dictionary with the device's information](#device-properties) as the second argument. The same will occur with the `on_connect` function if any new device have been added. Internally this function will just run `USBMonitor.changes_from_last_check` and will execute the callbacks for each returned device
- `on_connect`: **callable | None**. The function to call when a device is added. It is expected to have the following format `on_connect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `on_disconnect`: **callable | None**. The function to call when a device is removed. It is expected to have the following format `on_disconnect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `update_last_check_devices`: **bool**. If `True` it will update the internal `USBMonitor.last_check_devices` attribute. So the next time you'll call this method, it will check for differences against the devices found in that current call. If `False` it won't update the `USBMonitor.last_check_devices` attribute.
### Device Properties
The `device_info` returned by most functions will contain the following information:
Key | Value Description | Example
:-- | :-- | :--
`'ID_MODEL_ID'` | The product ID of the USB device. | `'0892'`
`'ID_MODEL'` | The name of the USB device model. | `'HD_Pro_Webcam_C920'`
`'ID_MODEL_FROM_DATABASE'` | Device model name, retrieved from the device database.| `'OrbiCam'`
`'ID_VENDOR'` | The name of the USB device vendor | `'046d'`
`'ID_VENDOR_ID'` | The vendor ID of the USB device. | `'046d'`
`'ID_VENDOR_FROM_DATABASE'` | USB device vendor's name, from the device database. | `'Logitech, Inc.'`
`'ID_USB_INTERFACES'` | A `tuple` representing the USB device's interfaces. | `('0e0100', '0e0200', '010100', '010200')`
`'DEVNAME'` | The device name or path | `'/dev/bus/usb/001/003'`
`'DEVTYPE'` | Should always be `'usb_device'`. | `'usb_device'`
`'ID_SERIAL'` | The serial number of the USB device. | `'92C5B92F'`
Note that, depending on the device and the OS, some of this information may be incomplete or certain attributes may overlap with others.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731836539.0
usb_monitor-1.23/README.md 0000644 0000765 0000024 00000024472 14716335173 014163 0 ustar 00haru staff # USBMonitor
**USBMonitor** is a versatile **cross-platform** library that simplifies **USB device monitoring** for _Windows_, _Linux_ and _MacOS_ systems. It enables developers to effortlessly track device **connections**, **disconnections**, and access to all connected device **attributes**.
With **USBMonitor**, developers can stay up-to-date with any changes in the connected USB devices, allowing them to **trigger specific actions** whenever a USB device is connected or disconnected. By ensuring **consistent functionality across various operating systems**, **USBMonitor** removes the need to address platform-specific quirks, inconsistencies, or incompatibilities, resulting in a smooth and efficient USB device management experience. The uniformity in functionality significantly enhances **code compatibility**, minimizing the risk of **code issues** or **unexpected breaks** when moving between platforms.
At its core, **USBMonitor** utilizes pyudev (for Linux environments), WMI (for Windows environments), and the I/O Registry (for MacOs environments). Handling all the low-level intricacies and translating OS-specific information to ensure consistency across all systems.
## Installation
To install **USBMonitor**, simply run:
```bash
pip install usb-monitor
```
## Usage
Using **USBMonitor** is both simple and straight-forward. In most cases, you'll just want to start the [monitoring _Daemon_](#usbmonitorstart_monitoringon_connect--none-on_disconnect--none-check_every_seconds--05), defining the `on_connect` and `on_disconnect` callback functions to manage events when a USB device connects or disconnects. Here's a basic example:
```python
from usbmonitor import USBMonitor
from usbmonitor.attributes import ID_MODEL, ID_MODEL_ID, ID_VENDOR_ID
device_info_str = lambda device_info: f"{device_info[ID_MODEL]} ({device_info[ID_MODEL_ID]} - {device_info[ID_VENDOR_ID]})"
# Define the `on_connect` and `on_disconnect` callbacks
on_connect = lambda device_id, device_info: print(f"Connected: {device_info_str(device_info=device_info)}")
on_disconnect = lambda device_id, device_info: print(f"Disconnected: {device_info_str(device_info=device_info)}")
# Create the USBMonitor instance
monitor = USBMonitor()
# Start the daemon
monitor.start_monitoring(on_connect=on_connect, on_disconnect=on_disconnect)
# ... Rest of your code ...
# If you don't need it anymore stop the daemon
monitor.stop_monitoring()
```
Output
Linux | Windows
:---: | :---:
 | 
Sometimes, when initializing your software, you may seek to confirm which USB devices are indeed connected.
```python
from usbmonitor import USBMonitor
from usbmonitor.attributes import ID_MODEL, ID_MODEL_ID, ID_VENDOR_ID
# Create the USBMonitor instance
monitor = USBMonitor()
# Get the current devices
devices_dict = monitor.get_available_devices()
# Print them
for device_id, device_info in devices_dict.items():
print(f"{device_id} -- {device_info[ID_MODEL]} ({device_info[ID_MODEL_ID]} - {device_info[ID_VENDOR_ID]})")
```
Output
```bash
/dev/bus/usb/001/001 -- xHCI_Host_Controller (0002 - 1d6b)
/dev/bus/usb/001/002 -- USB2.0_Hub (3431 - 2109)
/dev/bus/usb/001/003 -- USB_Optical_Mouse (c077 - 046d)
/dev/bus/usb/001/004 -- USB_Compliant_Keypad (9881 - 05a4)
/dev/bus/usb/002/001 -- xHCI_Host_Controller (0003 - 1d6b)
```
## API Reference
### USBMonitor(filter_devices = None)
Initialize the USBMonitor instance. It will allow to inspect and monitor connected devices
- `filter_devices`: **tuple[dict[str, str]] | None**. A tuple of dictionaries containing the device attributes to filter. If passed, it will only return and monitor devices that match any of the specified filters. For example, if you want to only retrieve and track devices with 'ID_VENDOR_FROM_DATABASE' = 'Realtek' or the device with 'ID_VENDOR_ID' = '1234' and 'ID_MODEL_ID' = '1A2B' you should instantiate with: `USBMonitor(filter_devices=({'ID_VENDOR_FROM_DATABASE': 'Realtek'}, {'ID_VENDOR_ID': '1234', 'ID_MODEL_ID': '1A2B'}))`. Default value is None.
### USBMonitor.start_monitoring(on_connect = None, on_disconnect = None, check_every_seconds = 0.5)
Starts a daemon that continuously monitors the connected USB devices in order to detect new connections or disconnections. When a device is disconnected, the `on_disconnect` callback function is invoked with the Device ID as the first argument and the [dictionary of device information](#device-properties) as the second argument. Similarly, when a new device is connected, the `on_connect` callback function is called with the same arguments. This allows developers to promptly respond to any changes in the connected USB devices and perform necessary actions.
- `on_connect`: **callable | None**. The function to call every time a device is **added**. It is expected to have the following format `on_connect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `on_disconnect`: **callable | None**. The function to call every time a device is **removed**. It is expected to have the following format `on_disconnect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `check_every_seconds`: **int | float**. Seconds to wait between each check for changes in the USB devices. Default value is 0.5 seconds.
### USBMonitor.stop_monitoring(warn_if_was_stopped=True)
Stops the monitoring of USB devices. This function will **stop** the daemon launched by `USBMonitor.start_monitoring`
- `warn_if_was_stopped`: **bool**. If set to `True`, this function will issue a warning if the monitoring of USB devices was already stopped (the daemon was not running).
### USBMonitor.get_available_devices()
Returns a dictionary of the currently available devices, where the key is the `Device ID` and the value is a [dictionary containing the device's information](#device-properties). All the keys of this dictionary can be found at `attributes.DEVICE_ATTRIBUTES`. They always correspond with the default Linux device properties (independently of the OS where the library is running).
- Returns: **dict[str, dict[str, str|tuple[str, ...]]]**: A dictionary containing the currently available devices. All values are strings except for `ID_USB_INTERFACES`, which is a `tuple` of `string`
### USBMonitor.changes_from_last_check(update_last_check_devices = True)
Returns a tuple of two dictionaries, one containing the devices that have been *removed* since the last check, and another one containing the devices that have been *added*. Both dictionaries will have the `Device ID` as key and all the device information as value. Remember that all the [keys of this dictionary](#device-properties) can be found at can be found at `attributes.DEVICE_ATTRIBUTES`.
- `update_last_check_devices`: **bool**. If `True` it will update the internal `USBMonitor.last_check_devices` attribute. So the next time you'll call this method, it will check for differences against the devices found in that current call. If `False` it won't update the `USBMonitor.last_check_devices` attribute.
- Returns: **tuple[dict[str, dict[str, str|tuple[str, ...]]], dict[str, dict[str, str|tuple[str, ...]]]]**: A `tuple` containing two `dictionaries`. The first `dictionary` contains the information of the devices that were **removed** since the last check and the second dictionary contains the information of the new **added** devices. All values are `strings` except for `ID_USB_INTERFACES`, which is a `tuple` of `string`.
### USBMonitor.check_changes(on_connect = None, on_disconnect = None, update_last_check_devices = True)
Checks for any new connections or disconnections of USB devices since the last check. If a device has been removed, the `on_disconnect` function will be called with the `Device ID` as the first argument and the [dictionary with the device's information](#device-properties) as the second argument. The same will occur with the `on_connect` function if any new device have been added. Internally this function will just run `USBMonitor.changes_from_last_check` and will execute the callbacks for each returned device
- `on_connect`: **callable | None**. The function to call when a device is added. It is expected to have the following format `on_connect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `on_disconnect`: **callable | None**. The function to call when a device is removed. It is expected to have the following format `on_disconnect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `update_last_check_devices`: **bool**. If `True` it will update the internal `USBMonitor.last_check_devices` attribute. So the next time you'll call this method, it will check for differences against the devices found in that current call. If `False` it won't update the `USBMonitor.last_check_devices` attribute.
### Device Properties
The `device_info` returned by most functions will contain the following information:
Key | Value Description | Example
:-- | :-- | :--
`'ID_MODEL_ID'` | The product ID of the USB device. | `'0892'`
`'ID_MODEL'` | The name of the USB device model. | `'HD_Pro_Webcam_C920'`
`'ID_MODEL_FROM_DATABASE'` | Device model name, retrieved from the device database.| `'OrbiCam'`
`'ID_VENDOR'` | The name of the USB device vendor | `'046d'`
`'ID_VENDOR_ID'` | The vendor ID of the USB device. | `'046d'`
`'ID_VENDOR_FROM_DATABASE'` | USB device vendor's name, from the device database. | `'Logitech, Inc.'`
`'ID_USB_INTERFACES'` | A `tuple` representing the USB device's interfaces. | `('0e0100', '0e0200', '010100', '010200')`
`'DEVNAME'` | The device name or path | `'/dev/bus/usb/001/003'`
`'DEVTYPE'` | Should always be `'usb_device'`. | `'usb_device'`
`'ID_SERIAL'` | The serial number of the USB device. | `'92C5B92F'`
Note that, depending on the device and the OS, some of this information may be incomplete or certain attributes may overlap with others.
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1731838012.468067
usb_monitor-1.23/setup.cfg 0000644 0000765 0000024 00000000046 14716340074 014510 0 ustar 00haru staff [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731837943.0
usb_monitor-1.23/setup.py 0000644 0000765 0000024 00000003454 14716337767 014426 0 ustar 00haru staff from setuptools import setup, find_packages
setup(
name='usb-monitor',
version='1.23',
author='Eric-Canas',
author_email='eric@ericcanas.com',
url='https://github.com/Eric-Canas/USBMonitor',
description='USBMonitor is an easy-to-use cross-platform library for USB device monitoring that simplifies '
'tracking of connections, disconnections, and examination of connected device attributes on '
'Windows, Linux and MacOs, freeing the user from platform-specific details or incompatibilities.',
long_description=open('README.md', 'r').read(),
long_description_content_type='text/markdown',
license='MIT',
packages=find_packages(),
python_requires='>=3.6',
install_requires=[
'pyudev; platform_system=="Linux"',
'pywin32; platform_system=="Windows"',
'wmi; platform_system=="Windows"',
],
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Operating System :: OS Independent',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS',
'Intended Audience :: Developers',
'Topic :: System :: Hardware',
'Topic :: System :: Hardware :: Hardware Drivers',
'Topic :: System :: Systems Administration',
'Topic :: System :: Monitoring',
'Topic :: System :: Operating System',
'Topic :: Software Development :: Embedded Systems',
'Topic :: Software Development :: Testing',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities',
],
) ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1731838012.4672494
usb_monitor-1.23/usb_monitor.egg-info/ 0000755 0000765 0000024 00000000000 14716340074 016721 5 ustar 00haru staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731838012.0
usb_monitor-1.23/usb_monitor.egg-info/PKG-INFO 0000644 0000765 0000024 00000027631 14716340074 020027 0 ustar 00haru staff Metadata-Version: 2.1
Name: usb-monitor
Version: 1.23
Summary: USBMonitor is an easy-to-use cross-platform library for USB device monitoring that simplifies tracking of connections, disconnections, and examination of connected device attributes on Windows, Linux and MacOs, freeing the user from platform-specific details or incompatibilities.
Home-page: https://github.com/Eric-Canas/USBMonitor
Author: Eric-Canas
Author-email: eric@ericcanas.com
License: MIT
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Intended Audience :: Developers
Classifier: Topic :: System :: Hardware
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Operating System
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyudev; platform_system == "Linux"
Requires-Dist: pywin32; platform_system == "Windows"
Requires-Dist: wmi; platform_system == "Windows"
# USBMonitor
**USBMonitor** is a versatile **cross-platform** library that simplifies **USB device monitoring** for _Windows_, _Linux_ and _MacOS_ systems. It enables developers to effortlessly track device **connections**, **disconnections**, and access to all connected device **attributes**.
With **USBMonitor**, developers can stay up-to-date with any changes in the connected USB devices, allowing them to **trigger specific actions** whenever a USB device is connected or disconnected. By ensuring **consistent functionality across various operating systems**, **USBMonitor** removes the need to address platform-specific quirks, inconsistencies, or incompatibilities, resulting in a smooth and efficient USB device management experience. The uniformity in functionality significantly enhances **code compatibility**, minimizing the risk of **code issues** or **unexpected breaks** when moving between platforms.
At its core, **USBMonitor** utilizes pyudev (for Linux environments), WMI (for Windows environments), and the I/O Registry (for MacOs environments). Handling all the low-level intricacies and translating OS-specific information to ensure consistency across all systems.
## Installation
To install **USBMonitor**, simply run:
```bash
pip install usb-monitor
```
## Usage
Using **USBMonitor** is both simple and straight-forward. In most cases, you'll just want to start the [monitoring _Daemon_](#usbmonitorstart_monitoringon_connect--none-on_disconnect--none-check_every_seconds--05), defining the `on_connect` and `on_disconnect` callback functions to manage events when a USB device connects or disconnects. Here's a basic example:
```python
from usbmonitor import USBMonitor
from usbmonitor.attributes import ID_MODEL, ID_MODEL_ID, ID_VENDOR_ID
device_info_str = lambda device_info: f"{device_info[ID_MODEL]} ({device_info[ID_MODEL_ID]} - {device_info[ID_VENDOR_ID]})"
# Define the `on_connect` and `on_disconnect` callbacks
on_connect = lambda device_id, device_info: print(f"Connected: {device_info_str(device_info=device_info)}")
on_disconnect = lambda device_id, device_info: print(f"Disconnected: {device_info_str(device_info=device_info)}")
# Create the USBMonitor instance
monitor = USBMonitor()
# Start the daemon
monitor.start_monitoring(on_connect=on_connect, on_disconnect=on_disconnect)
# ... Rest of your code ...
# If you don't need it anymore stop the daemon
monitor.stop_monitoring()
```
Output
Linux | Windows
:---: | :---:
 | 
Sometimes, when initializing your software, you may seek to confirm which USB devices are indeed connected.
```python
from usbmonitor import USBMonitor
from usbmonitor.attributes import ID_MODEL, ID_MODEL_ID, ID_VENDOR_ID
# Create the USBMonitor instance
monitor = USBMonitor()
# Get the current devices
devices_dict = monitor.get_available_devices()
# Print them
for device_id, device_info in devices_dict.items():
print(f"{device_id} -- {device_info[ID_MODEL]} ({device_info[ID_MODEL_ID]} - {device_info[ID_VENDOR_ID]})")
```
Output
```bash
/dev/bus/usb/001/001 -- xHCI_Host_Controller (0002 - 1d6b)
/dev/bus/usb/001/002 -- USB2.0_Hub (3431 - 2109)
/dev/bus/usb/001/003 -- USB_Optical_Mouse (c077 - 046d)
/dev/bus/usb/001/004 -- USB_Compliant_Keypad (9881 - 05a4)
/dev/bus/usb/002/001 -- xHCI_Host_Controller (0003 - 1d6b)
```
## API Reference
### USBMonitor(filter_devices = None)
Initialize the USBMonitor instance. It will allow to inspect and monitor connected devices
- `filter_devices`: **tuple[dict[str, str]] | None**. A tuple of dictionaries containing the device attributes to filter. If passed, it will only return and monitor devices that match any of the specified filters. For example, if you want to only retrieve and track devices with 'ID_VENDOR_FROM_DATABASE' = 'Realtek' or the device with 'ID_VENDOR_ID' = '1234' and 'ID_MODEL_ID' = '1A2B' you should instantiate with: `USBMonitor(filter_devices=({'ID_VENDOR_FROM_DATABASE': 'Realtek'}, {'ID_VENDOR_ID': '1234', 'ID_MODEL_ID': '1A2B'}))`. Default value is None.
### USBMonitor.start_monitoring(on_connect = None, on_disconnect = None, check_every_seconds = 0.5)
Starts a daemon that continuously monitors the connected USB devices in order to detect new connections or disconnections. When a device is disconnected, the `on_disconnect` callback function is invoked with the Device ID as the first argument and the [dictionary of device information](#device-properties) as the second argument. Similarly, when a new device is connected, the `on_connect` callback function is called with the same arguments. This allows developers to promptly respond to any changes in the connected USB devices and perform necessary actions.
- `on_connect`: **callable | None**. The function to call every time a device is **added**. It is expected to have the following format `on_connect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `on_disconnect`: **callable | None**. The function to call every time a device is **removed**. It is expected to have the following format `on_disconnect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `check_every_seconds`: **int | float**. Seconds to wait between each check for changes in the USB devices. Default value is 0.5 seconds.
### USBMonitor.stop_monitoring(warn_if_was_stopped=True)
Stops the monitoring of USB devices. This function will **stop** the daemon launched by `USBMonitor.start_monitoring`
- `warn_if_was_stopped`: **bool**. If set to `True`, this function will issue a warning if the monitoring of USB devices was already stopped (the daemon was not running).
### USBMonitor.get_available_devices()
Returns a dictionary of the currently available devices, where the key is the `Device ID` and the value is a [dictionary containing the device's information](#device-properties). All the keys of this dictionary can be found at `attributes.DEVICE_ATTRIBUTES`. They always correspond with the default Linux device properties (independently of the OS where the library is running).
- Returns: **dict[str, dict[str, str|tuple[str, ...]]]**: A dictionary containing the currently available devices. All values are strings except for `ID_USB_INTERFACES`, which is a `tuple` of `string`
### USBMonitor.changes_from_last_check(update_last_check_devices = True)
Returns a tuple of two dictionaries, one containing the devices that have been *removed* since the last check, and another one containing the devices that have been *added*. Both dictionaries will have the `Device ID` as key and all the device information as value. Remember that all the [keys of this dictionary](#device-properties) can be found at can be found at `attributes.DEVICE_ATTRIBUTES`.
- `update_last_check_devices`: **bool**. If `True` it will update the internal `USBMonitor.last_check_devices` attribute. So the next time you'll call this method, it will check for differences against the devices found in that current call. If `False` it won't update the `USBMonitor.last_check_devices` attribute.
- Returns: **tuple[dict[str, dict[str, str|tuple[str, ...]]], dict[str, dict[str, str|tuple[str, ...]]]]**: A `tuple` containing two `dictionaries`. The first `dictionary` contains the information of the devices that were **removed** since the last check and the second dictionary contains the information of the new **added** devices. All values are `strings` except for `ID_USB_INTERFACES`, which is a `tuple` of `string`.
### USBMonitor.check_changes(on_connect = None, on_disconnect = None, update_last_check_devices = True)
Checks for any new connections or disconnections of USB devices since the last check. If a device has been removed, the `on_disconnect` function will be called with the `Device ID` as the first argument and the [dictionary with the device's information](#device-properties) as the second argument. The same will occur with the `on_connect` function if any new device have been added. Internally this function will just run `USBMonitor.changes_from_last_check` and will execute the callbacks for each returned device
- `on_connect`: **callable | None**. The function to call when a device is added. It is expected to have the following format `on_connect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `on_disconnect`: **callable | None**. The function to call when a device is removed. It is expected to have the following format `on_disconnect(device_id: str, device_info: dict[str, dict[str, str|tuple[str, ...]]])`
- `update_last_check_devices`: **bool**. If `True` it will update the internal `USBMonitor.last_check_devices` attribute. So the next time you'll call this method, it will check for differences against the devices found in that current call. If `False` it won't update the `USBMonitor.last_check_devices` attribute.
### Device Properties
The `device_info` returned by most functions will contain the following information:
Key | Value Description | Example
:-- | :-- | :--
`'ID_MODEL_ID'` | The product ID of the USB device. | `'0892'`
`'ID_MODEL'` | The name of the USB device model. | `'HD_Pro_Webcam_C920'`
`'ID_MODEL_FROM_DATABASE'` | Device model name, retrieved from the device database.| `'OrbiCam'`
`'ID_VENDOR'` | The name of the USB device vendor | `'046d'`
`'ID_VENDOR_ID'` | The vendor ID of the USB device. | `'046d'`
`'ID_VENDOR_FROM_DATABASE'` | USB device vendor's name, from the device database. | `'Logitech, Inc.'`
`'ID_USB_INTERFACES'` | A `tuple` representing the USB device's interfaces. | `('0e0100', '0e0200', '010100', '010200')`
`'DEVNAME'` | The device name or path | `'/dev/bus/usb/001/003'`
`'DEVTYPE'` | Should always be `'usb_device'`. | `'usb_device'`
`'ID_SERIAL'` | The serial number of the USB device. | `'92C5B92F'`
Note that, depending on the device and the OS, some of this information may be incomplete or certain attributes may overlap with others.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731838012.0
usb_monitor-1.23/usb_monitor.egg-info/SOURCES.txt 0000644 0000765 0000024 00000001177 14716340074 020613 0 ustar 00haru staff LICENSE
README.md
setup.py
usb_monitor.egg-info/PKG-INFO
usb_monitor.egg-info/SOURCES.txt
usb_monitor.egg-info/dependency_links.txt
usb_monitor.egg-info/requires.txt
usb_monitor.egg-info/top_level.txt
usbmonitor/__init__.py
usbmonitor/attributes.py
usbmonitor/usbmonitor.py
usbmonitor/__platform_specific_detectors/__init__.py
usbmonitor/__platform_specific_detectors/_constants.py
usbmonitor/__platform_specific_detectors/_darwin_usb_detector.py
usbmonitor/__platform_specific_detectors/_linux_usb_detector.py
usbmonitor/__platform_specific_detectors/_usb_detector_base.py
usbmonitor/__platform_specific_detectors/_windows_usb_detector.py ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731838012.0
usb_monitor-1.23/usb_monitor.egg-info/dependency_links.txt 0000644 0000765 0000024 00000000001 14716340074 022767 0 ustar 00haru staff
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731838012.0
usb_monitor-1.23/usb_monitor.egg-info/requires.txt 0000644 0000765 0000024 00000000123 14716340074 021315 0 ustar 00haru staff
[:platform_system == "Linux"]
pyudev
[:platform_system == "Windows"]
pywin32
wmi
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731838012.0
usb_monitor-1.23/usb_monitor.egg-info/top_level.txt 0000644 0000765 0000024 00000000013 14716340074 021445 0 ustar 00haru staff usbmonitor
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1731838012.465091
usb_monitor-1.23/usbmonitor/ 0000755 0000765 0000024 00000000000 14716340074 015070 5 ustar 00haru staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717225473.0
usb_monitor-1.23/usbmonitor/__init__.py 0000644 0000765 0000024 00000000357 14626544001 017202 0 ustar 00haru staff from .usbmonitor import USBMonitor
# Import platform-specific detectors
from .__platform_specific_detectors._windows_usb_detector import _WindowsUSBDetector
from .__platform_specific_detectors._linux_usb_detector import _LinuxUSBDetector ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1731838012.4668252
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/ 0000755 0000765 0000024 00000000000 14716340074 023133 5 ustar 00haru staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717225473.0
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/__init__.py 0000644 0000765 0000024 00000000000 14626544001 025226 0 ustar 00haru staff ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731837645.0
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/_constants.py 0000644 0000765 0000024 00000006402 14716337315 025666 0 ustar 00haru staff """
This file contains constant values and mappings used in the platform-specific USB device detection implementations.
It includes constants for attributes, attribute separators, and regex patterns for specific Operating Systems.
Additionally, it defines the default time interval for checking USB device changes.
Author: Eric-Canas
Date: 28-03-2023
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
from ..attributes import ID_MODEL_ID, ID_VENDOR, ID_MODEL, ID_VENDOR_FROM_DATABASE, ID_MODEL_FROM_DATABASE, \
DEVNAME, ID_USB_CLASS_FROM_DATABASE, ID_USB_INTERFACES, DEVTYPE, ID_VENDOR_ID, ID_SERIAL
_SECONDS_BETWEEN_CHECKS = 0.5
_THREAD_JOIN_TIMEOUT_SECONDS = 5
_DEVICE_ID, _PNP_DEVICE_ID = 'DeviceID', 'PNPDeviceID'
_LINUX_TO_WINDOWS_ATTRIBUTES = {
ID_MODEL_ID: 'HardwareID',
ID_MODEL: 'Name',
ID_MODEL_FROM_DATABASE: 'Caption',
ID_VENDOR_ID: 'HardwareID',
ID_VENDOR: 'Name',
ID_VENDOR_FROM_DATABASE: 'Manufacturer',
ID_USB_INTERFACES: 'CompatibleID',
ID_USB_CLASS_FROM_DATABASE: 'PNPClass',
DEVNAME: _DEVICE_ID,
DEVTYPE: _PNP_DEVICE_ID,
ID_SERIAL: _PNP_DEVICE_ID
}
_LINUX_TUPLE_ATTRIBUTES_SEPARATORS = {ID_USB_INTERFACES: ':'}
USB, USBSTOR, USB4, USBPRINT = 'USB', 'USBSTOR', 'USB4', 'USBPRINT'
_WINDOWS_USB_REGEX_ATTRIBUTES = {ID_MODEL_ID: r'PID_([0-9A-Fa-f]{4})', ID_VENDOR_ID: r'VID_([0-9A-Fa-f]{4})',
DEVTYPE: r'^(.+?)\\', ID_SERIAL: r'\\([^\\]+)$'}
_WINDOWS_USB4_REGEX_ATTRIBUTES = {ID_MODEL_ID: r'PID_([0-9A-Fa-f]{4})', ID_VENDOR_ID: r'VID_([0-9A-Fa-f]{4})',
DEVTYPE: r'^(.+?)\\', ID_SERIAL: r'\\([^\\]+)$'}
_WINDOWS_USBPRINT_REGEX_ATTRIBUTES = {ID_MODEL_ID: r'PID_([0-9A-Fa-f]{4})', ID_VENDOR_ID: r'VID_([0-9A-Fa-f]{4})',
DEVTYPE: r'^(.+?)\\', ID_SERIAL: r'\\([^\\]+)$'}
_WINDOWS_USBSTOR_REGEX_ATTRIBUTES = {ID_MODEL_ID: r'PROD_([a-zA-Z0-9\_\/\.\-]{2,16})&', ID_VENDOR_ID: r'VEN_([a-zA-Z0-9\.\_\-\/]{2,8})&',
DEVTYPE: r'^(.+?)\\', ID_SERIAL: r'\\([^\\]+)$'}
_WINDOWS_REGEX_ATTRIBUTES_BY_DRIVER = {USB: _WINDOWS_USB_REGEX_ATTRIBUTES,
USBSTOR: _WINDOWS_USBSTOR_REGEX_ATTRIBUTES,
USB4: _WINDOWS_USB4_REGEX_ATTRIBUTES,
USBPRINT: _WINDOWS_USBPRINT_REGEX_ATTRIBUTES}
_WINDOWS_TO_LOWERCASE_ATTRIBUTES = (ID_MODEL_ID, ID_VENDOR_ID)
_WINDOWS_NON_USB_DEVICES_IDS = ("ROOT_HUB20", "ROOT_HUB30", "VIRTUAL_POWER_PDO")
_WINDOWS_USB_QUERY = f"SELECT {', '.join(set(_LINUX_TO_WINDOWS_ATTRIBUTES.values()))} FROM Win32_PnPEntity " \
f"WHERE {_PNP_DEVICE_ID} LIKE 'USB%'"
# Darwin-specific constants
_DARWIN_TO_LINUX_ATTRIBUTES = {
ID_MODEL_ID: 'idProduct',
ID_MODEL: 'kUSBProductString',
ID_MODEL_FROM_DATABASE: 'USB Product Name',
ID_VENDOR_ID: 'idVendor',
ID_VENDOR: 'kUSBVendorString',
ID_VENDOR_FROM_DATABASE: 'USB Vendor Name',
ID_USB_INTERFACES: 'IOCFPlugInTypes',
ID_USB_CLASS_FROM_DATABASE: 'bDeviceClass',
DEVNAME: 'BSD Name',
DEVTYPE: 'Device Speed',
ID_SERIAL: 'kUSBSerialNumberString',
}
_DARWIN_REGEX_ATTRIBUTES = {
ID_MODEL_ID: r'idProduct: ([0-9A-Fa-f]{4})',
ID_VENDOR_ID: r'idVendor: ([0-9A-Fa-f]{4})'
}
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717231981.0
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/_darwin_usb_detector.py 0000644 0000765 0000024 00000010761 14626560555 027707 0 ustar 00haru staff """
_DarwinUSBDetector: This platform-specific implementation of the _USBDetectorBase class is designed for MacOS systems.
It provides the necessary functionality to detect USB devices connected to a MacOS system and monitor changes in their
connections. The class utilizes the ioreg command to interact with the MacOS system's device management subsystem.
Author: Eric-Canas
Date: 01-06-2024
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
from __future__ import annotations
import re
import subprocess
from warnings import warn
from ..attributes import DEVTYPE, ID_VENDOR_ID, DEVNAME, DEVICE_ATTRIBUTES
from ._constants import _DARWIN_TO_LINUX_ATTRIBUTES, _DARWIN_REGEX_ATTRIBUTES, _SECONDS_BETWEEN_CHECKS
from ._usb_detector_base import _USBDetectorBase
class _DarwinUSBDetector(_USBDetectorBase):
def __init__(self, filter_devices: list[dict[str, str]] | tuple[dict[str, str]] | None = None):
super(_DarwinUSBDetector, self).__init__(filter_devices=filter_devices)
def get_available_devices(self) -> dict[str, dict[str, str]]:
"""
Returns a dictionary of the currently available devices, where the key is the device ID and the value is a
dictionary of the device's information.
:return: dict[str, dict[str, str]]. The key is the device ID, the value is a dictionary of the device's
information.
"""
devices_info = self.__get_usb_devices()
if self.filter_devices is not None:
devices_info = self._apply_devices_filter(devices=devices_info)
return devices_info
def _monitor_changes(self, on_connect: callable | None = None, on_disconnect: callable | None = None,
check_every_seconds: int | float = _SECONDS_BETWEEN_CHECKS) -> None:
"""
Monitors the USB devices. This function should ALWAYS be called from a background thread.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive two
arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to receive
two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param check_every_seconds: int | float. The number of seconds to wait between each check for changes in the
USB devices. Defaults to 0.5 seconds.
"""
while not self._stop_thread.is_set():
self.check_changes(on_connect=on_connect, on_disconnect=on_disconnect)
self._stop_thread.wait(check_every_seconds)
def __get_usb_devices(self) -> dict[str, dict[str, str]]:
"""
Retrieves the list of USB devices using the `ioreg` command.
:return: dict[str, dict[str, str]]. A dictionary of the device information.
"""
try:
ioreg_output = subprocess.check_output(['ioreg', '-p', 'IOUSB', '-w0', '-l'], text=True)
except subprocess.CalledProcessError as e:
warn(f"Failed to retrieve USB devices information: {e}")
return {}
devices_info = {}
current_device = {}
device_id = None
for line in ioreg_output.splitlines():
if "+-o" in line:
if current_device and device_id:
devices_info[device_id] = current_device
current_device = {}
device_id_match = re.search(r"\+-o\s+(.+?)\s+<", line)
if device_id_match:
device_id = device_id_match.group(1)
else:
device_id = None
else:
for attribute, darwin_attr in _DARWIN_TO_LINUX_ATTRIBUTES.items():
if darwin_attr in line:
value = line.split('=')[-1].strip().strip("}").strip('"')
current_device[attribute] = value
for attribute, regex in _DARWIN_REGEX_ATTRIBUTES.items():
match = re.search(regex, line)
if match:
current_device[attribute] = match.group(1)
if current_device and device_id:
devices_info[device_id] = current_device
# Add the device_id as the devname to the info dict
for device_id, device_info in devices_info.items():
device_info[DEVNAME] = device_id
return devices_info
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717877015.0
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/_linux_usb_detector.py 0000644 0000765 0000024 00000010766 14631134427 027556 0 ustar 00haru staff """
_LinuxUSBDetector: This platform-specific implementation of the _USBDetectorBase class is designed for Linux systems.
It provides the necessary functionality to detect USB devices connected to a Linux system and monitor changes in their
connections. The class utilizes the pyudev library to interact with the Linux system's device management
subsystem (udev).
Author: Eric-Canas
Date: 28-03-2023
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
from __future__ import annotations
from ._constants import _SECONDS_BETWEEN_CHECKS, _LINUX_TUPLE_ATTRIBUTES_SEPARATORS
from ..attributes import ID_VENDOR_ID, DEVTYPE, DEVICE_ATTRIBUTES, DEVNAME
from ._usb_detector_base import _USBDetectorBase
class _LinuxUSBDetector(_USBDetectorBase):
def __init__(self, filter_devices: list[dict[str, str]] | tuple[dict[str, str]] | None = None):
import pyudev
self.context = pyudev.Context()
self.monitor = None
super(_LinuxUSBDetector, self).__init__(filter_devices=filter_devices)
def get_available_devices(self) -> dict[str, dict[str, str | tuple[str, ...]]]:
"""
Returns a dictionary of the currently available devices, where the key is the device ID and the value is a
dictionary of the device's information.
:return: dict[str, dict[str, str]]. The key is the device ID, the value is a dictionary of the device's
information.
"""
usb_devices = [device for device in self.context.list_devices(subsystem='usb') if ID_VENDOR_ID in device]
devices_info = {}
for device in usb_devices:
device_id = device[DEVNAME]
device_info = {attr: device.get(attr, "") for attr in DEVICE_ATTRIBUTES}
device_info = self.__generate_tuple_attributes_from_string(device_info=device_info)
devices_info[device_id] = device_info
if self.filter_devices is not None:
devices_info = self._apply_devices_filter(devices=devices_info)
return devices_info
def __generate_tuple_attributes_from_string(self, device_info: dict[str, str]) -> dict[str, tuple[str]|str]:
"""
Generates a tuple of attributes for those attributes that are expected to be a tuple,
but are stored as a string.
:param device_info: dict[str, str]. The device information.
:return: dict[str, tuple[str]|str]. The device information with the tuple attributes.
"""
for attribute, separator in _LINUX_TUPLE_ATTRIBUTES_SEPARATORS.items():
if attribute in device_info:
assert isinstance(device_info[attribute], str), f"The attribute '{attribute}' is expected to be a string"
# noinspection PyTypeChecker
device_info[attribute] = tuple(value for value in device_info[attribute].split(separator) if value != "")
return device_info
def _monitor_changes(self, on_connect: callable | None = None, on_disconnect: callable | None = None,
check_every_seconds: int | float = _SECONDS_BETWEEN_CHECKS) -> None:
import pyudev
def __handle_device_event(device):
action = device.action
if device.get(DEVTYPE) == 'usb_device':
device_id = device[DEVNAME]
if action == "add" and on_connect is not None:
device_info = {attr: device.get(attr, "") for attr in DEVICE_ATTRIBUTES}
device_info = self.__generate_tuple_attributes_from_string(device_info=device_info)
on_connect(device_id, device_info)
self.last_check_devices = self.get_available_devices()
elif action == "remove" and on_disconnect is not None and device_id in self.last_check_devices:
device_info = self.last_check_devices[device_id].copy()
on_disconnect(device_id, device_info)
self.last_check_devices = self.get_available_devices()
if self.monitor is None:
self.monitor = pyudev.Monitor.from_netlink(self.context)
self.monitor.filter_by(subsystem='usb')
self._thread = pyudev.MonitorObserver(self.monitor, name="USB Monitor", callback=__handle_device_event)
# Start the observer thread
self._thread.start()
# Keep the main thread alive, checking for changes every specified interval
while not self._stop_thread.is_set():
self._stop_thread.wait(check_every_seconds)
# Stop the observer thread
self._thread.stop() ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717877302.0
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/_usb_detector_base.py 0000644 0000765 0000024 00000024616 14631135066 027330 0 ustar 00haru staff """
_USBDetectorBase: This abstract base class provides the core functionality for monitoring USB devices on different
platforms. It defines the required methods and attributes, as well as common functionality for checking device changes,
starting and stopping the monitoring process, and maintaining the state of the monitoring thread. The _USBDetectorBase
class is intended to be subclassed by platform-specific implementations to provide the necessary support for USB
device monitoring.
Author: Eric-Canas
Date: 27-03-2023
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
from __future__ import annotations
import threading
from warnings import warn
from abc import ABC, abstractmethod
from ._constants import _SECONDS_BETWEEN_CHECKS, _THREAD_JOIN_TIMEOUT_SECONDS
class _USBDetectorBase(ABC):
def __init__(self, filter_devices: list[dict[str, str]] | tuple[dict[str, str]] = None):
"""
Initializes the _USBDetectorBase class.
:param filter_devices: list[dict[str, str]] | tuple[dict[str, str]] | None. A list of dictionaries containing
the device information to filter the devices by. If None, no filtering will be done. The dictionaries
must contain the same keys as the dictionaries returned by the `get_available_devices` method.
For example, if you want to only monitor devices with ID_MODEL_ID = "A2B2" or "ABCD" you could pass
filter_devices=({"ID_MODEL_ID": "A2B2"}, {"ID_MODEL_ID": "ABCD"}).
"""
self._thread = None
self.filter_devices = filter_devices
self._stop_thread = threading.Event()
if filter_devices is not None:
assert isinstance(filter_devices, (list, tuple)), f"filter_devices must be a list or a tuple of dicts " \
f"(or None). Got {type(filter_devices)}"
assert all(isinstance(device, dict) for device in filter_devices), f"filter_devices must contain dicts. " \
f"Got {set(type(device) for device in filter_devices)}"
self.on_start_devices = self.get_available_devices()
self.last_check_devices = self.on_start_devices.copy()
def changes_from_last_check(self, update_last_check_devices: bool = True) -> tuple[dict[str, str], dict[str, str]]:
"""
Returns a tuple of two tuples, the first containing the device IDs of the devices that were removed, the second
containing the device IDs of the devices that were added.
:param update_last_check_devices: bool. Whether to update the last checked devices to the current devices
:return: tuple[dict[str, str], dict[str, str]]. The first tuple contains the information of the devices that
were removed, the second tuple contains the information of the new devices that were added.
"""
current_devices, prev_devices = self.get_available_devices(), self.last_check_devices
# Get the difference between the current devices and the previous ones
removed_devices = {_id: _info for _id, _info in prev_devices.items() if _id not in current_devices}
added_devices = {_id: _info for _id, _info in current_devices.items() if _id not in prev_devices}
# Update the last checked devices to the current devices if requested
if update_last_check_devices:
self.last_check_devices = current_devices.copy()
return removed_devices, added_devices
@abstractmethod
def get_available_devices(self) -> dict[str, dict[str, str | tuple[str, ...]]]:
"""
Returns a dictionary of the currently available devices, where the key is the device ID and the value is a
dictionary of the device's information.
:return: dict[str, dict[str, str|tuple[str, ...]]. The key is the device ID, the value is a dictionary of the device's
information.
"""
raise NotImplementedError("This method must be implemented in the child class")
def _apply_devices_filter(self, devices: dict[str, dict[str, str | tuple[str, ...]]]) -> dict[str, dict[str, str | tuple[str, ...]]]:
"""
Filters the devices by the given filters. Only devices that match all the filters in any of the dictionaries
will be returned.
:param devices: dict[str, dict[str, str|tuple[str, ...]]]. The devices to filter. Returned by the
`get_available_devices` method.
:return: dict[str, dict[str, str|tuple[str, ...]]]. The input devices that matches any of the given filters
"""
# Copy the dict to avoid allow iteration while modifying the original dict
for device_id, device_info in devices.copy().items():
# Iterate over each filter dict
for filter_dict in self.filter_devices:
if all(device_info[key] == value for key, value in filter_dict.items()):
break
else:
devices.pop(device_id)
return devices
def check_changes(self, on_connect: callable | None = None, on_disconnect: callable | None = None,
update_last_check_devices: bool = True) -> None:
"""
Checks for changes in the USB devices. If a device is removed, the `on_disconnect` function will be called
with the device ID as the first argument and the device information as the second argument. If a device is
added, the `on_connect` function with the same arguments.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive
two arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to
receive two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param update_last_check_devices: bool. Whether to update the last checked devices to the current devices
"""
removed_devices, added_devices = self.changes_from_last_check(update_last_check_devices=update_last_check_devices)
if on_disconnect is not None:
for device_id, device_info in removed_devices.items():
on_disconnect(device_id, device_info)
if on_connect is not None:
for device_id, device_info in added_devices.items():
on_connect(device_id, device_info)
def start_monitoring(self, on_connect: callable|None = None, on_disconnect: callable|None = None,
check_every_seconds: int | float = _SECONDS_BETWEEN_CHECKS) -> None:
"""
Starts monitoring the USB devices. This function will trigger a background thread that will check for changes
in the USB devices every `check_every_seconds` seconds. If a device is removed, the `on_disconnect` function
will be called with the device ID as the first argument and the device information as the second argument.
If a device is added, the `on_connect` function with the same arguments.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive two
arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to receive
two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param check_every_seconds: int | float. The number of seconds to wait between each check for changes in the
USB devices. Defaults to 0.5 seconds.
"""
assert self._thread is None, "The USB monitor is already running"
self._thread = threading.Thread(name="USB Monitor", target=self._monitor_changes,
args=(on_connect, on_disconnect, check_every_seconds),
daemon=True)
self._thread.start()
def _monitor_changes(self, on_connect: callable | None = None, on_disconnect: callable | None = None,
check_every_seconds: int | float = _SECONDS_BETWEEN_CHECKS) -> None:
"""
Monitors the USB devices. This function should ALWAYS be called from a background thread.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive two
arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to receive
two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param check_every_seconds: int | float. The number of seconds to wait between each check for changes in the
USB devices. Defaults to 0.5 seconds.
"""
assert self._thread is not None, "The USB monitor is not running"
if self._stop_thread.is_set():
warn("USB monitor can not be started because it is already stopped. Call stop_monitoring() first",
RuntimeWarning)
while not self._stop_thread.is_set():
self.check_changes(on_connect=on_connect, on_disconnect=on_disconnect)
self._stop_thread.wait(check_every_seconds)
def stop_monitoring(self, warn_if_was_stopped: bool = True, warn_if_timeout: bool = True,
timeout=_THREAD_JOIN_TIMEOUT_SECONDS) -> None:
"""
Stops monitoring the USB devices.
:param warn_if_was_stopped: bool. Whether to warn if the USB monitor was already stopped.
"""
if self._thread is not None:
self._stop_thread.set()
self._thread.join(timeout=timeout)
if warn_if_timeout and self._thread.is_alive():
warn(f"USB monitor thread did not stop in {timeout} seconds. "
f"It could still be running", RuntimeWarning)
self._thread = None
elif warn_if_was_stopped:
warn("USB monitor can not be stopped because it is not running", RuntimeWarning)
self._stop_thread.clear()
def __del__(self):
self.stop_monitoring(warn_if_was_stopped=False) ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717225473.0
usb_monitor-1.23/usbmonitor/__platform_specific_detectors/_windows_usb_detector.py 0000644 0000765 0000024 00000016071 14626544001 030101 0 ustar 00haru staff """
_WindowsUSBDetector: This platform-specific implementation of the _USBDetectorBase class is designed for Windows
systems. It provides the necessary functionality to detect USB devices connected to a Windows system and monitor
changes in their connections. The class utilizes the WMI (Windows Management Instrumentation) library to interact with
the Windows system's device management subsystem.
Author: Eric-Canas
Date: 27-03-2023
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
from __future__ import annotations
import re
from warnings import warn
from ..attributes import DEVTYPE
from ._constants import _DEVICE_ID, _LINUX_TO_WINDOWS_ATTRIBUTES, _SECONDS_BETWEEN_CHECKS, \
_WINDOWS_USB_REGEX_ATTRIBUTES, \
_WINDOWS_NON_USB_DEVICES_IDS, _WINDOWS_USB_QUERY, _WINDOWS_TO_LOWERCASE_ATTRIBUTES, \
_WINDOWS_REGEX_ATTRIBUTES_BY_DRIVER
from ._usb_detector_base import _USBDetectorBase
class _WindowsUSBDetector(_USBDetectorBase):
def __init__(self, filter_devices: list[dict[str, str]] | tuple[dict[str, str]] | None = None):
self._wmi_interface = None
# CONSTANTS
super(_WindowsUSBDetector, self).__init__(filter_devices=filter_devices)
def get_available_devices(self) -> dict[str, dict[str, str]]:
"""
Returns a dictionary of the currently available devices, where the key is the device ID and the value is a
dictionary of the device's information.
:return: dict[str, dict[str, str]]. The key is the device ID, the value is a dictionary of the device's
information.
"""
if self._wmi_interface is None:
self._wmi_interface = self.__create_wmi_interface()
devices = self._wmi_interface.query(_WINDOWS_USB_QUERY)
devices = {getattr(device, _DEVICE_ID): {new_name: getattr(device, attribute)
for new_name, attribute in _LINUX_TO_WINDOWS_ATTRIBUTES.items()}
for device in devices}
devices = self.__filter_devices(devices=devices)
devices = self.__finetune_incompatible_attributes(devices=devices)
if self.filter_devices is not None:
devices = self._apply_devices_filter(devices=devices)
return devices
def _monitor_changes(self, on_connect: callable | None = None, on_disconnect: callable | None = None,
check_every_seconds: int | float = _SECONDS_BETWEEN_CHECKS) -> None:
"""
Monitors the USB devices. This function should ALWAYS be called from a background thread.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive two
arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to receive
two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param check_every_seconds: int | float. The number of seconds to wait between each check for changes in the
USB devices. Defaults to 0.5 seconds.
"""
# If running this in a background thread, we MUST create the WMI interface inside the thread.
self._wmi_interface = self.__create_wmi_interface()
super(_WindowsUSBDetector, self)._monitor_changes(on_connect=on_connect, on_disconnect=on_disconnect,
check_every_seconds=check_every_seconds)
def __filter_devices(self, devices: dict[str, dict[str, tuple[str]|str]]) -> dict[str, dict[str, tuple[str]|str]]:
"""
Filters the devices to only include the ones that are USB devices.
:param devices: dict[str, dict[str, str]]. The dictionary of devices to filter.
:return: dict[str, dict[str, str]]. The filtered devices.
"""
return {device_id: device_info for device_id, device_info in devices.items()
if not any(substring in device_info[DEVTYPE] for substring in _WINDOWS_NON_USB_DEVICES_IDS)}
def __finetune_incompatible_attributes(self, devices: dict[str, dict[str | tuple[str, ...]]]) -> dict[str, dict[str, str]]:
"""
Transforms some attributes to be more similar to the Linux attributes.
:param devices: dict[str, str|tuple[str,...]]. The dictionary of devices to transform.
:return: dict[str, dict[str, str]]. The transformed devices.
"""
for device_id, device_info in devices.items():
driver_type = self.__get_driver_type_from_device_id(device_id=device_id)
new_attributes = {attribute: self.__apply_regex(value=device_id, regex=regex)#device_info[attribute], regex)
for attribute, regex in _WINDOWS_REGEX_ATTRIBUTES_BY_DRIVER[driver_type].items()
if attribute in device_info}
device_info.update(new_attributes)
new_attributes = {attr: device_info[attr].upper() for attr in _WINDOWS_TO_LOWERCASE_ATTRIBUTES}
device_info.update(new_attributes)
return devices
def __apply_regex(self, value: tuple[str] | str, regex: str) -> str:
"""
Apply the regex to a value, taking into account if it is a tuple or not.
:param value: tuple[str] | str. The value to apply the regex to.
:param regex: str. The regex to apply.
:return: str. The value after applying the regex.
"""
if isinstance(value, str):
value = (value,)
values_found = []
for value in value:
match = re.search(regex, value)
if match is not None:
values_found.append(match.group(1))
# If no value was found, return the original value
if len(values_found) == 0:
warn(f"Could not find a value for the regex '{regex}' in the value '{value}'")
return value
# Otherwise, return check there are no inconsistencies and return the value
assert all(value == values_found[0] for value in values_found), "The values found are not all the same"
return values_found[0]
def __get_driver_type_from_device_id(self, device_id: str) -> str:
"""
Returns the driver type from the device ID.
:param device_id: str. The device ID.
:return: str. The driver type.
"""
device_splitted_info = device_id.split('\\')
assert len(device_splitted_info) >= 1, f"The device ID '{device_id}' is not valid"
driver_type = device_splitted_info[0]
assert driver_type in _WINDOWS_REGEX_ATTRIBUTES_BY_DRIVER, f"The driver type '{driver_type}' is not supported " \
f"yet, please create an issue in github"
return driver_type
def __create_wmi_interface(self):
from pythoncom import CoInitialize
CoInitialize()
import wmi
# If running this in a background thread, we MUST create the WMI interface inside the thread.
return wmi.WMI() ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1731780303.0
usb_monitor-1.23/usbmonitor/attributes.py 0000644 0000765 0000024 00000002066 14716157317 017642 0 ustar 00haru staff """
device_info dictionary attributes. Same attributes as Linux udev (independently of the OS where the library is running).
All these attributes are the keys of the device_info dictionary returned by the `get_available_devices` method.
The expected values of these attributes are all strings, except for the `ID_USB_INTERFACES` attribute, which is a tuple.
Author: Eric-Canas
Date: 26-03-2023
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
ID_VENDOR_ID = 'ID_VENDOR_ID'
ID_VENDOR = 'ID_VENDOR'
ID_MODEL = 'ID_MODEL'
ID_MODEL_ID = 'ID_MODEL_ID'
ID_SERIAL = 'ID_SERIAL'
ID_USB_INTERFACES = 'ID_USB_INTERFACES'
ID_REVISION = 'ID_REVISION'
ID_USB_CLASS_FROM_DATABASE = 'ID_USB_CLASS_FROM_DATABASE'
ID_VENDOR_FROM_DATABASE = 'ID_VENDOR_FROM_DATABASE'
ID_MODEL_FROM_DATABASE = 'ID_MODEL_FROM_DATABASE'
DEVNAME = 'DEVNAME'
DEVTYPE = 'DEVTYPE'
DEVICE_ATTRIBUTES = (ID_MODEL_ID, ID_MODEL, ID_MODEL_FROM_DATABASE, ID_VENDOR, ID_VENDOR_ID, ID_VENDOR_FROM_DATABASE,
ID_USB_INTERFACES, ID_USB_CLASS_FROM_DATABASE, DEVNAME, DEVTYPE, ID_SERIAL)
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1717874808.0
usb_monitor-1.23/usbmonitor/usbmonitor.py 0000644 0000765 0000024 00000015773 14631130170 017646 0 ustar 00haru staff """
USBMonitor: USBMonitor is an easy-to-use cross-platform library for USB device monitoring that simplifies
tracking of connections, disconnections, and examination of connected device attributes on both
Windows and Linux, freeing the user from platform-specific details or incompatibilities.
Author: Eric-Canas
Date: 24-03-2023
Email: eric@ericcanas.com
Github: https://github.com/Eric-Canas
"""
from __future__ import annotations
from .__platform_specific_detectors._constants import _SECONDS_BETWEEN_CHECKS, _THREAD_JOIN_TIMEOUT_SECONDS
from warnings import warn
import sys
class USBMonitor:
def __init__(self, filter_devices: list[dict[str, str]] | tuple[dict[str, str]] | None = None):
"""
Creates a new USBMonitor object. This object can be used to monitor USB devices connected to the system.
:param filter_devices: list[dict[str, str]] | tuple[dict[str, str]] | None. A list of dictionaries containing
the device information to filter the devices by. If None, no filtering will be done. The dictionaries
must contain the same keys as the dictionaries returned by the `get_available_devices` method.
For example, if you want to only monitor devices with ID_MODEL_ID = "A2B2" or "ABCD" you could pass
filter_devices=({"ID_MODEL_ID": "A2B2"}, {"ID_MODEL_ID": "ABCD"}).
"""
if sys.platform.startswith('linux'):
from .__platform_specific_detectors._linux_usb_detector import _LinuxUSBDetector
self.monitor = _LinuxUSBDetector(filter_devices=filter_devices)
elif sys.platform.startswith('win'):
from .__platform_specific_detectors._windows_usb_detector import _WindowsUSBDetector
self.monitor = _WindowsUSBDetector(filter_devices=filter_devices)
elif sys.platform.startswith('darwin'):
from .__platform_specific_detectors._darwin_usb_detector import _DarwinUSBDetector
self.monitor = _DarwinUSBDetector(filter_devices=filter_devices)
else:
raise NotImplementedError(f"Your OS is not supported: {sys.platform}")
def get_available_devices(self) -> dict[str, dict[str, str | tuple[str, ...]]]:
"""
Returns a dictionary of the currently available devices, where the key is the device ID and the value is a
dictionary of the device's information. These keys IDs can be found at attributes.DEVICE_ATTRIBUTES. They
do always correspond with the default Linux IDs (independently of the OS where the library is running).
:return: dict[str, dict[str, str|tuple[str, ...]]]. The key is the device ID, the value is a dictionary of the device's
information.
"""
return self.monitor.get_available_devices()
def check_changes(self, on_connect: callable | None = None, on_disconnect: callable | None = None,
update_last_check_devices: bool = True) -> None:
"""
Checks for changes in the USB devices. If a device is removed, the `on_disconnect` function will be called
with the device ID as the first argument and the device information as the second argument. If a device is
added, the `on_connect` function with the same arguments.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive
two arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to
receive two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param update_last_check_devices: bool. Whether to update the last checked devices to the current devices
"""
self.monitor.check_changes(on_connect=on_connect, on_disconnect=on_disconnect,
update_last_check_devices=update_last_check_devices)
def changes_from_last_check(self, update_last_check_devices: bool = True) -> tuple[dict[str, str], dict[str, str]]:
"""
Returns a tuple of two tuples, the first containing the device IDs of the devices that were removed, the second
containing the device IDs of the devices that were added.
:param update_last_check_devices: bool. Whether to update the last checked devices to the current devices
:return: tuple[dict[str, str], dict[str, str]]. The first tuple contains the information of the devices that
were removed, the second tuple contains the information of the new devices that were added.
"""
return self.monitor.changes_from_last_check(update_last_check_devices=update_last_check_devices)
def start_monitoring(self, on_connect: callable|None = None, on_disconnect: callable|None = None,
check_every_seconds: int | float = _SECONDS_BETWEEN_CHECKS) -> None:
"""
Starts monitoring the USB devices. This function will trigger a background thread that will check for changes
in the USB devices every `check_every_seconds` seconds. If a device is removed, the `on_disconnect` function
will be called with the device ID as the first argument and the device information as the second argument.
If a device is added, the `on_connect` function with the same arguments.
:param on_connect: callable | None. The function to call when a device is added. It is expected to receive two
arguments, the device ID and the device information. on_connect(device_id: str, device_info: dict[str, str])
:param on_disconnect: callable | None. The function to call when a device is removed. It is expected to receive
two arguments, the device ID and the device information. on_disconnect(device_id: str, device_info: dict[str, str])
:param check_every_seconds: int | float. The number of seconds to wait between each check for changes in the
USB devices. Defaults to 0.5 seconds.
"""
if on_connect is None and on_disconnect is None:
warn("You are starting the monitor without any callback functions. This won't notice anything "
"when a device is connected or disconnected.")
self.monitor.start_monitoring(on_connect=on_connect, on_disconnect=on_disconnect,
check_every_seconds=check_every_seconds)
def stop_monitoring(self, timeout=_THREAD_JOIN_TIMEOUT_SECONDS) -> None:
"""
Stops monitoring the USB devices. This function will stop the background thread that was checking for changes
in the USB devices.
"""
self.monitor.stop_monitoring(timeout=timeout)
# When requesting a function that does not exist, it will be redirected to the monitor
def __getattr__(self, item):
if hasattr(self.monitor, item):
return getattr(self.monitor, item)
else:
raise AttributeError(f"USBMonitor has no attribute '{item}'")