receiver: add support for EX100 keyboard/mouse and receiver (046d:c517)

docs: add EX100 keyboard/mouse combo
device: add EX100 keyboard and mouse
hidpp10: fix notification parsing, add device features flags, fix documentation links
notifications: fix wpid processing for 27 MHz protocol
cli: add display of device features flags for HID++ 1.0 devices
This commit is contained in:
Alex Cherkayev 2020-07-19 02:27:52 +03:00 committed by Peter F. Patel-Schneider
parent 4dfa55c96c
commit e436b1bd1d
8 changed files with 240 additions and 6 deletions

View File

@ -49,7 +49,7 @@ a tuple of known feature settings (from lib/logitech/settings_templates.py).
| USB ID | Kind | Max Paired Devices |
------------|------------|--------------------|
| 046d:c517 | Nano | 1 |
| 046d:c517 | 27Mhz old | 2-4? |
| 046d:c518 | Nano | 1 |
| 046d:c51a | Nano | 1 |
| 046d:c51b | Nano | 1 |
@ -68,7 +68,8 @@ a tuple of known feature settings (from lib/logitech/settings_templates.py).
| 064d:c53f | Lightspeed | 1 |
| 17ef:6042 | Nano | 1 |
* The receiver with usb Id 046d:c517 is old, 27 MHz receiver, supporting only
subset of HID++ 1.0 protocol. Only hardware pairing supported.
### Keyboards (Unifying):
@ -215,7 +216,10 @@ setting is useful only to disable smooth scrolling.
| MK550 | | | | |
| MK700 | 2008 | 1.0 | yes | FN swap, reprog keys |
| MK710 | | 1.0 | yes | FN swap, reprog keys |
| EX100 keyboard | 6500 | 1.0 | yes | |
| EX100 mouse | 3f00 | 1.0 | yes | |
* The EX100 is old, pre-unifying set, supporting only part of HID++ 1.0 features
[solaar]: https://github.com/pwr-Solaar/Solaar
[logitech]: https://www.logitech.com

120
docs/devices/ex100.txt Normal file
View File

@ -0,0 +1,120 @@
./scan-registers.sh ff /dev/hidraw4
# Old notification flags: 000100
>> ( 0.035) [10 FF 8100 000100] '\x10\xff\x81\x00\x00\x01\x00'
<< ( 0.015) [10 FF 8101 000000] '\x10\xff\x81\x01\x00\x00\x00'
>> ( 0.020) [10 FF 8101 000200] '\x10\xff\x81\x01\x00\x02\x00'
<< ( 0.030) [10 FF 8102 000000] '\x10\xff\x81\x02\x00\x00\x00'
>> ( 0.036) [10 FF 8102 000200] '\x10\xff\x81\x02\x00\x02\x00'
--
<< ( 0.142) [10 FF 8109 000000] '\x10\xff\x81\t\x00\x00\x00'
>> ( 0.148) [10 FF 8109 010000] '\x10\xff\x81\t\x01\x00\x00'
--
<< ( 1.790) [10 FF 8170 000000] '\x10\xff\x81p\x00\x00\x00'
>> ( 1.796) [10 FF 8170 012100] '\x10\xff\x81p\x01!\x00'
<< ( 1.806) [10 FF 8171 000000] '\x10\xff\x81q\x00\x00\x00'
>> ( 1.812) [10 FF 8171 011200] '\x10\xff\x81q\x01\x12\x00'
--
<< ( 1.838) [10 FF 8173 000000] '\x10\xff\x81s\x00\x00\x00'
>> ( 1.844) [10 FF 8173 643F00] '\x10\xff\x81sd?\x00'
--
<< ( 2.046) [10 FF 8180 000000] '\x10\xff\x81\x80\x00\x00\x00'
>> ( 2.052) [10 FF 8180 030000] '\x10\xff\x81\x80\x03\x00\x00'
--
<< ( 3.326) [10 FF 81D0 000000] '\x10\xff\x81\xd0\x00\x00\x00'
>> ( 3.332) [10 FF 81D0 000000] '\x10\xff\x81\xd0\x00\x00\x00'
devices
01 mouse
Red button pressed
>> ( 1676.106) [10 01 0810 000000] '\x10\x01\x08\x10\x00\x00\x00'
>> ( 1676.114) [10 01 4600 000021] '\x10\x01F\x00\x00\x00!'
>> ( 1676.122) [10 FF 4600 211100] '\x10\xffF\x00!\x11\x00'
Power lewel?
?? Input: 10 01 81 07 00 00 00
<< ( 1739.032) [10 01 8107 000000] '\x10\x01\x81\x07\x00\x00\x00'
>> ( 1739.040) [10 01 8107 030000] '\x10\x01\x81\x07\x03\x00\x00'
[10 01 8107 070000] '\x10\x01\x81\x07\x07\x00\x00'
power change
>> ( 2441.563) [10 01 0703 000000] '\x10\x01\x07\x03\x00\x00\x00'
>> ( 100.159) [10 01 0707 000000] '\x10\x01\x07\x07\x00\x00\x00'
enable power event
<< ( 59.190) [10 01 8000 100000] '\x10\x01\x80\x00\x10\x00\x00'
>> ( 59.193) [10 01 8000 000000] '\x10\x01\x80\x00\x00\x00\x00'
03 keyboard
Power level?
?? Input: 10 03 81 07 00 00 00
<< ( 1777.961) [10 03 8107 000000] '\x10\x03\x81\x07\x00\x00\x00'
>> ( 1777.967) [10 03 8107 070000] '\x10\x03\x81\x07\x07\x00\x00'
power on
>> ( 1571.805) [10 03 0810 000000] '\x10\x03\x08\x10\x00\x00\x00'
>> ( 1574.709) [10 03 0800 000000] '\x10\x03\x08\x00\x00\x00\x00'
red button pressed
>> ( 1619.043) [10 03 0810 000000] '\x10\x03\x08\x10\x00\x00\x00'
>> ( 1619.051) [10 03 4600 000011] '\x10\x03F\x00\x00\x00\x11'
>> ( 1619.059) [10 FF 4600 221100] '\x10\xffF\x00"\x11\x00'
>> ( 1621.747) [10 03 0800 000000] '\x10\x03\x08\x00\x00\x00\x00'
Fn pressed
>> ( 1651.715) [10 03 032C 100000] '\x10\x03\x03,\x10\x00\x00'
>> ( 1652.170) [10 03 0300 000000] '\x10\x03\x03\x00\x00\x00\x00'
$ bin/solaar probe
Nano Receiver
Device path : /dev/hidraw3
USB id : 046d:c517
Serial : None
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless (0x000100)
Register Dump
Notification Register 0x00: 0x000100
Connection State 0x02: 0x000200
Device Activity 0xb3: None
Pairing Register 0xb5 0x00: None
Pairing Register 0xb5 0x10: None
Pairing Register 0xb5 0x20: None
Pairing Register 0xb5 0x30: None
Pairing Name 0xb5 0x40: None
Pairing Register 0xb5 0x01: None
Pairing Register 0xb5 0x11: None
Pairing Register 0xb5 0x21: None
Pairing Register 0xb5 0x31: None
Pairing Name 0xb5 0x41: None
Pairing Register 0xb5 0x02: None
Pairing Register 0xb5 0x12: None
Pairing Register 0xb5 0x22: None
Pairing Register 0xb5 0x32: None
Pairing Name 0xb5 0x42: None
Pairing Register 0xb5 0x03: None
Pairing Register 0xb5 0x13: None
Pairing Register 0xb5 0x23: None
Pairing Register 0xb5 0x33: None
Pairing Name 0xb5 0x43: None
Pairing Register 0xb5 0x04: None
Pairing Register 0xb5 0x14: None
Pairing Register 0xb5 0x24: None
Pairing Register 0xb5 0x34: None
Pairing Name 0xb5 0x44: None
Pairing Register 0xb5 0x05: None
Pairing Register 0xb5 0x15: None
Pairing Register 0xb5 0x25: None
Pairing Register 0xb5 0x35: None
Pairing Name 0xb5 0x45: None
Firmware 0xf1 0x00: None
Firmware 0xf1 0x01: None
Firmware 0xf1 0x02: None
Firmware 0xf1 0x03: None
Firmware 0xf1 0x04: None
Battery status:
1.9V critical
2.3V low
2.5V full

View File

@ -27,6 +27,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
## should this last be changed so that may_unpair is used for all receivers? writing to _R.receiver_pairing doesn't seem right
# re_pairs determines whether a receiver pairs by replacing existing pairings, default to False
## currently only one receiver is so marked - should there be more?
# ex100_wpid_fix enable workarounds for EX100 and possible other old 27Mhz receivers
_DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver')
@ -86,6 +87,18 @@ _lightspeed_receiver = lambda product_id: {
'name': 'Lightspeed Receiver'
}
_ex100_receiver = lambda product_id: {
'vendor_id': 0x046d,
'product_id': product_id,
'usb_interface': 1,
'hid_driver': _DRIVER, # noqa: F821
'name': 'EX100 Receiver 27 Mhz',
'max_devices': 4,
'may_unpair': False,
're_pairs': True,
'ex100_wpid_fix': True
}
# standard Unifying receivers (marked with the orange Unifying logo)
UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b)
UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532)
@ -93,8 +106,10 @@ UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532)
# Nano receviers that support the Unifying protocol
NANO_RECEIVER_ADVANCED = _nano_receiver(0xc52f)
# ex100 old style receiver pre-unifyimg protocol
EX100_27MHZ_RECEIVER_C517 = _ex100_receiver(0xc517)
# Nano receivers that don't support the Unifying protocol
NANO_RECEIVER_C517 = _nano_receiver_maxn(0xc517, 6)
NANO_RECEIVER_C518 = _nano_receiver(0xc518)
NANO_RECEIVER_C51A = _nano_receiver(0xc51a)
NANO_RECEIVER_C51B = _nano_receiver(0xc51b)
@ -119,7 +134,7 @@ ALL = (
UNIFYING_RECEIVER_C52B,
UNIFYING_RECEIVER_C532,
NANO_RECEIVER_ADVANCED,
NANO_RECEIVER_C517,
EX100_27MHZ_RECEIVER_C517,
NANO_RECEIVER_C518,
NANO_RECEIVER_C51A,
NANO_RECEIVER_C51B,

View File

@ -293,6 +293,13 @@ _D(
wpid='3622',
registers=(_R.battery_status, ),
)
_D(
'Wireless Keyboard EX100',
codename='EX100',
protocol=1.0,
wpid='6500',
registers=(_R.battery_status, ),
)
# Mice
@ -523,6 +530,14 @@ _D(
wpid='6822',
registers=(_R.battery_status, ),
)
_D(
'Wireless Mouse EX100',
codename='EX100m',
protocol=1.0,
wpid='3F00',
registers=(_R.battery_status, ),
# settings=[ _RS.smooth_scroll(), ], # command accepted, but no change in whell action
)
# Trackballs

View File

@ -64,14 +64,26 @@ POWER_SWITCH_LOCATION = _NamedInts(
# - the rest work only on devices as far as we can tell right now
# In the future would be useful to have separate enums for receiver and device notification flags,
# but right now we don't know enough.
# additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing
NOTIFICATION_FLAG = _NamedInts(
numpad_numerical_keys=0x800000,
f_lock_status=0x400000,
roller_H=0x200000,
battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D)
mouse_extra_buttons=0x080000,
roller_V=0x040000,
keyboard_sleep_raw=0x020000, # system control keys such as Sleep
keyboard_multimedia_raw=0x010000, # consumer controls such as Mute and Calculator
# reserved_r1b4= 0x001000, # unknown, seen on a unifying receiver
reserved5=0x008000,
reserved4=0x004000,
reserved3=0x002000,
reserved2=0x001000,
software_present=0x000800, # .. no idea
reserved1=0x000400,
keyboard_illumination=0x000200, # illumination brightness level changes (by pressing keys)
wireless=0x000100, # notify when the device wireless goes on/off-line
mx_air_3d_gesture=0x000001,
)
ERROR = _NamedInts(
@ -116,6 +128,25 @@ REGISTERS = _NamedInts(
notifications=0x00,
firmware=0xF1,
)
# Flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing
DEVICE_FEATURES = _NamedInts(
reserved1=0x010000,
special_buttons=0x020000,
enhanced_key_usage=0x040000,
fast_fw_rev=0x080000,
reserved2=0x100000,
reserved3=0x200000,
scroll_accel=0x400000,
buttons_control_resolution=0x800000,
inhibit_lock_key_sound=0x000001,
reserved4=0x000002,
mx_air_3d_engine=0x000004,
host_control_leds=0x000008,
reserved5=0x000010,
reserved6=0x000020,
reserved7=0x000040,
reserved8=0x000080,
)
#
# functions
@ -316,3 +347,19 @@ def set_notification_flags(device, *flag_bits):
assert flag_bits & 0x00FFFFFF == flag_bits
result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3))
return result is not None
def get_device_features(device):
assert device
# Avoid a call if the device is not online,
# or the device does not support registers.
if device.kind is not None:
# peripherals with protocol >= 2.0 don't support registers
if device.protocol and device.protocol >= 2.0:
return
flags = read_register(device, REGISTERS.mouse_button_flags)
if flags is not None:
assert len(flags) == 3
return _bytes2int(flags)

View File

@ -160,8 +160,8 @@ def _process_hidpp10_custom_notification(device, status, n):
# message layout: 10 ix <register> <xx> <yy> <zz> <00>
assert n.data[-1:] == b'\x00'
data = chr(n.address).encode() + n.data
charge, status_text = _hidpp10.parse_battery_status(n.sub_id, data)
status.set_battery_info(charge, status_text, None)
charge, status_text, next_charge = _hidpp10.parse_battery_status(n.sub_id, data)
status.set_battery_info(charge, status_text, next_charge)
return True
if n.sub_id == _R.keyboard_illumination:
@ -201,6 +201,9 @@ def _process_hidpp10_notification(device, status, n):
if protocol_name:
if _log.isEnabledFor(_DEBUG):
wpid = _strhex(n.data[2:3] + n.data[1:2])
# workaround for short EX100 wpids
if protocol_name == '27 MHz':
wpid = _strhex(n.data[2:3]) + '00'
assert wpid == device.wpid, '%s wpid mismatch, got %s' % (device, wpid)
flags = ord(n.data[:1]) & 0xF0

View File

@ -88,6 +88,14 @@ class PairedDevice(object):
self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2])
# assert link_notification.address == (0x04 if unifying else 0x03)
kind = ord(link_notification.data[0:1]) & 0x0F
# fix EX100 wpid
if receiver.ex100_wpid_fix: # EX100 receiver
self.wpid = _strhex(link_notification.data[2:3]) + '00'
# workaround for EX100 switched kind
if self.wpid == '3F00':
kind = 2
if self.wpid == '6500':
kind = 1
self._kind = _hidpp10.DEVICE_KIND[kind]
else:
# force a reading of the wpid
@ -97,6 +105,20 @@ class PairedDevice(object):
self.wpid = _strhex(pair_info[3:5])
kind = ord(pair_info[7:8]) & 0x0F
self._kind = _hidpp10.DEVICE_KIND[kind]
elif receiver.ex100_wpid_fix:
# ex100 receiver, fill fake device_info with known wpid's
# accordingly to drivers/hid/hid-logitech-dj.c
# index 1 or 2 always mouse, index 3 always the keyboard,
# index 4 is used for an optional separate numpad
if number == 1: # mouse
self.wpid = '3F00'
self._kind = _hidpp10.DEVICE_KIND[2]
elif number == 3: # keyboard
self.wpid = '6500'
self._kind = _hidpp10.DEVICE_KIND[1]
else: # unknown device number on EX100
_log.error('failed to set fake EX100 wpid for device %d of %s', number, receiver)
raise _base.NoSuchDevice(number=number, receiver=receiver, error='Unknown EX100 device')
else:
# unifying protocol not supported, must be a Nano receiver
device_info = self.receiver.read_register(_R.receiver_info, 0x04)
@ -365,6 +387,7 @@ class Receiver(object):
self._str = '<%s(%s,%s%s)>' % (
self.name.replace(' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle
)
self.ex100_wpid_fix = product_info.get('ex100_wpid_fix', False)
self._firmware = None
self._devices = {}

View File

@ -96,6 +96,13 @@ def _print_device(dev):
print(' Notifications: %s (0x%06X).' % (', '.join(notification_names), notification_flags))
else:
print(' Notifications: (none).')
device_features = _hidpp10.get_device_features(dev)
if device_features is not None:
if device_features:
device_features_names = _hidpp10.DEVICE_FEATURES.flag_names(device_features)
print(' Features: %s (0x%06X)' % (', '.join(device_features_names), device_features))
else:
print(' Features: (none)')
if dev.online and dev.features:
print(' Supports %d HID++ 2.0 features:' % len(dev.features))