[Implementation] Report Rate Control On HID++ 2.0+ Devices
Created by: leogx9r
Information
- Solaar version: 1.0.4-64-gf68a831c
- Distribution: Arch Linux
- Kernel version: Linux 5.10.3-1 x86_64 GNU/Linux
- Output of
solaar show
for the target device (if applicable):
$ ➜ ./bin/solaar show
Lightspeed Receiver
Device path : /dev/hidraw4
USB id : 046d:C53F
Serial : 7E689DA8
Firmware : 44.01.B0005
Bootloader : 00.02
Other : AA.DE
Has 1 paired device(s) out of a maximum of 1.
Notifications: wireless, software present (0x000900)
Device activity counters: (empty)
1: G305 Lightspeed Wireless Gaming Mouse
Device path : None
WPID : 4074
Codename : G305
Kind : mouse
Protocol : HID++ 4.2
Polling rate : 8 ms (125Hz)
Serial number: 522871CF
Model ID: 407400000000
Unit ID: 39EAC711
Bootloader: BOT 69.01.B0014
Firmware: RQM 68.01.B0014
The power switch is located on the base.
Supports 27 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
Firmware: Bootloader BOT 69.01.B0014 407482F3F4D0
Firmware: Firmware RQM 68.01.B0014 407482F3F4D0
Unit ID: 39EAC711 Model ID: 407400000000 Transport IDs: {'wpid': '4074'}
3: DEVICE NAME {0005}
Name: G305 Lightspeed Wireless Gaming Mouse
Kind: mouse
4: WIRELESS DEVICE STATUS {1D4B}
5: BATTERY STATUS {1000}
Battery: 90%, discharging, next level 50%.
6: COLOR LED EFFECTS {8070}
7: ONBOARD PROFILES {8100}
Device Mode: Host
8: MOUSE BUTTON SPY {8110}
9: REPORT RATE {8060}
Polling Rate (ms): 1
10: MODE STATUS {8090}
11: DFUCONTROL SIGNED {00C2}
12: DEVICE RESET {1802} internal, hidden
13: unknown:1803 {1803} internal, hidden
14: CONFIG DEVICE PROPS {1806} internal, hidden
15: unknown:1811 {1811} internal, hidden
16: OOBSTATE {1805} internal, hidden
17: unknown:1830 {1830} internal, hidden
18: unknown:1890 {1890} internal, hidden
19: unknown:1DF3 {1DF3} internal, hidden
20: unknown:1E00 {1E00} hidden
21: unknown:1EB0 {1EB0} internal, hidden
22: unknown:1861 {1861} internal, hidden
23: unknown:18B1 {18B1} internal, hidden
24: unknown:1E22 {1E22} internal, hidden
25: unknown:1801 {1801} internal, hidden
26: ADJUSTABLE DPI {2201}
Sensitivity (DPI): 1600
Battery: 90%, discharging, next level 50%.
Greetings,
I'm already aware of the previous efforts to add and as such, control the report/polling rates for HID++ 2.0+ devices, which was removed due to the insufficient documentation available of changing the device to Host-mode which seems to be required for this particular feature.
I've spent a couple hours trying to understand the code base of this project and managed to get it working. The device mode for HID++ 2.0 and beyond can be retrieved or changed by calling 0x8100
, labeled as FEATURE.ONBOARD_PROFILES
with the function ID 0x20
and 0x10
respectively. Setting the polling rate requires the device to be set in Host mode before it can be adjusted, as such the solution is simply to check what mode the device is in, before changing the polling rate and switch modes if required.
The following patch is what I've managed to hack together in the brief time I spent with this code base. While it probably has mistakes, it's my hope that (while this isn't a pull request due to lacking time to properly respond to such engagements), someone can (properly) implement this feature. This was tested with my Logitech G305, which by default, uses a 1000 Hz polling rate when running the device in onboard mode. I've managed to change the polling rates using the following patch to all supported values, 125 Hz, 250 Hz, 500 Hz and 1000 Hz. The code itself should work on all HID++ 2.0+ devices since it is based off the work here as well as the previous code from pull requests like https://github.com/pwr-Solaar/Solaar/pull/840, https://github.com/pwr-Solaar/Solaar/pull/824, with relevant issues being https://github.com/pwr-Solaar/Solaar/issues/850, https://github.com/pwr-Solaar/Solaar/issues/792
diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py
index dd25d97..d42337e 100644
--- a/lib/logitech_receiver/hidpp20.py
+++ b/lib/logitech_receiver/hidpp20.py
@@ -183,6 +183,12 @@ BATTERY_STATUS = _NamedInts(
thermal_error=0x06
)
+ONBOARD_MODES = _NamedInts(
+ MODE_NO_CHANGE=0x00,
+ MODE_ONBOARD=0x01,
+ MODE_HOST=0x02
+)
+
CHARGE_STATUS = _NamedInts(charging=0x00, full=0x01, not_charging=0x02, error=0x07)
CHARGE_LEVEL = _NamedInts(average=50, full=90, critical=5)
@@ -1331,6 +1337,19 @@ def set_host_name(device, name):
return response
+def get_onboard_mode(device):
+ state = feature_request(device, FEATURE.ONBOARD_PROFILES, 0x20)
+
+ if state:
+ mode = _unpack('!B', state[:1])[0]
+ return mode
+
+
+def set_onboard_mode(device, mode):
+ state = feature_request(device, FEATURE.ONBOARD_PROFILES, 0x10, mode)
+ return state
+
+
def get_polling_rate(device):
state = feature_request(device, FEATURE.REPORT_RATE, 0x10)
if state:
diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py
index 52790dd..99b6862 100644
--- a/lib/logitech_receiver/settings_templates.py
+++ b/lib/logitech_receiver/settings_templates.py
@@ -81,6 +81,7 @@ _HIRES_INV = ('hires-smooth-invert', _('Scroll Wheel Direction'),
_('Invert direction for vertical scroll with wheel.'))
_HIRES_RES = ('hires-smooth-resolution', _('Scroll Wheel Resolution'),
_('High-sensitivity mode for vertical scroll with the wheel.'))
+_REPORT_RATE = ('report_rate', _("Polling Rate (ms)"), _("Frequency of device polling, in milliseconds"))
_FN_SWAP = ('fn-swap', _('Swap Fx function'),
_('When set, the F1..F12 keys will activate their special function,\n'
'and you must hold the FN key to activate their standard function.') + '\n\n' +
@@ -527,6 +528,28 @@ def _feature_adjustable_dpi():
return _Setting(_DPI, rw, callback=_feature_adjustable_dpi_callback, device_kind=(_DK.mouse, _DK.trackball))
+# Implemented based on code in libratrag
+def _feature_report_rate_callback(device):
+ # Host mode is required for report rate to be adjustable
+ if _hidpp20.get_onboard_mode(device) != _hidpp20.ONBOARD_MODES.MODE_HOST:
+ _hidpp20.set_onboard_mode(device, _hidpp20.ONBOARD_MODES.MODE_HOST)
+
+ reply = device.feature_request(_F.REPORT_RATE, 0x00)
+ assert reply, 'Oops, report rate choices cannot be retrieved!'
+ rate_list = []
+ rate_flags = _bytes2int(reply[0:1])
+ for i in range(0,8):
+ if (rate_flags >> i) & 0x01:
+ rate_list.append(i+1)
+ return _ChoicesV(_NamedInts.list(rate_list), byte_count=1) if rate_list else None
+
+
+def _feature_report_rate():
+ """Report Rate feature"""
+ rw = _FeatureRW(_F.REPORT_RATE, read_fnid=0x10, write_fnid=0x20)
+ return _Setting(_REPORT_RATE, rw, callback=_feature_report_rate_callback, device_kind=(_DK.mouse, ))
+
+
def _feature_pointer_speed():
"""Pointer Speed feature"""
# min and max values taken from usb traces of Win software
@@ -756,6 +779,7 @@ _SETTINGS_TABLE = [
_S(_REPROGRAMMABLE_KEYS, _F.REPROG_CONTROLS_V4, _feature_reprogrammable_keys),
_S(_DIVERT_KEYS, _F.REPROG_CONTROLS_V4, _feature_divert_keys),
_S(_DISABLE_KEYS, _F.KEYBOARD_DISABLE_KEYS, _feature_disable_keyboard_keys),
+ _S(_REPORT_RATE, _F.REPORT_RATE, _feature_report_rate),
_S(_DIVERT_CROWN, _F.CROWN, _feature_divert_crown),
_S(_DIVERT_GKEYS, _F.GKEY, _feature_divert_gkeys),
_S(_PLATFORM, _F.MULTIPLATFORM, _feature_multiplatform),
diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py
index 7a97b36..585e5d7 100644
--- a/lib/solaar/cli/show.py
+++ b/lib/solaar/cli/show.py
@@ -212,8 +212,12 @@ def _print_device(dev, num=None):
if ids:
unitId, modelId, tid_map = ids
print(' Unit ID: %s Model ID: %s Transport IDs: %s' % (unitId, modelId, tid_map))
- elif feature == _hidpp20.FEATURE.REPORT_RATE:
- print(' Polling Rate (ms): %d' % _hidpp20.get_polling_rate(dev))
+ elif feature == _hidpp20.FEATURE.ONBOARD_PROFILES:
+ if _hidpp20.get_onboard_mode(dev) == _hidpp20.ONBOARD_MODES.MODE_HOST:
+ mode = 'Host'
+ else:
+ mode = 'On-Board'
+ print(' Device Mode: %s' % mode)
elif feature == _hidpp20.FEATURE.BATTERY_STATUS or feature == _hidpp20.FEATURE.BATTERY_VOLTAGE:
print('', end=' ')
_battery_line(dev)
This is based off the current HEAD on master branch, which at the time of writing is f68a831c. Additionally, the show
command was modified to show whether the device in question is in host mode or onboard mode.
Below are a couple screenshots of said patch working, measured using evhz
,
Respectfully, Leonardo.