From c3b73964d5a2c6b8724ce12e4d8d7501b3290f6b Mon Sep 17 00:00:00 2001 From: Daniel Pavel Date: Wed, 22 May 2013 20:41:11 +0300 Subject: [PATCH] improved support for some Nano receivers --- lib/logitech/unifying_receiver/base.py | 16 +- lib/logitech/unifying_receiver/descriptors.py | 17 ++- lib/logitech/unifying_receiver/hidpp10.py | 16 +- lib/logitech/unifying_receiver/receiver.py | 142 ++++++++---------- lib/logitech/unifying_receiver/status.py | 41 ++--- lib/solaar/cli.py | 4 +- lib/solaar/gtk.py | 19 ++- lib/solaar/listener.py | 6 +- lib/solaar/ui/main_window.py | 29 ++-- rules.d/99-logitech-unifying-receiver.rules | 7 +- 10 files changed, 139 insertions(+), 158 deletions(-) diff --git a/lib/logitech/unifying_receiver/base.py b/lib/logitech/unifying_receiver/base.py index 1c64df8b..ecc9861e 100644 --- a/lib/logitech/unifying_receiver/base.py +++ b/lib/logitech/unifying_receiver/base.py @@ -67,9 +67,10 @@ class DeviceUnreachable(_KwException): # # vendor_id, product_id, usb interface number, hid driver -DEVICE_UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, 'logitech-djreceiver') -DEVICE_UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, 'logitech-djreceiver') -DEVICE_NANO_RECEIVER = (0x046d, 0xc526, 1, 'hid-generic') +DEVICE_UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, 'logitech-djreceiver') +DEVICE_UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, 'logitech-djreceiver') +DEVICE_NANO_RECEIVER = (0x046d, 0xc52f, 1, 'hid-generic') +DEVICE_VXNANO_RECEIVER = (0x046d, 0xc526, 1, 'hid-generic') def receivers(): @@ -78,8 +79,10 @@ def receivers(): yield d for d in _hid.enumerate(*DEVICE_UNIFYING_RECEIVER_2): yield d - #for d in _hid.enumerate(*DEVICE_NANO_RECEIVER): - # yield d + for d in _hid.enumerate(*DEVICE_NANO_RECEIVER): + yield d + for d in _hid.enumerate(*DEVICE_VXNANO_RECEIVER): + yield d def notify_on_receivers(callback): @@ -87,7 +90,8 @@ def notify_on_receivers(callback): _hid.monitor_async(callback, DEVICE_UNIFYING_RECEIVER, DEVICE_UNIFYING_RECEIVER_2, - # DEVICE_NANO_RECEIVER, + DEVICE_NANO_RECEIVER, + DEVICE_VXNANO_RECEIVER, ) diff --git a/lib/logitech/unifying_receiver/descriptors.py b/lib/logitech/unifying_receiver/descriptors.py index 2a78d13e..dec0b065 100644 --- a/lib/logitech/unifying_receiver/descriptors.py +++ b/lib/logitech/unifying_receiver/descriptors.py @@ -51,11 +51,11 @@ def check_features(device, already_known): # _DeviceDescriptor = namedtuple('_DeviceDescriptor', - ['name', 'kind', 'codename', 'registers', 'settings']) + ['name', 'kind', 'product_id', 'codename', 'registers', 'settings']) DEVICES = {} -def _D(name, codename=None, kind=None, registers=None, settings=None): +def _D(name, codename=None, kind=None, product_id=None, registers=None, settings=None): if kind is None: kind = (_hidpp10.DEVICE_KIND.mouse if 'Mouse' in name else _hidpp10.DEVICE_KIND.keyboard if 'Keyboard' in name @@ -68,7 +68,9 @@ def _D(name, codename=None, kind=None, registers=None, settings=None): codename = name.split(' ')[-1] assert codename is not None - DEVICES[codename] = _DeviceDescriptor(name, kind, codename, registers, settings) + DEVICES[codename] = _DeviceDescriptor(name, kind, product_id, codename, registers, settings) + if product_id: + DEVICES[product_id] = DEVICES[codename] # # @@ -133,6 +135,13 @@ _D('Wireless Illuminated Keyboard K800', # Mice +# _D('VX Nano Cordless Laser Mouse', product_id='c526', codename='VXNano', +# registers={'battery_charge': 0x0D}, +# settings=[ +# _register_smooth_scroll(0x01, true_value=0x40, mask=0x40), +# ], +# ) + _D('Wireless Mouse M315') _D('Wireless Mouse M325') _D('Wireless Mouse M505') @@ -154,7 +163,7 @@ _D('Marathon Mouse M705', ) _D('Zone Touch Mouse T400') _D('Touch Mouse T620') -_D('Logitech Cube', kind='mouse') +_D('Logitech Cube', kind=_hidpp10.DEVICE_KIND.mouse) _D('Anywhere Mouse MX', codename='Anywhere MX', # registers={'battery_charge': 0x0D}, # settings=[ diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/lib/logitech/unifying_receiver/hidpp10.py index 6b5dba91..5dc99c62 100644 --- a/lib/logitech/unifying_receiver/hidpp10.py +++ b/lib/logitech/unifying_receiver/hidpp10.py @@ -153,7 +153,7 @@ def get_serial(device): def get_firmware(device): - firmware = [] + firmware = [None, None] reply = device.request(0x81F1, 0x01) if reply: @@ -163,23 +163,17 @@ def get_firmware(device): if reply: fw_version += '.B' + _strhex(reply[1:3]) fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None) - firmware.append(fw) - - if device.kind is None and device.max_devices == 1: - # Nano receiver - return firmware - if device.kind is not None and not device._unifying: - # Nano device - return firmware + firmware[0] = fw reply = device.request(0x81F1, 0x04) if reply: bl_version = _strhex(reply[1:3]) bl_version = '%s.%s' % (bl_version[0:2], bl_version[2:4]) bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, '', bl_version, None) - firmware.append(bl) + firmware[1] = bl - return tuple(firmware) + if any(firmware): + return tuple(f for f in firmware if f) def get_notification_flags(device): diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index b0e0ffa3..f2fbb23c 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -32,19 +32,40 @@ class PairedDevice(object): assert number > 0 and number <= receiver.max_devices self.number = number - self._unifying = receiver.max_devices > 1 - self._protocol = None if self._unifying else 1.0 - self._wpid = None - self._power_switch = None - self._polling_rate = None if self._unifying else 0 + self.wpid = None + self.polling_rate = 0 + + self._kind = None self._codename = None self._name = None - self._kind = None - self._serial = None if self._unifying else receiver.serial + + self._protocol = None if self.receiver.unifying_supported else 1.0 + self._power_switch = None if self.receiver.unifying_supported else '(unknown)' + self._serial = None if self.receiver.unifying_supported else self.receiver.serial + + if self.receiver.unifying_supported: + pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) + if pair_info: + self.wpid = _strhex(pair_info[3:5]) + kind = ord(pair_info[7:8]) & 0x0F + self._kind = _hidpp10.DEVICE_KIND[kind] + self.polling_rate = ord(pair_info[2:3]) + # else: + # # guesswork... + # descriptor = _descriptors.DEVICES.get(self.receiver.product_id) + # if descriptor: + # self._kind = descriptor.kind + # self._codename = descriptor.codename + # self._name = descriptor.name + + # device_info = self.receiver.request(0x83B5, 0x04) + # if device_info: + # self.wpid = _strhex(device_info[3:5]) + self._firmware = None self._keys = None - self.features = _hidpp20.FeaturesArray(self) if self._unifying else None + self.features = _hidpp20.FeaturesArray(self) if self.receiver.unifying_supported else None self._registers = None self._settings = None @@ -55,66 +76,36 @@ class PairedDevice(object): # _log.debug("device %d protocol %s", self.number, self._protocol) return self._protocol or 0 - @property - def wpid(self): - if self._wpid is None: - if self._unifying: - pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) - if pair_info: - self._wpid = _strhex(pair_info[3:5]) - if self._kind is None: - kind = ord(pair_info[7:8]) & 0x0F - self._kind = _hidpp10.DEVICE_KIND[kind] - if self._polling_rate is None: - self._polling_rate = ord(pair_info[2:3]) - else: - # guesswork... - device_info = self.receiver.request(0x83B5, 0x04) - self.wpid = _strhex(device_info[3:5]) - return self._wpid - - @property - def polling_rate(self): - if self._polling_rate is None: - if self._unifying: - self.wpid, 0 - else: - self._polling_rate = 0 - return self._polling_rate - @property def power_switch_location(self): if self._power_switch is None: - if self._unifying: - ps = self.receiver.request(0x83B5, 0x30 + self.number - 1) - if ps: - ps = ord(ps[9:10]) & 0x0F - self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps] + ps = self.receiver.request(0x83B5, 0x30 + self.number - 1) + if ps: + ps = ord(ps[9:10]) & 0x0F + self._power_switch = _hidpp10.POWER_SWITCH_LOCATION[ps] return self._power_switch @property def codename(self): if self._codename is None: - if self._unifying: - codename = self.receiver.request(0x83B5, 0x40 + self.number - 1) - if codename: - self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') - # _log.debug("device %d codename %s", self.number, self._codename) + codename = self.receiver.request(0x83B5, 0x40 + self.number - 1) + if codename: + self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') + # _log.debug("device %d codename %s", self.number, self._codename) return self._codename @property def name(self): if self._name is None: - if self._unifying: - if self.codename in _descriptors.DEVICES: - self._name, self._kind = _descriptors.DEVICES[self._codename][:2] - elif self.protocol >= 2.0: - self._name = _hidpp20.get_name(self) + if self.codename in _descriptors.DEVICES: + self._name, self._kind = _descriptors.DEVICES[self._codename][:2] + elif self.protocol >= 2.0: + self._name = _hidpp20.get_name(self) return self._name or self.codename or '?' @property def kind(self): - if self._kind is None and self._unifying: + if self._kind is None and self.receiver.unifying_supported: pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) if pair_info: kind = ord(pair_info[7:8]) & 0x0F @@ -146,7 +137,7 @@ class PairedDevice(object): @property def keys(self): if self._keys is None: - if self._unifying: + if self.protocol >= 2.0: self._keys = _hidpp20.get_keys(self) or () return self._keys @@ -201,7 +192,7 @@ class PairedDevice(object): return _base.request(self.receiver.handle, self.number, request_id, *params) def feature_request(self, feature, function=0x00, *params): - if self._unifying: + if self.protocol >= 2.0: return _hidpp20.feature_request(self, feature, function, *params) def ping(self): @@ -236,15 +227,18 @@ class Receiver(object): number = 0xFF kind = None - def __init__(self, handle, path=None): + def __init__(self, handle, device_info): assert handle self.handle = handle - assert path - self.path = path + assert device_info + self.path = device_info.path + self.product_id = device_info.product_id + # read the serial immediately, so we can find out max_devices + # this will tell us if it's a Unifying or Nano receiver serial_reply = self.request(0x83B5, 0x03) assert serial_reply - self._serial = _strhex(serial_reply[1:5]) + self.serial = _strhex(serial_reply[1:5]) self.max_devices = ord(serial_reply[6:7]) if self.max_devices == 1: @@ -255,6 +249,9 @@ class Receiver(object): raise Exception("unknown receiver type") self._str = '<%s(%s,%s%s)>' % (self.name.replace(' ', ''), self.path, '' if type(self.handle) == int else 'T', self.handle) + old_equad_reply = self.request(0x83B5, 0x04) + self.unifying_supported = old_equad_reply is None + self._firmware = None self._devices = {} @@ -266,13 +263,6 @@ class Receiver(object): def __del__(self): self.close() - @property - def serial(self): - assert self._serial - # if self._serial is None and self.handle: - # self._serial = _hidpp10.get_serial(self) - return self._serial - @property def firmware(self): if self._firmware is None and self.handle: @@ -308,21 +298,13 @@ class Receiver(object): def register_new_device(self, number): if self._devices.get(number) is not None: raise IndexError("%s: device number %d already registered" % (self, number)) + dev = PairedDevice(self, number) - # create a device object, but only use it if the receiver knows about it - - # Nano receiver - if self.max_devices == 1 and number == 1: - # the Nano receiver does not provide the wpid - _log.info("%s: found Nano device %d (%s)", self, number, dev.serial) - # dev._wpid = self.serial + ':1' - self._devices[number] = dev - return dev - if dev.wpid: - _log.info("%s: found Unifying device %d (%s)", self, number, dev.wpid) + _log.info("%s: found device %d (%s)", self, number, dev.wpid) self._devices[number] = dev return dev + self._devices[number] = None def set_lock(self, lock_closed=True, device=0, timeout=0): @@ -403,18 +385,18 @@ class Receiver(object): __bool__ = __nonzero__ = lambda self: self.handle is not None @classmethod - def open(self, path): + def open(self, device_info): """Opens a Logitech Receiver found attached to the machine, by Linux device path. :returns: An open file handle for the found receiver, or ``None``. """ try: - handle = _base.open_path(path) + handle = _base.open_path(device_info.path) if handle: - return Receiver(handle, path) + return Receiver(handle, device_info) except OSError as e: - _log.exception("open %s", path) + _log.exception("open %s", device_info) if e.errno == _errno.EACCES: raise except: - _log.exception("open %s", path) + _log.exception("open %s", device_info) diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index c2384ef9..87a221fc 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -273,6 +273,7 @@ class DeviceStatus(dict): _log.warn("%s: unrecognized %s", self._device, n) def _process_hidpp10_notification(self, n): + # unpair notification if n.sub_id == 0x40: if n.address == 0x02: # device un-paired @@ -283,44 +284,33 @@ class DeviceStatus(dict): _log.warn("%s: disconnection with unknown type %02X: %s", self._device, n.address, n) return True + # wireless link notification if n.sub_id == 0x41: - if n.address == 0x04: # unifying protocol - # wpid = _strhex(n.data[4:5] + n.data[3:4]) - # assert wpid == device.wpid - + protocol_name = ('unifying (eQuad DJ)' if n.address == 0x04 + else 'eQuad' if n.address == 0x03 + else None) + if protocol_name: flags = ord(n.data[:1]) & 0xF0 link_encrypyed = bool(flags & 0x20) link_established = not (flags & 0x40) if _log.isEnabledFor(_DEBUG): sw_present = bool(flags & 0x10) has_payload = bool(flags & 0x80) - _log.debug("%s: unifying connection notification: software=%s, encrypted=%s, link=%s, payload=%s", - self._device, sw_present, link_encrypyed, link_established, has_payload) - self[ENCRYPTED] = link_encrypyed - self._changed(link_established) - - elif n.address == 0x03: # eQuad protocol - # Nano devices might not have been initialized fully - if self._device._kind is None: - kind = ord(n.data[:1]) & 0x0F - self._device._kind = _hidpp10.DEVICE_KIND[kind] - if self._device._wpid is None: - self._device._wpid = _strhex(n.data[2:3] + n.data[1:2]) - - flags = ord(n.data[:1]) & 0xF0 - link_encrypyed = bool(flags & 0x20) - link_established = not (flags & 0x40) - if _log.isEnabledFor(_DEBUG): - sw_present = bool(flags & 0x10) - has_payload = bool(flags & 0x80) - _log.debug("%s: eQuad connection notification: software=%s, encrypted=%s, link=%s, payload=%s", - self._device, sw_present, link_encrypyed, link_established, has_payload) + _log.debug("%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", + self._device, protocol_name, sw_present, link_encrypyed, link_established, has_payload) self[ENCRYPTED] = link_encrypyed self._changed(link_established) + if protocol_name == 'eQuad': + # some Nano devices might not have been initialized fully + if self._device._kind is None: + kind = ord(n.data[:1]) & 0x0F + self._device._kind = _hidpp10.DEVICE_KIND[kind] + assert self._device.wpid == _strhex(n.data[2:3] + n.data[1:2]) else: _log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n) + # if the device just came online, read the battery charge if self._active: # and BATTERY_LEVEL not in self: self.read_battery() @@ -332,6 +322,7 @@ class DeviceStatus(dict): # if n.address == 0x03, it's an actual input event return True + # power notification if n.sub_id == 0x4B: if n.address == 0x01: if _log.isEnabledFor(_DEBUG): diff --git a/lib/solaar/cli.py b/lib/solaar/cli.py index 161257cf..42ec8c97 100644 --- a/lib/solaar/cli.py +++ b/lib/solaar/cli.py @@ -36,7 +36,7 @@ def _receiver(): from logitech.unifying_receiver.base import receivers for dev_info in receivers(): try: - r = Receiver.open(dev_info.path) + r = Receiver.open(dev_info) if r: return r except Exception as e: @@ -99,7 +99,7 @@ def _print_receiver(receiver, verbose=False): else: print (" All notifications disabled") - if paired_count > 0: + if receiver.unifying_supported: activity = receiver.request(0x83B3) if activity: activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)] diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py index 67f6d89a..6c85aa05 100644 --- a/lib/solaar/gtk.py +++ b/lib/solaar/gtk.py @@ -50,12 +50,12 @@ def _run(args): listeners = {} from solaar.listener import ReceiverListener - def handle_receivers_events(action, device): + def handle_receivers_events(action, device_info): assert action is not None - assert device is not None + assert device_info is not None # whatever the action, stop any previous receivers at this path - l = listeners.pop(device.path, None) + l = listeners.pop(device_info.path, None) if l is not None: assert isinstance(l, ReceiverListener) l.stop() @@ -63,18 +63,17 @@ def _run(args): if action == 'add': # a new receiver device was detected try: - l = ReceiverListener.open(device.path, status_changed) + l = ReceiverListener.open(device_info, status_changed) if l is not None: - listeners[device.path] = l + listeners[device_info.path] = l except OSError: # permission error, blacklist this path for now - listeners.pop(device.path, None) + listeners.pop(device_info.path, None) GLib.idle_add(ui.error_dialog, 'Permissions error', - 'Found a Logitech Unifying Receiver device,\n' - 'but did not have permission to open it.\n' + 'Found a Logitech Receiver, but did not have permission to open it.\n' '\n' - 'If you\'ve just installed Solaar, try removing\n' - 'the receiver and plugging it back in.') + 'If you\'ve just installed Solaar, try removing the receiver\n' + 'and plugging it back in.') # elif action == 'remove': # # we'll be receiving remove events for any hidraw devices, diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 5f217713..d7b67d22 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -161,12 +161,12 @@ class ReceiverListener(_listener.EventsListener): __unicode__ = __str__ @classmethod - def open(self, path, status_changed_callback): + def open(self, device_info, status_changed_callback): assert status_changed_callback - receiver = Receiver.open(path) + receiver = Receiver.open(device_info) if receiver: rl = ReceiverListener(receiver, status_changed_callback) rl.start() return rl else: - _log.warn("failed to open %s", path) + _log.warn("failed to open %s", device_info) diff --git a/lib/solaar/ui/main_window.py b/lib/solaar/ui/main_window.py index b79a5a4b..b1d9571a 100644 --- a/lib/solaar/ui/main_window.py +++ b/lib/solaar/ui/main_window.py @@ -70,16 +70,17 @@ def _make_receiver_box(receiver): device = f._device if True: # f._info_label.get_visible() and '\n' not in f._info_label.get_text(): items = [('Path', device.path), ('Serial', device.serial)] + \ - [(fw.kind, fw.version) for fw in device.firmware] + list((fw.kind, fw.version) for fw in device.firmware) + \ + [None] notification_flags = _hidpp10.get_notification_flags(device) if notification_flags: notification_flags = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) else: notification_flags = ('(none)',) - items.append(('Notifications', ('\n%16s' % ' ').join(notification_flags))) + items[-1] = ('Notifications', ('\n%16s' % ' ').join(notification_flags)) - f._info_label.set_markup('%s' % '\n'.join('%-14s: %s' % item for item in items)) + f._info_label.set_markup('%s' % '\n'.join('%-14s: %s' % i for i in items if i)) f._info_label.set_sensitive(True) def _toggle_info_label(action, f): @@ -94,7 +95,7 @@ def _make_receiver_box(receiver): toggle_info_action = _action.make_toggle('dialog-information', 'Details', _toggle_info_label, frame) toolbar.insert(toggle_info_action.create_tool_item(), 0) pair_action = _action.pair(frame) - if receiver.max_devices == 1: + if not receiver.unifying_supported: pair_action.set_sensitive(False) pair_action.set_tooltip('Pairing not supported by this receiver') toolbar.insert(pair_action.create_tool_item(), -1) @@ -186,16 +187,14 @@ def _make_device_box(index): device = f._device assert device - items = [None] * 8 hid = device.protocol - items[0] = ('Protocol', 'HID++ %1.1f' % hid if hid else 'unknown') - items[1] = ('Polling rate', '%d ms' % device.polling_rate) if device.polling_rate else None - items[2] = ('Wireless PID', device.wpid) - items[3] = ('Serial', device.serial) - firmware = device.firmware - if firmware: - firmware = [(fw.kind, (fw.name + ' ' + fw.version).strip()) for fw in firmware] - items[4:4+len(firmware)] = firmware + items = [ + ('Protocol', 'HID++ %1.1f' % hid if hid else 'unknown'), + ('Polling rate', '%d ms' % device.polling_rate) if device.polling_rate else None, + ('Wireless PID', device.wpid), + ('Serial', device.serial) ] + \ + list((fw.kind, (fw.name + ' ' + fw.version).strip()) for fw in device.firmware) + \ + [None] if device.status: notification_flags = _hidpp10.get_notification_flags(device) @@ -204,8 +203,6 @@ def _make_device_box(index): else: notification_flags = ('(none)',) items[-1] = ('Notifications', ('\n%16s' % ' ').join(notification_flags)) - else: - items[-1] = None frame._info_label.set_markup('%s' % '\n'.join('%-14s: %s' % i for i in items if i)) frame._info_label.set_sensitive(True) @@ -422,7 +419,7 @@ def _update_device_box(frame, dev): for i in frame._toolbar.get_children(): i.set_active(False) - if dev.receiver.max_devices == 1: + if not dev.receiver.unifying_supported: unpair_button = frame.get_child().get_children()[-1] unpair_button.set_sensitive(False) unpair_button.set_tooltip_text('Unpairing not supported by this device') diff --git a/rules.d/99-logitech-unifying-receiver.rules b/rules.d/99-logitech-unifying-receiver.rules index d7b40bc1..d0fdbb4c 100644 --- a/rules.d/99-logitech-unifying-receiver.rules +++ b/rules.d/99-logitech-unifying-receiver.rules @@ -8,8 +8,13 @@ # HIDAPI/hidraw for Logitech Unifying Receiver ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52b", GROUP="plugdev", MODE="0660" +ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c532", GROUP="plugdev", MODE="0660" -# HIDAPI/hidraw for Logitech Nano Receiver +# HIDAPI/hidraw for Logitech Nano Receiver, "Unifying ready" +ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52f", GROUP="plugdev", MODE="0660" + +# HIDAPI/hidraw for VX Nano receiver +#ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c51a", GROUP="plugdev", MODE="0660" #ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c526", GROUP="plugdev", MODE="0660" # vim: ft=udevrules