Skip to content

Hardware

busylight_core.hardware

USB Hardware Description

This module provides a description of USB hardware devices, including HID and serial connections.

Attributes

busylight_core.hardware.HardwareHandle module-attribute

HardwareHandle = Device | Serial

Classes

busylight_core.hardware.ConnectionType

Bases: int, Enum

USB device connection protocols supported by the library.

Defines the different communication protocols that can be used to interact with USB status light devices. Use these values with Hardware.enumerate() to filter device discovery by protocol type.

Attributes
busylight_core.hardware.ConnectionType.ANY class-attribute instance-attribute
ANY = -1
busylight_core.hardware.ConnectionType.UNKNOWN class-attribute instance-attribute
UNKNOWN = 0
busylight_core.hardware.ConnectionType.HID class-attribute instance-attribute
HID = 1
busylight_core.hardware.ConnectionType.SERIAL class-attribute instance-attribute
SERIAL = 2
busylight_core.hardware.ConnectionType.BLUETOOTH class-attribute instance-attribute
BLUETOOTH = 3

busylight_core.hardware.Hardware dataclass

Hardware(
    device_type,
    path,
    vendor_id,
    product_id,
    serial_number,
    manufacturer_string,
    product_string=None,
    release_number=None,
    usage=None,
    usage_page=None,
    interface_number=None,
    bus_type=None,
    is_acquired=False,
)

Represents a USB-connected status light hardware device.

Contains all the information needed to identify, connect to, and communicate with a USB device that can function as a status light. Hardware instances are typically created through discovery methods (enumerate, from_hid, from_portinfo) and then used to initialize Light instances for device control.

Attributes
busylight_core.hardware.Hardware.device_type instance-attribute
device_type
busylight_core.hardware.Hardware.path instance-attribute
path
busylight_core.hardware.Hardware.vendor_id instance-attribute
vendor_id
busylight_core.hardware.Hardware.product_id instance-attribute
product_id
busylight_core.hardware.Hardware.serial_number instance-attribute
serial_number
busylight_core.hardware.Hardware.manufacturer_string instance-attribute
manufacturer_string
busylight_core.hardware.Hardware.product_string class-attribute instance-attribute
product_string = None
busylight_core.hardware.Hardware.release_number class-attribute instance-attribute
release_number = None
busylight_core.hardware.Hardware.usage class-attribute instance-attribute
usage = None
busylight_core.hardware.Hardware.usage_page class-attribute instance-attribute
usage_page = None
busylight_core.hardware.Hardware.interface_number class-attribute instance-attribute
interface_number = None
busylight_core.hardware.Hardware.bus_type class-attribute instance-attribute
bus_type = None
busylight_core.hardware.Hardware.is_acquired class-attribute instance-attribute
is_acquired = False
busylight_core.hardware.Hardware.device_id cached property
device_id

A tuple of the vendor and product identifiers.

Each item in the tuple is a 16-bit integer.

busylight_core.hardware.Hardware.handle cached property
handle

Hardware device I/O handle.

Functions
busylight_core.hardware.Hardware.enumerate classmethod
enumerate(by_type=ANY)

Discover all USB devices that could potentially be status lights.

Scans the system for USB devices using HID and serial protocols, creating Hardware instances for each discovered device. Use this for device discovery, inventory management, or when you need to present users with available hardware options.

The returned Hardware instances represent raw device information and have not been tested for compatibility with specific Light classes. Use Light.claims() to determine which Light class can control each device.

:param by_type: Limit discovery to specific connection types or scan all types :return: List of Hardware instances representing discovered USB devices :raises NotImplementedError: If the specified connection type is not supported

Source code in src/busylight_core/hardware.py
@classmethod
def enumerate(cls, by_type: ConnectionType = ConnectionType.ANY) -> list[Hardware]:
    """Discover all USB devices that could potentially be status lights.

    Scans the system for USB devices using HID and serial protocols,
    creating Hardware instances for each discovered device. Use this
    for device discovery, inventory management, or when you need to
    present users with available hardware options.

    The returned Hardware instances represent raw device information
    and have not been tested for compatibility with specific Light classes.
    Use Light.claims() to determine which Light class can control each device.

    :param by_type: Limit discovery to specific connection types or scan all types
    :return: List of Hardware instances representing discovered USB devices
    :raises NotImplementedError: If the specified connection type is not supported
    """
    hardware_info = []

    match by_type:
        case ConnectionType.ANY:
            for connection_type in list(ConnectionType)[2:]:
                with contextlib.suppress(NotImplementedError):
                    hardware_info.extend(cls.enumerate(connection_type))

        case ConnectionType.HID:
            for device_dict in hid.enumerate():
                with contextlib.suppress(InvalidHardwareError):
                    hardware_info.append(cls.from_hid(device_dict))

        case ConnectionType.SERIAL:
            for port_info in list_ports.comports():
                with contextlib.suppress(InvalidHardwareError):
                    hardware_info.append(cls.from_portinfo(port_info))

        case _:
            msg = f"Device connection {by_type.name} not implemented"
            raise NotImplementedError(msg)

    return hardware_info
busylight_core.hardware.Hardware.from_portinfo classmethod
from_portinfo(port_info)

Create Hardware instance from serial port information.

Converts a pyserial ListPortInfo object into a Hardware instance suitable for Light class initialization. Use this when you have serial port information from pyserial's list_ports.comports() and need to create a Hardware representation.

:param port_info: Serial port information from pyserial enumeration :return: Hardware instance representing the serial device :raises InvalidHardwareError: If port information is incomplete or invalid

Source code in src/busylight_core/hardware.py
@classmethod
def from_portinfo(cls, port_info: ListPortInfo) -> Hardware:
    """Create Hardware instance from serial port information.

    Converts a pyserial ListPortInfo object into a Hardware instance
    suitable for Light class initialization. Use this when you have
    serial port information from pyserial's list_ports.comports()
    and need to create a Hardware representation.

    :param port_info: Serial port information from pyserial enumeration
    :return: Hardware instance representing the serial device
    :raises InvalidHardwareError: If port information is incomplete or invalid
    """
    try:
        return cls(
            device_type=ConnectionType.SERIAL,
            vendor_id=port_info.vid,
            product_id=port_info.pid,
            path=port_info.device.encode("utf-8"),
            serial_number=port_info.serial_number,
            manufacturer_string=port_info.manufacturer,
            product_string=port_info.product,
            bus_type=1,
        )
    except Exception:
        logger.exception("%s", port_info)
        raise InvalidHardwareError(port_info.__dict__) from None
busylight_core.hardware.Hardware.from_hid classmethod
from_hid(device)

Create Hardware instance from HID device information.

Converts a HID device dictionary (from hid.enumerate()) into a Hardware instance suitable for Light class initialization. Use this when you have HID device information and need to create a Hardware representation.

:param device: HID device dictionary from hidapi enumeration :return: Hardware instance representing the HID device :raises InvalidHardwareError: If device information is incomplete or invalid

Source code in src/busylight_core/hardware.py
@classmethod
def from_hid(cls, device: dict) -> Hardware:
    """Create Hardware instance from HID device information.

    Converts a HID device dictionary (from hid.enumerate()) into a Hardware
    instance suitable for Light class initialization. Use this when you have
    HID device information and need to create a Hardware representation.

    :param device: HID device dictionary from hidapi enumeration
    :return: Hardware instance representing the HID device
    :raises InvalidHardwareError: If device information is incomplete or invalid
    """
    try:
        return cls(device_type=ConnectionType.HID, **device)
    except Exception:
        logger.exception("%s", device)
        raise InvalidHardwareError(device) from None
busylight_core.hardware.Hardware.as_dict
as_dict()
Source code in src/busylight_core/hardware.py
def as_dict(self) -> dict[str, int | str | bytes | None]:
    return {
        "vendor_id": self.vendor_id,
        "product_id": self.product_id,
        "path": self.path,
        "serial_number": self.serial_number,
        "manufacturer": self.manufacturer_string,
        "product_string": self.product_string,
        "release_number": self.release_number,
        "usage": self.usage,
        "usage_page": self.usage_page,
        "bus_type": self.bus_type,
        "device_type": self.device_type.name,
    }
busylight_core.hardware.Hardware.acquire
acquire()

Open the hardware device.

The device is available for I/O operations after this method returns. If the device is already acquired, no actions are taken.

Source code in src/busylight_core/hardware.py
def acquire(self) -> None:
    """Open the hardware device.

    The device is available for I/O operations after this method
    returns. If the device is already acquired, no actions are
    taken.

    """
    if self.is_acquired:
        logger.debug(f"{self} already acquired")
        return

    match self.device_type:
        case ConnectionType.HID:
            self.handle.open_path(self.path)
            self.is_acquired = True
        case ConnectionType.SERIAL:
            self.handle.open()
            self.is_acquired = True
        case _:
            msg = f"{self.device_type.value.title()} hardware not implemented"
            raise NotImplementedError(msg)
busylight_core.hardware.Hardware.release
release()

Close the hardware device.

Subsequent I/O operations to this device will fail after this method returns. If the device has already been released, no action is taken.

Source code in src/busylight_core/hardware.py
def release(self) -> None:
    """Close the hardware device.

    Subsequent I/O operations to this device will fail after this
    method returns. If the device has already been released, no
    action is taken.
    """
    if not self.is_acquired:
        logger.debug(f"{self} already released")
        return

    match self.device_type:
        case ConnectionType.HID | ConnectionType.SERIAL:
            self.handle.close()
            self.is_acquired = False
        case _:
            msg = f"{self.device_type.value.title()} hardware not implemented"
            raise NotImplementedError(msg)