"""Base bus and driver API. Copyright Jonas Malaco and contributors SPDX-License-Identifier: GPL-3.0-or-later """ # uses the psf/black style class BaseDriver: """Base driver API. All drivers are expected to implement this API for compatibility with the liquidctl CLI or other thirdy party tools. Drivers will automatically implement the context manager protocol, but this should only be used from a call to `connect`. Example: for dev in .find_supported_devices(): with dev.connect(): print(dev.get_status()) if dev.serial_number == '49385027ZP': dev.set_fixed_speed('fan3', 42) """ @classmethod def find_supported_devices(cls, **kwargs): """Find and bind to compatible devices. Returns a list of bound driver instances. """ raise NotImplementedError() def connect(self, **kwargs): """Connect to the device. Returns `self`. ## Notes for driver authors Procedure before any read or write operation can be performed. Typically a handshake between driver and device. """ raise NotImplementedError() def initialize(self, **kwargs): """Initialize the device and the driver. This method should be called every time the systems boots, resumes from a suspended state, or if the device has just been (re)connected. In those scenarios, no other method, except `connect()` or `disconnect()`, should be called until the device and driver has been (re-)initialized. Returns None or a list of `(property, value, unit)` tuples, similarly to `get_status()`. ## Notes for driver authors Apart from `connect()`, some devices might require a one-time initialization procedure after powering on, or to detect hardware changes. This should be called *after* connecting to the device. """ raise NotImplementedError() def disconnect(self, **kwargs): """Disconnect from the device. ## Notes for driver authors Procedure before the driver can safely unbind from the device. Typically just cleanup. """ raise NotImplementedError() def get_status(self, **kwargs): """Get a status report. Returns a list of `(property, value, unit)` tuples. """ raise NotImplementedError() def set_color(self, channel, mode, colors, **kwargs): """Set the color mode for a specific channel.""" raise NotImplementedError() def set_screen(self, channel, mode, value, **kwargs): """Set the screen mode and content. Unstable. """ raise NotImplementedError() def set_speed_profile(self, channel, profile, **kwargs): """Set channel to follow a speed duty profile.""" raise NotImplementedError() def set_fixed_speed(self, channel, duty, **kwargs): """Set channel to a fixed speed duty.""" raise NotImplementedError() @property def description(self): """Human readable description of the corresponding device.""" raise NotImplementedError() @property def vendor_id(self): """Numeric vendor identifier, or None if N/A.""" raise NotImplementedError() @property def product_id(self): """Numeric product identifier, or None if N/A.""" raise NotImplementedError() @property def release_number(self): """Device versioning number, or None if N/A. In USB devices this is bcdDevice. """ raise NotImplementedError() @property def serial_number(self): """Serial number reported by the device, or None if N/A.""" raise NotImplementedError() @property def bus(self): """Bus the device is connected to, or None if N/A.""" raise NotImplementedError() @property def address(self): """Address of the device on the corresponding bus, or None if N/A. This typically depends on the bus enumeration order. """ raise NotImplementedError() @property def port(self): """Physical location of the device, or None if N/A. This typically refers to a USB port, which is *not* dependent on bus enumeration order. However, a USB port is hub-specific, and hubs can be chained. Thus, for USB devices, this returns a tuple of port numbers, from the root hub to the parent of the connected device. """ raise NotImplementedError() def __enter__(self): return self def __exit__(self, *args): self.disconnect() class BaseBus: """Base bus API.""" def find_devices(self, **kwargs): """Find compatible devices and yield corresponding driver instances.""" return def find_all_subclasses(cls): """Recursively find loaded subclasses of `cls`. Returns a set of subclasses of `cls`. """ sub = set(cls.__subclasses__()) return sub.union([s for c in cls.__subclasses__() for s in find_all_subclasses(c)])