def _make_interrupt_transfer(self, endpoint, data, size, actual_length, timeout=2000): """ Read message from USB device :param endpoint: Endpoint address to listen on. :param data: Variable to hold received data. :param size: Size expected to be received. :param actual_length: Actual length received. """ if self.interface is None: raise USBDeviceInterfaceNotClaimedError(self.dev_key) ret = usb.interrupt_transfer( self.__libusb_dev_handle__, # ct.c_char_p endpoint, # ct.ubyte ct.cast(data, ct.POINTER(ct.c_ubyte)), # ct.POINTER(ct.c_ubyte) size, ct.cast(actual_length, ct.POINTER(ct.c_int)), # ct.POINTER(ct.c_int) timeout) # ct.c_uint32 if ret >= 0: _logger.debug(f'Read {size} ' + _('bytes from device').format(size) + f' {self.dev_key}.') return True usb_error(ret, _('Failed to communicate with device') + f' {self.dev_key}.') return False
def run(): # Set global debug value and setup application logging. ToolContextManager.initialize_logging(tool_cmd) parser = ToolContextManager.get_argparser(tool_cmd, tool_desc) parser.add_argument('-c', '--class-id', help=_('filter by device class id'), type=str) parser.add_argument('-d', '--descriptors', help=_('Show device descriptor values.'), default=False, action='store_true') args = parser.parse_args() # Verify class id is valid by looking in the Enum by value. if args.device_class_id: try: DeviceClassID(args.device_class_id) except ValueError: _logger.error('Invalid class id argument value.') return -1 with ToolContextManager(tool_cmd, args) as tool_env: process = ListDevicesClass(args, tool_env) exit_code = process.run() return exit_code
def validate_config_base(self, config_file, resource_types): """ Validate the configuration file against the base schema. :param config_file: Absolute path to configuration json file. :param resource_types: list of strings representing valid 'resourceType' values. :return: config dict. """ # Read the base schema, all json configs must validate against this schema. base_schema = self.load_config_schema('base.schema') if not base_schema: return None try: with open(config_file) as h: config = json.loads(h.read()) except JSONDecodeError: _logger.error(_('Configuration file is not valid JSON.')) return None try: validate(config, base_schema) except ValidationError as e: _logger.error( _('Configuration file did not validate against the base schema.' ) + f'\n{e}') return None if config['resourceType'] not in resource_types: valid_types = ",".join(resource_types) _logger.error( _('Resource type does not match accepted types') + f' ({valid_types}).') return None return config
def get_argparser(tool_cmd, tool_desc): """ :param tool_cmd: Tool command line id. :param tool_desc: Tool description. """ # Setup program arguments. parser = argparse.ArgumentParser(prog=tool_cmd, description=tool_desc) parser.add_argument('--debug', help=_('enable debug output'), default=False, action='store_true') parser.add_argument('--log-file', help=_('write output to a log file'), default=False, action='store_true') parser.add_argument('-q', '--quiet', help=_('suppress normal output'), default=False, action='store_true') parser.add_argument('--bus', help=_('filter by usb device bus number'), type=int, default=None) parser.add_argument('--address', help=_('filter by usb device address number'), type=int, default=None) return parser
def _find_devices(self): """ Find all attached USB devices that match the filter. """ self.error = False self.device_count = 0 self._usb_devices = list() # Get device list from libusb. dev_list = ct.POINTER(ct.POINTER(usb.device))() cnt = usb.get_device_list(None, ct.byref(dev_list)) if cnt < 0: usb_error(cnt, _('Failed to find attached USB devices.')) self.error = True return _logger.debug(_('Vendor filters: ') + ', '.join(self._filters)) _logger.debug(_('Searching for Ultimarc USB devices...')) for dev in dev_list: if not dev: # We must always look for the Null device and quit the loop. break dev_desc = usb.device_descriptor() ret = usb.get_device_descriptor(dev, ct.byref(dev_desc)) if ret != usb.LIBUSB_SUCCESS: usb_error(ret, _('failed to get USB device descriptor.')) continue if not self._filters or f'{dev_desc.idVendor:04x}' in self._filters: ud = USBDeviceInfo(dev, dev_desc) self._usb_devices.append(ud) self.device_count += 1 _logger.debug(_('Device search complete.')) usb.free_device_list(dev_list, 1)
def _get_device_handle(self): """ Return the raw LibUSB device handle pointer for our device. Must only be called from __enter__(). :return: LibUSBDeviceHandle """ # Get device list from libusb. self.__dev_list__ = ct.POINTER(ct.POINTER(usb.device))() cnt = usb.get_device_list(None, ct.byref(self.__dev_list__)) if cnt < 0: usb_error(cnt, _('Failed to find attached USB devices.')) return None for dev in self.__dev_list__: if not dev: # We must always look for the Null device and quit the loop. break dev_desc = usb.device_descriptor() ret = usb.get_device_descriptor(dev, ct.byref(dev_desc)) if ret != usb.LIBUSB_SUCCESS: usb_error(ret, _('failed to get USB device descriptor.')) continue if self.vendor_id == dev_desc.idVendor and self.product_id == dev_desc.idProduct and \ self.bus == usb.get_bus_number(dev) and self.address == usb.get_device_address(dev): dev_handle = ct.POINTER(usb.device_handle)() ret = usb.open(dev, ct.byref(dev_handle)) if ret == usb.LIBUSB_SUCCESS: return dev_handle usb_error(ret, _('Failed to open device') + f' {self.dev_key}.') self._close_device_list_handle() _logger.debug( _('Device was not found on host when trying to open it.')) return None
def write_alt(self, b_request, report_id, w_index, data=None, size=None, request_type=USBRequestType.REQUEST_TYPE_CLASS, recipient=USBRequestRecipient.RECIPIENT_INTERFACE): """ Write message to USB device. :param b_request: Request field for the setup packet :param report_id: report_id portion of the Value field for the setup packet. :param w_index: Index field for the setup packet :param data: ctypes structure class. :param size: size of message. :param request_type: USBRequestType enum value. :param recipient: USBRequestRecipient enum value. :return: True if successful otherwise False. """ if self.interface is None: raise USBDeviceInterfaceNotClaimedError(self.dev_key) if not isinstance(request_type, USBRequestType): raise ValueError( 'Request type argument must be a USBRequestType enum value.') if not isinstance(recipient, USBRequestRecipient): raise ValueError( 'Request type argument must be a USBRequestRecipient enum value.' ) if isinstance(report_id, int): report_id = ct.c_uint8(report_id) # Combine direction, request type and recipient together. request_type = USBRequestDirection.ENDPOINT_OUT | request_type | recipient w_value = ct.c_uint16(USB_REPORT_TYPE_OUT.value | report_id.value) payload = (ct.c_ubyte * 5)(0) offset = 1 if report_id else 0 pos = 0 if report_id: payload[0] = report_id while pos < size: payload_size = 4 if size - pos > 4 else size - pos ct.memmove( ct.addressof(payload) + offset, ct.byref(data, pos), payload_size) ret = self._make_control_transfer(request_type, b_request, w_value, w_index, ct.byref(payload), payload_size + offset) pos += payload_size _logger.debug(_(' '.join(hex(x) for x in payload))) _logger.debug( _('Write operation complete, wrote {} bytes.').format(pos)) return ret
def set_pin(self, pin_config): """ Write a pin to the current Mini-pac device """ pin = pin_config[0] # Get the current configuration from the device cur_config = self.get_current_configuration() macros = self._create_macro_array_(cur_config) action_index, alternate_action_index, shift_index = PinMapping[pin] action = pin_config[1].upper() cur_config.bytes[action_index] = 0 if action in IPACSeriesMapping: cur_config.bytes[action_index] = IPACSeriesMapping[action] else: macro_val = 0xe0 for x in macros: if x['name'].upper() == action: cur_config.bytes[action_index] = macro_val else: macro_val += 1 if cur_config.bytes[action_index] == 0: _logger.info(_(f'{pin} action "{action}" is not a valid value')) # Pin alternate action alternate_action = pin_config[2].upper() # Empty string means no value if len(alternate_action) > 0: cur_config.bytes[alternate_action_index] = 0 if alternate_action in IPACSeriesMapping: cur_config.bytes[alternate_action_index] = IPACSeriesMapping[ alternate_action] else: macro_val = 0xe0 for x in macros: if x['name'].upper() == alternate_action: cur_config.bytes[alternate_action_index] = macro_val else: macro_val += 1 if cur_config.bytes[alternate_action_index] == 0: _logger.info( _(f'{pin} alternate action "{alternate_action}" is not a valid value' )) else: # No Alternate Value cur_config.bytes[alternate_action_index] = 0 # Pin designated as shift cur_config.bytes[shift_index] = 0x40 if pin_config[3].lower() in [ 'true', '1', 't', 'y' ] else 0x0 # Header - Setup to send back to device cur_config.header.type = 0x50 cur_config.header.byte_2 = 0xdd cur_config.header.byte_3 = 0x0f return self.write_alt(USBRequestCode.SET_CONFIGURATION, int(0x03), MINI_PAC_INDEX, cur_config, ct.sizeof(cur_config))
def run(self): """ Main program process :return: Exit code value """ # write program main process here after setting 'tool_cmd' and 'tool_desc'... # Get devices we want to work with based on filters. devices = [ dev for dev in self.tool_env.devices.filter( class_id=DeviceClassID.MiniPac, bus=self.args.bus, address=self.args.address) ] if not devices: _logger.error(_('No Mini-PAC devices found, aborting')) return -1 # Set debounce value if self.args.set_debounce: for dev in devices: with dev as dev_h: response = dev_h.set_debounce(self.args.set_debounce) if response: _logger.info( f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('debounce successfully applied to device.')) # Get config from device if self.args.get_config: for dev in devices: with dev as dev_h: indent = int( self.args.indent) if self.args.indent else None response = dev_h.get_device_config(indent, self.args.file) _logger.info(response) # Set mini-pac device configuration from a configuration file if self.args.set_config: for dev in devices: with dev as dev_h: use_current = self.args.current dev_h.set_config(self.args.set_config, use_current) _logger.info( f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('configuration successfully applied to device.')) if self.args.set_pin: for dev in devices: with dev as dev_h: dev_h.set_pin(self.args.set_pin) _logger.info(f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('pin successfully applied to device.')) return 0
def __exit__(self, exc_type, exc_val, exc_tb): """ Clean up or close everything we need to """ if self.__dev_handle__: _logger.debug(_('Closing USB device') + f' {self.dev_key}.') usb.close(self.__dev_handle__) self.__dev_handle__ = None self._close_device_list_handle() if exc_type is not None: # print((traceback.format_exc())) _logger.error( _('USB device encountered an unexpected error, quitting.'))
def __init__(self, vendor_filter: list = None): """ :param vendor_filter: list of vendor/manufacturer IDs to capture. """ if vendor_filter: if not isinstance(vendor_filter, list): raise TypeError(_('USB device filter must be a list.')) for vendor_id in vendor_filter: if not isinstance(vendor_id, str) or len(vendor_id) != 4: raise ValueError( _("Invalid USB vendor/manufacturer id") + f' ({vendor_id}).') self._filters = vendor_filter self._find_devices()
class AimTrakDevice(USBDeviceHandle): """ Manage an AimTrak light gun device """ class_id = 'aimtrak' # Used to match/filter devices. class_descr = _('Aimtrak Light Gun') pass
def read_interrupt(self, endpoint, response, uses_report_id=True): """ Read response from USB device on the interrupt endpoint. :param endpoint: Endpoint to receive message on. :param response: Variable holding the complete response from the device. :param uses_report_id: True if report_id is in the message. """ if self.interface is None: raise USBDeviceInterfaceNotClaimedError(self.dev_key) actual_length = ct.c_int(0) length = 5 if uses_report_id else 4 # Expecting the report_id in the message payload = (ct.c_ubyte * length)(0) payload_ptr = ct.byref(payload) # Here don't add the report_id into the response structure, 4 instead of 5 for pos in range(0, ct.sizeof(response), 4 if uses_report_id else 5): self._make_interrupt_transfer(endpoint, payload_ptr, length, ct.byref(actual_length)) # Remove report_id (byte 0) if it is used actual_length = actual_length \ if actual_length == length and not uses_report_id else ct.c_int(actual_length.value - 1) ct.memmove( ct.addressof(response) + pos, ct.byref(payload, 1) if uses_report_id else ct.byref(payload), actual_length.value) _logger.debug(_(' '.join(hex(x) for x in payload))) return response
def __exit__(self, exc_type, exc_val, exc_tb): """ Clean up or close everything we need to """ self._env_config_obj.cleanup() remove_pidfile(self._command) if exc_type is not None: print((traceback.format_exc())) _logger.error(_('tool encountered an unexpected error, quitting.')) exit(1)
def _make_control_transfer(self, request_type, b_request, w_value, w_index, data, size, timeout=2000): """ Read/Write data from USB device. :param request_type: Request type value. Combines direction, type and recipient enum values. Bit 7: Request direction (0=Host to device - Out, 1=Device to host - In). Bits 5-6: Request type (0=standard, 1=class, 2=vendor, 3=reserved). Bits 0-4: Recipient (0=device, 1=interface, 2=endpoint,3=other). :param b_request: Request field for the setup packet. The actual request, see USBRequestCodes Enum. :param w_value: Value field for the setup packet. A word-size value that varies according to the request. For example, in the CLEAR_FEATURE request the value is used to select the feature, in the GET_DESCRIPTOR request the value indicates the descriptor type and in the SET_ADDRESS request the value contains the device address. :param w_index: Index field for the setup packet. A word-size value that varies according to the request. The index is generally used to specify an endpoint or an interface. :param data: ctypes structure class. :param size: size of data. :return: True if successful otherwise False. https://www.jungo.com/st/support/documentation/windriver/802/wdusb_man_mhtml/node55.html#SECTION001213000000000000000 """ if self.interface is None: raise USBDeviceInterfaceNotClaimedError(self.dev_key) if not isinstance(b_request, USBRequestCode): raise ValueError( 'b_request argument must be USBRequestCode enum value.') ret = usb.control_transfer( self.__libusb_dev_handle__, # ct.c_char_p request_type, # ct.c_uint8 b_request, # ct.c_uint8, must be a USBRequestCode Enum value. w_value, # ct.c_uint16 w_index, # ct.c_uint16 ct.cast(data, ct.POINTER(ct.c_ubyte)), # ct.POINTER(ct.c_ubyte) ct.c_uint16(size), # ct.c_uint16 timeout) # ct.c_uint32 if ret >= 0: if request_type & USBRequestDirection.ENDPOINT_IN: _logger.debug('Read {} bytes from'.format(size) + f' {self.dev_key}.') else: _logger.debug('Wrote {} bytes to'.format(size) + f' {self.dev_key}.') return True usb_error(ret, _('Failed to communicate with device') + f' {self.dev_key}.') return False
def to_json_str(self, pac_struct): """ Converts a PacStruct to a json object """ json_obj = { 'schemaVersion': 2.0, 'resourceType': 'mini-pac-pins', 'deviceClass': self.class_id } # header configuration header = PacConfigUnion() header.asByte = pac_struct.header.byte_4 json_obj['debounce'] = get_ipac_series_debounce_key( header.config.debounce) # macros macros = self._create_macro_array_(pac_struct) if len(macros): json_obj['macros'] = macros # pins pins = [] for key in PinMapping: action_index, alternate_action_index, shift_index = PinMapping[key] pin = {} if pac_struct.bytes[action_index]: pin['name'] = key pin['action'] = get_ipac_series_mapping_key( pac_struct.bytes[action_index]) if pin['action'] is None: mi = get_ipac_series_macro_mapping_index( pac_struct.bytes[action_index]) if mi is not None: pin['action'] = macros[mi]['name'] else: _logger.debug(_(f'{key} action is not a valid value')) if pac_struct.bytes[alternate_action_index]: alt_action = get_ipac_series_mapping_key( pac_struct.bytes[alternate_action_index]) if alt_action is None: mi = get_ipac_series_macro_mapping_index( pac_struct.bytes[alternate_action_index]) if mi is not None: pin['alternate_action'] = macros[mi]['name'] else: pin['alternate_action'] = alt_action if pac_struct.bytes[shift_index]: pin['shift'] = True pins.append(pin) json_obj['pins'] = pins return json_obj if self.validate_config(json_obj, 'mini-pac.schema') else None
def get_state(self): """ Return the USB button click state. :return: 1 if clicked otherwise 0, None if error. """ # TODO: Can't seem to make this work to return the button click state. See USBButtonGetState() in PacDrive.cpp. data = USBButtonColorStruct(0x02, RGBValueStruct(0x0, 0x0, 0x0)) ret = self.read(USBRequestCode.CLEAR_FEATURE, USBButtonReportID, USBButtonWIndex, data, ct.sizeof(data)) if ret: return data.red _logger.error(_('Failed to read usb button state.')) return None
def get_device_config(self, indent=None, file=None): """ Return a json string of the device configuration """ config = self.get_current_configuration() json_obj = self.to_json_str(config) if file: try: with open(file, 'w') as h: json.dump(json_obj, h, indent=indent) except FileNotFoundError as err: return err return _('Wrote Mini-pac configuration to file.') else: return json.dumps(json_obj, indent=indent) if config else None
def get_color(self): """ Return override USB button RGB color. Will return (0, 0, 0) if the device has not yet been written to using the set_color() method. :return: (Integer, Integer, Integer) or None """ data = USBButtonColorStruct(0x01, RGBValueStruct(0x0, 0x0, 0x0)) ret = self.read(USBRequestCode.CLEAR_FEATURE, USBButtonReportID, USBButtonWIndex, data, ct.sizeof(data)) if ret: return data.red, data.green, data.blue _logger.error(_('Failed to read color data from usb button.')) return None, None, None
def set_color(self, red, green, blue): """ Set USB button color, overrides the current device configuration released color. :param red: integer between 0 and 255 :param green: integer between 0 and 255 :param blue: integer between 0 and 255 :return: True if successful otherwise False. """ for color in [red, green, blue]: if not isinstance(color, int) or not 0 <= color <= 255: raise ValueError(_('Color argument value is invalid')) data = USBButtonColorStruct(0x01, RGBValueStruct(red, green, blue)) return self.write(USBRequestCode.SET_CONFIGURATION, USBButtonReportID, USBButtonWIndex, data, ct.sizeof(data))
def get_descriptor_string(self, index): """ Return the String value of a device descriptor property. :param index: integer :return: String or None """ buf = ct.create_string_buffer(1024) ret = usb.get_string_descriptor_ascii( self.__libusb_dev_handle__, index, ct.cast(buf, ct.POINTER(ct.c_ubyte)), ct.sizeof(buf)) if ret > 0: result = buf.value.decode('utf-8') return result usb_error(ret, _('failed to get descriptor property field string value.'), debug=True) return None
def __init__(self, command, args): """ Initialize Tool Context Manager :param command: command name :param args: parsed argparser commandline arguments object. """ if not command: _logger.error(_('command not set, aborting.')) exit(1) self._command = command # The Environment dict is where we can setup any information related to all tools. self._env = {'command': command, 'devices': USBDevices(_VENDOR_FILTER)} write_pidfile_or_die(command) if not self._env: remove_pidfile(command) exit(1)
def validate_config(self, config, schema_file): """ Validate a configuration dict against a schema file. :param config: dict :param schema_file: relative or abspath of schema. :return: True if valid otherwise False. """ schema = self.load_config_schema(schema_file) if not schema: return False try: validate(config, schema) except ValidationError as e: _logger.error( _('Configuration file did not validate against config schema.') ) _logger.error(e) return False return True
def __enter__(self): """ Return object with properties set to config values """ _logger.debug(_('Opening USB device') + f' {self.dev_key}') dev_handle = self._get_device_handle() if not dev_handle: raise USBDeviceNotFoundError(self.dev_key) # We need to claim the USB device interface. # usb.set_auto_detach_kernel_driver(dev_handle, 1) # status = usb.claim_interface(dev_handle, 0) # if status != usb.LIBUSB_SUCCESS: # usb.close(dev_handle) # self._close_device_list_handle() # raise USBDeviceClaimInterfaceError(self.dev_key) self._close_device_list_handle() self.__dev_handle__ = dev_handle self.__dev_handle_obj__ = self.__dev_class__(self.__dev_handle__, self.dev_key) return self.__dev_handle_obj__
def filter(self, class_id=None, bus=None, address=None): """ Return iterator for all devices that match the filter. :param class_id: string :param bus: integer :param address: integer :return: iter(list) """ if (class_id and not isinstance(class_id, (str, DeviceClassID))) or (bus and not isinstance(bus, int)) or \ (address and not isinstance(address, int)): raise ValueError(_('Invalid filter method argument')) devices = list() for dev in self._usb_devices: if class_id and dev.class_id != (class_id if isinstance( class_id, str) else class_id.value): continue if bus and dev.bus != bus: continue if address and dev.address != address: continue devices.append(dev) return iter(devices)
def load_config_schema(self, schema_file): """ Load the requested schema file. :param schema_file: Schema file name only, no path included. :return: schema dict. """ schema_paths = [ './ultimarc/schemas', '../ultimarc/schemas', '../../ultimarc/schemas', '../schemas', './schemas' ] schema_path = None for path in schema_paths: schema_path = os.path.abspath(os.path.join(path, schema_file)) if os.path.exists(schema_path): break if not schema_path: _logger.error(_('Unable to locate schema directory.')) return None with open(schema_path) as h: return json.loads(h.read())
def set_debounce(self, debounce): """ Set debounce value to the current Mini-pac device """ val = debounce.lower() if val in IPACSeriesDebounce: # Get the current configuration from the device cur_config = self.get_current_configuration() header = PacConfigUnion() header.config.debounce = IPACSeriesDebounce[val] cur_config.header.byte_4 = header.asByte else: _logger.info(_(f'"{debounce}" is not a valid debounce value')) return None # Header - Setup to send back to device cur_config.header.type = 0x50 cur_config.header.byte_2 = 0xdd cur_config.header.byte_3 = 0x0f return self.write_alt(USBRequestCode.SET_CONFIGURATION, int(0x03), MINI_PAC_INDEX, cur_config, ct.sizeof(cur_config))
def list_devices_found(self): """ List all the devices found. """ # Find all Ultimarc USB devices. _logger.info(_('Device classes found') + ':') for cat in self.env.devices.get_device_classes(): _logger.info(f' {cat}') _logger.info('\n' + _('Devices') + ':') for dev in self.get_devices(): _logger.info(f' {dev}') if not self.args.descriptors: return try: desc_string_fields = [ 'iManufacturer', 'iProduct', 'iSerialNumber', 'idProduct', 'idVendor' ] for dev in self.get_devices(): _logger.info('') # Open device. with dev as dev_h: _logger.info( _('Showing device descriptor properties for') + f' {dev.dev_key}') _logger.info(' ' + _('Bus') + f': {dev.bus}') _logger.info(' ' + _('Address') + f': {dev.address}') for fld in dev_h.descriptor_fields: desc_val = dev_h.get_descriptor_value(fld) if fld in desc_string_fields: desc_str = dev_h.get_descriptor_string( dev_h.get_descriptor_value(fld)) or '' _logger.info( f' {fld}: {desc_val:04x} (desc idx: 0x{desc_val:04x}, str: "{desc_str}")' ) else: _logger.info(f' {fld}: {desc_val}') except (USBDeviceNotFoundError, USBDeviceClaimInterfaceError) as e: _logger.error( _('An error occurred while inspecting device') + f' {e.dev_key}.')
def run(self): """ Main program process :return: Exit code value """ # Get devices we want to work with based on filters. devices = [ dev for dev in self.env.devices.filter( class_id=DeviceClassID.USBButton, bus=self.args.bus, address=self.args.address) ] if not devices: _logger.error(_('No USB button devices found, aborting')) return -1 # See if we are setting a color from the command line args. if self.args.set_color: match = re.match(_RGB_STRING_REGEX, self.args.set_color) red, green, blue = match.groups() for dev in devices: with dev as dev_h: dev_h.set_color(int(red), int(green), int(blue)) _logger.info(f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('Color') + f': RGB({red},{green},{blue}).') # Return the current color RGB values. elif self.args.get_color: for dev in devices: with dev as dev_h: red, green, blue = dev_h.get_color() if red is not None: _logger.info( f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('Color') + f': RGB({red},{green},{blue}).') # Set a random RGB color. elif self.args.set_random_color: for dev in devices: red = random.randrange(255) green = random.randrange(255) blue = random.randrange(255) with dev as dev_h: dev_h.set_color(red, green, blue) _logger.info(f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('randomly set button color to') + f' RGB({red},{green},{blue}).') # Apply a usb button config. elif self.args.set_config: for dev in devices: with dev as dev_h: application = ConfigApplication.temporary if self.args.temporary else ConfigApplication.permanent if dev_h.set_config(self.args.set_config, application): _logger.info( f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('configuration successfully applied to device.')) else: _logger.error( f'{dev.dev_key} ({dev.bus},{dev.address}): ' + _('failed to apply configuration to device.')) return 0
def run(): # Set global debug value and setup application logging. ToolContextManager.initialize_logging(tool_cmd) parser = ToolContextManager.get_argparser(tool_cmd, tool_desc) # --set-color --get-color --load-config --export-config parser.add_argument('--set-color', help=_('set usb button color with RGB value'), type=str, default=None, metavar='INT,INT,INT') parser.add_argument('--set-random-color', help=_('randomly set usb button color'), default=False, action='store_true') parser.add_argument('--get-color', help=_('output current usb button color RGB value'), default=False, action='store_true') parser.add_argument('--set-config', help=_('Set button config from config file.'), type=str, default=None, metavar='CONFIG-FILE') parser.add_argument('--temporary', help=_('Apply config until device unplugged.'), default=False, action='store_true') args = parser.parse_args() num_args = sum([ bool(args.set_color), args.set_random_color, args.get_color, bool(args.set_config) ]) if num_args == 0: _logger.warning(_('Nothing to do.')) return 0 if num_args > 1 and (not args.set_config or not args.temporary): # Enhance this check and provide better feedback on which arguments are mutually exclusive. _logger.error( _('More than one mutually exclusive argument specified.')) return -1 if args.set_color: if not re.match(_RGB_STRING_REGEX, args.set_color): _logger.error( _('Invalid RGB value found for --set-color argument.')) return -1 if not args.set_config and args.temporary: _logger.error( _('The --temporary argument can only be used with the --set-config argument.' )) return -1 if args.set_config: # Always force absolute path for config files. args.set_config = os.path.abspath(args.set_config) if not os.path.exists(args.set_config): _logger.error( _('Unable to find configuration file specified in argument.')) return -1 with ToolContextManager(tool_cmd, args) as tool_env: process = USBButtonClass(args, tool_env) exit_code = process.run() return exit_code