incipient support for the Nano receiver

This commit is contained in:
Daniel Pavel 2013-04-28 14:27:16 +02:00
parent 1a9be279c6
commit 079ef8d800
7 changed files with 98 additions and 43 deletions

View File

@ -27,7 +27,7 @@ del logging
from .common import strhex from .common import strhex
from .base import NoReceiver, NoSuchDevice, DeviceUnreachable from .base import NoReceiver, NoSuchDevice, DeviceUnreachable
from .receiver import Receiver, PairedDevice, MAX_PAIRED_DEVICES from .receiver import Receiver, PairedDevice
from .hidpp20 import FeatureNotSupported, FeatureCallError from .hidpp20 import FeatureNotSupported, FeatureCallError
from . import listener from . import listener

View File

@ -58,19 +58,22 @@ class DeviceUnreachable(_KwException):
# #
# #
# vendor_id, product_id, interface number, driver
DEVICE_UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, 'logitech-djreceiver')
DEVICE_UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, 'logitech-djreceiver')
#DEVICE_NANO_RECEIVER = (0x046d, 0xc526, 1, 'generic-usb')
def receivers(): def receivers():
"""List all the Linux devices exposed by the UR attached to the machine.""" """List all the Linux devices exposed by the UR attached to the machine."""
# (Vendor ID, Product ID) = ('Logitech', 'Unifying Receiver') for d in _hid.enumerate(*DEVICE_UNIFYING_RECEIVER):
# interface 2 if the actual receiver interface 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(0x046d, 0xc52b, 2):
if d.driver == 'logitech-djreceiver':
yield d
# apparently there are TWO product ids possible for the UR?
for d in _hid.enumerate(0x046d, 0xc532, 2):
if d.driver == 'logitech-djreceiver':
yield d
def open_path(path): def open_path(path):

View File

@ -133,6 +133,13 @@ def get_firmware(device):
fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None) fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None)
firmware.append(fw) 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
reply = device.request(0x81F1, 0x04) reply = device.request(0x81F1, 0x04)
if reply: if reply:
bl_version = _strhex(reply[1:3]) bl_version = _strhex(reply[1:3])

View File

@ -29,9 +29,10 @@ class PairedDevice(object):
def __init__(self, receiver, number): def __init__(self, receiver, number):
assert receiver assert receiver
self.receiver = _proxy(receiver) self.receiver = _proxy(receiver)
assert number > 0 and number <= MAX_PAIRED_DEVICES assert number > 0 and number <= receiver.max_devices
self.number = number self.number = number
self._unifying = receiver.max_devices > 1
self._protocol = None self._protocol = None
self._wpid = None self._wpid = None
self._power_switch = None self._power_switch = None
@ -43,7 +44,7 @@ class PairedDevice(object):
self._firmware = None self._firmware = None
self._keys = None self._keys = None
self.features = _hidpp20.FeaturesArray(self) self.features = _hidpp20.FeaturesArray(self) if self._unifying else None
self._registers = None self._registers = None
self._settings = None self._settings = None
@ -57,25 +58,29 @@ class PairedDevice(object):
@property @property
def wpid(self): def wpid(self):
if self._wpid is None: if self._wpid is None:
pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) if self._unifying:
if pair_info: pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1)
self._wpid = _strhex(pair_info[3:5]) if pair_info:
if self._kind is None: self._wpid = _strhex(pair_info[3:5])
kind = ord(pair_info[7:8]) & 0x0F if self._kind is None:
self._kind = _hidpp10.DEVICE_KIND[kind] kind = ord(pair_info[7:8]) & 0x0F
if self._polling_rate is None: self._kind = _hidpp10.DEVICE_KIND[kind]
self._polling_rate = ord(pair_info[2:3]) if self._polling_rate is None:
self._polling_rate = ord(pair_info[2:3])
# else:
# device_info = self.receiver.request(0x83B5, 0x04)
# self.wpid = _strhex(device_info[3:5])
return self._wpid return self._wpid
@property @property
def polling_rate(self): def polling_rate(self):
if self._polling_rate is None: if self._polling_rate is None and self._unifying:
self.wpid, 0 self.wpid, 0
return self._polling_rate return self._polling_rate
@property @property
def power_switch_location(self): def power_switch_location(self):
if self._power_switch is None: if self._power_switch is None and self._unifying:
ps = self.receiver.request(0x83B5, 0x30 + self.number - 1) ps = self.receiver.request(0x83B5, 0x30 + self.number - 1)
if ps: if ps:
ps = ord(ps[9:10]) & 0x0F ps = ord(ps[9:10]) & 0x0F
@ -84,7 +89,7 @@ class PairedDevice(object):
@property @property
def codename(self): def codename(self):
if self._codename is None: if self._codename is None and self._unifying:
codename = self.receiver.request(0x83B5, 0x40 + self.number - 1) codename = self.receiver.request(0x83B5, 0x40 + self.number - 1)
if codename: if codename:
self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') self._codename = codename[2:].rstrip(b'\x00').decode('utf-8')
@ -93,7 +98,7 @@ class PairedDevice(object):
@property @property
def name(self): def name(self):
if self._name is None: if self._name is None and self._unifying:
if self.codename in _descriptors.DEVICES: if self.codename in _descriptors.DEVICES:
self._name, self._kind = _descriptors.DEVICES[self._codename][:2] self._name, self._kind = _descriptors.DEVICES[self._codename][:2]
elif self.protocol >= 2.0: elif self.protocol >= 2.0:
@ -102,7 +107,7 @@ class PairedDevice(object):
@property @property
def kind(self): def kind(self):
if self._kind is None: if self._kind is None and self._unifying:
pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1)
if pair_info: if pair_info:
kind = ord(pair_info[7:8]) & 0x0F kind = ord(pair_info[7:8]) & 0x0F
@ -128,13 +133,13 @@ class PairedDevice(object):
@property @property
def serial(self): def serial(self):
if self._serial is None: if self._serial is None and self._unifying:
self._serial = _hidpp10.get_serial(self) self._serial = _hidpp10.get_serial(self)
return self._serial or '?' return self._serial or '?'
@property @property
def keys(self): def keys(self):
if self._keys is None: if self._keys is None and self._unifying:
self._keys = _hidpp20.get_keys(self) or () self._keys = _hidpp20.get_keys(self) or ()
return self._keys return self._keys
@ -165,7 +170,8 @@ class PairedDevice(object):
return _base.request(self.receiver.handle, self.number, request_id, *params) return _base.request(self.receiver.handle, self.number, request_id, *params)
def feature_request(self, feature, function=0x00, *params): def feature_request(self, feature, function=0x00, *params):
return _hidpp20.feature_request(self, feature, function, *params) if self._unifying:
return _hidpp20.feature_request(self, feature, function, *params)
def ping(self): def ping(self):
return _base.ping(self.receiver.handle, self.number) is not None return _base.ping(self.receiver.handle, self.number) is not None
@ -197,9 +203,7 @@ class Receiver(object):
The paired devices are available through the sequence interface. The paired devices are available through the sequence interface.
""" """
number = 0xFF number = 0xFF
name = 'Unifying Receiver'
kind = None kind = None
max_devices = MAX_PAIRED_DEVICES
def __init__(self, handle, path=None): def __init__(self, handle, path=None):
assert handle assert handle
@ -207,7 +211,19 @@ class Receiver(object):
assert path assert path
self.path = path self.path = path
self._serial = None serial_reply = self.request(0x83B5, 0x03)
assert serial_reply
self._serial = _strhex(serial_reply[1:5])
self.max_devices = ord(serial_reply[6:7][0])
if self.max_devices == 1:
self.name = 'Nano Receiver'
elif self.max_devices == 6:
self.name = 'Unifying Receiver'
else:
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)
self._firmware = None self._firmware = None
self._devices = {} self._devices = {}
@ -221,8 +237,9 @@ class Receiver(object):
@property @property
def serial(self): def serial(self):
if self._serial is None and self.handle: assert self._serial
self._serial = _hidpp10.get_serial(self) # if self._serial is None and self.handle:
# self._serial = _hidpp10.get_serial(self)
return self._serial return self._serial
@property @property
@ -260,6 +277,15 @@ class Receiver(object):
raise IndexError("device number %d already registered" % number) raise IndexError("device number %d already registered" % number)
dev = PairedDevice(self, number) dev = PairedDevice(self, number)
# create a device object, but only use it if the receiver knows about it # 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: if dev.wpid:
_log.info("found device %d (%s)", number, dev.wpid) _log.info("found device %d (%s)", number, dev.wpid)
self._devices[number] = dev self._devices[number] = dev
@ -283,7 +309,7 @@ class Receiver(object):
return _base.request(self.handle, 0xFF, request_id, *params) return _base.request(self.handle, 0xFF, request_id, *params)
def __iter__(self): def __iter__(self):
for number in range(1, 1 + MAX_PAIRED_DEVICES): for number in range(1, 1 + self.max_devices):
if number in self._devices: if number in self._devices:
dev = self._devices[number] dev = self._devices[number]
else: else:
@ -301,7 +327,7 @@ class Receiver(object):
if type(key) != int: if type(key) != int:
raise TypeError('key must be an integer') raise TypeError('key must be an integer')
if key < 1 or key > MAX_PAIRED_DEVICES: if key < 1 or key > self.max_devices:
raise IndexError(key) raise IndexError(key)
return self.register_new_device(key) return self.register_new_device(key)
@ -329,7 +355,7 @@ class Receiver(object):
return self.__contains__(dev.number) return self.__contains__(dev.number)
def __str__(self): def __str__(self):
return '<Receiver(%s,%s%s)>' % (self.path, '' if type(self.handle) == int else 'T', self.handle) return self._str
__unicode__ = __repr__ = __str__ __unicode__ = __repr__ = __str__
__bool__ = __nonzero__ = lambda self: self.handle is not None __bool__ = __nonzero__ = lambda self: self.handle is not None

View File

@ -12,7 +12,7 @@ from logging import getLogger, DEBUG as _DEBUG
_log = getLogger('LUR.status') _log = getLogger('LUR.status')
del getLogger del getLogger
from .common import NamedInts as _NamedInts from .common import NamedInts as _NamedInts, strhex as _strhex
from . import hidpp10 as _hidpp10 from . import hidpp10 as _hidpp10
from . import hidpp20 as _hidpp20 from . import hidpp20 as _hidpp20
@ -223,8 +223,24 @@ class DeviceStatus(dict):
self[ENCRYPTED] = link_encrypyed self[ENCRYPTED] = link_encrypyed
self._changed(link_established) self._changed(link_established)
elif n.address == 0x03: elif n.address == 0x03: # eQuad protocol
_log.warn("%s: connection notification with eQuad protocol, ignored: %s", self._device.number, n) # 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)
self[ENCRYPTED] = link_encrypyed
self._changed(link_established)
else: else:
_log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n) _log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n)

View File

@ -6,7 +6,10 @@
# Make sure the plugdev group exists on your system and your user is a member # Make sure the plugdev group exists on your system and your user is a member
# before applying these rules. # before applying these rules.
# HIDAPI/hidraw # 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}=="c52b", GROUP="plugdev", MODE="0660"
# HIDAPI/hidraw for Logitech Nano Receiver
#ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c526", GROUP="plugdev", MODE="0660"
# vim: ft=udevrules # vim: ft=udevrules

View File

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
if test -z "$1"; then if test -z "$1"; then
echo "Use: $0 <device number 1..6>" echo "Use: $0 <device number 1..6> [<receiver device>]"
exit 2 exit 2
fi fi
@ -14,10 +14,10 @@ for x in $z; do
for y in $z; do for y in $z; do
echo "10 0${1} 81${x}${y} 000000" echo "10 0${1} 81${x}${y} 000000"
done done
done | "$HC" --hidpp | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> ' done | "$HC" --hidpp $2 | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> '
for x in $z; do for x in $z; do
for y in $z; do for y in $z; do
echo "10 0${1} 83${x}${y} 000000" echo "10 0${1} 83${x}${y} 000000"
done done
done | "$HC" --hidpp | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> ' done | "$HC" --hidpp $2 | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> '