pwr-Solaar / Solaar

Linux device manager for Logitech devices
https://pwr-solaar.github.io/Solaar
GNU General Public License v2.0
5.55k stars 411 forks source link

Help needed to implement new mouse and keyboard #1305

Closed jocelynthode closed 3 years ago

jocelynthode commented 3 years ago

Hey guys,

I recently bought a Logitech Wireless Pro X Superlight and a G915 TKL keyboard. I can configure them just fine through Piper/ratbagd when they are plugged over usb.

However I'd like to get more information in my system tray icon such as the battery level. I found about this project and I saw that when using solaar I only see in the windows "Lightspeed Receiver" but I don't see any recognized keyboard or mouse under it.

After digging a bit in the code I tried adding in descriptors.py the following declaration:

_D(
    'PRO X Wireless',
    kind='mouse',
    codename='PRO X',
    protocol=4.2,
    usbid=0xc547,
    wpid='4093',
)

However, now when I launched my built copy of Solaar I get the following error warning and then the notification listener crashes:

21:56:41,644  WARNING [ReceiverListener:hidraw2] logitech_receiver.notifications: <Device(1,4093,PRO X Wireless,E7C3DFD4)>: unrecognized Notification(10,1,41,0F,329340)

by analyzing error I found out that there is a missing case for this kind of notification. Specifically the code trie sto use the _process_hidpp10_custom_notification and not a specially 2.0+ method. the adress 0x0F has also no case.

Could someone provide me some pointers on where to go from there so that I could try and implement support for these devices?

Cheers

pfps commented 3 years ago

This appears to be a connection notification (0x41). That should not be processed by the custom notification code but instead by _process_hidpp10_notification. These messages are mostly the same for all protocols. The warning message that should have been created is "connection notification with unknown protocol".

Try running with -dd and see if the debug messages help say what is going on. If that doesn't help, try putting in print statements in notifications.py to diagnose the control flow.

_process_hidpp10_notification should be updated to allow this protocol.

jocelynthode commented 3 years ago

Hey @pfps . Thanks for the tips I actually lost myself in the code. You were right and it was using the _process_hidpp10_notification method. I added a new protocol for 0x0F and now I'm stuck at the next issue.

I'll try and dig by myself in the following days and see if I get stuck again!

Thanks!

pfps commented 3 years ago

I fixed up _process_hidpp10_notification to better handle some corner cases and also all protocols (which are supposed to be mostly the same). Try PR #1306. You could base your changes on this PR.

To download and work with Solar

git clone https://github.com/pwr-Solaar/Solaar.git
cd Solaar

Run Solaar as bin/solaar from this directory.

To run PR #1306, first clone Solaar if you have not already done so and cd to the clone directory. The first time you download the pull request, fetch it into a new branch and checkout that branch, as in:

git fetch origin pull/1306/head:pull_1306
git checkout pull_1306

To download a new version of the pull request, fetch it and then set your pull branch to the new fetch, as in:

git checkout pull_1306
git fetch origin pull/1306/head
git reset --hard FETCH_HEAD
pfps commented 3 years ago

@jocelynthode What is the next issue you ran into?

jocelynthode commented 3 years ago

Hey @pfps,

sorry I was at work and unable to work further on this. I just started debugging again.

my next issue is now in the _notifications_handler listener method

The first few notifications being handled are working fine and I get the following logs:

DEVICE CONNNECTION NOTIFICATION Notification(10,1,41,0F,329340) <Device(1,4093,PRO X Wireless,E7C3DFD4)> {}
DEVICE CONNNECTION NOTIFICATION Notification(10,1,41,0F,318E40) <Device(1,408E,G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard,EC762B99)> {}

However afterwards I start getting weird notification with a 0x00 address: Notification(11,1,11,00,00000000000000000000000000000000)

This obviously make the code crash on the line further if not (0 < n.devnumber <= self.receiver.max_devices): since there are no max_device.

The receiver in these weird notifications are the mouse and keyboard (e.g.: <Device(255,C545,G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard,?)>) My guess is that the code should handle only notifications for the Lightspeed receiver but here the receiver is the Peripheral directly instead of its parent lightspeed receiver.

This might be due to the new code that catches a few too many Notifications?

pfps commented 3 years ago

What might be happening is that the notifications are being picked up by the device listener. These notifications are not going to cause any processing in this part of Solaar to the correct thing to do is to pass them through. But the code needs a bit of fixup to do the right thing.

pfps commented 3 years ago

OK, try PR #1306 now.

jocelynthode commented 3 years ago

Seems to have fixed the issue! on to the next one ;)

jocelynthode commented 3 years ago

I had to fix a few conditions. Now it seems as if the lightspeed receiver disconnect which logs the following:

21:47:01,074     INFO [ReceiverListener:hidraw2] solaar.listener: <LightspeedReceiver(/dev/hidraw2,12)>: notifications listener has stopped

From my understanding this seems to then trigger an update of the window making every lightspeed receiver entry disappear and thus deleting the mouse and keyboard.

I think I'm close to having it working as the status update for both the mouse and keyboards look good:

22:13:14,773     INFO [ReceiverListener:hidraw2] solaar.listener: status_changed <Device(1,4093,PRO X Wireless,E7C3DFD4)>: paired online, {'LINK ENCRYPTED': True, 'BATTERY LEVEL': 75, 'BATTERY STATUS': NamedInt(0, 'discharging'), 'BATTERY NEXT LEVEL': None, 'BATTERY VOLTAGE': None, 'BATTERY CHARGING': False, 'ERROR': None} (0) 
22:13:14,773     INFO [ReceiverListener:hidraw2] solaar.listener: status_changed <Device(1,4093,PRO X Wireless,E7C3DFD4)>: paired online, {'LINK ENCRYPTED': True, 'BATTERY LEVEL': 75, 'BATTERY STATUS': NamedInt(0, 'discharging'), 'BATTERY NEXT LEVEL': None, 'BATTERY VOLTAGE': None, 'BATTERY CHARGING': False, 'ERROR': None} (0) 

22:13:14,931     INFO [ReceiverListener:hidraw7] solaar.listener: status_changed <Device(1,408E,G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard,EC762B99)>: paired online, {'LINK ENCRYPTED': True, 'BATTERY LEVEL': NamedInt(50, 'average'), 'BATTERY STATUS': NamedInt(0, 'discharging'), 'BATTERY NEXT LEVEL': None, 'BATTERY VOLTAGE': 4015, 'BATTERY CHARGING': False, 'ERROR': None} (0) 
22:13:14,931     INFO [ReceiverListener:hidraw7] solaar.listener: status_changed <Device(1,408E,G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard,EC762B99)>: paired online, {'LINK ENCRYPTED': True, 'BATTERY LEVEL': NamedInt(50, 'average'), 'BATTERY STATUS': NamedInt(0, 'discharging'), 'BATTERY NEXT LEVEL': None, 'BATTERY VOLTAGE': 4015, 'BATTERY CHARGING': False, 'ERROR': None} (0) 

I feel like I'm missing a key understanding on why the lightspeed devices would switch to inactive.

pfps commented 3 years ago

Devices go into power-saving mode which disconnects them from the receiver but receivers shouldn't (unless the USB port goes into power saving mode). Perhaps you could post the output of solaar show when the devices are active to show how everything is connected.

There are a lot of moving parts here, which causes complexity in the code and in debugging. You could also post the output of solaar -dd to show the messages that are being sent and what Solaar is doing.

Also, having two devices active at the same time makes debugging much more difficult as Solaar is multi-threaded and the interactions with the devices can be interleaved.

jocelynthode commented 3 years ago

Unfortunately I don't have any other keyboard at the moment so I can't really remove it :/

Here is the output of my solaar show

Lightspeed Receiver
  Device path  : /dev/hidraw2
  USB id       : 046d:C547
  Serial       : XXX
    Firmware   : 04.00.B0005
    Bootloader : 00.05
    Other      : 84.70
  Has 1 paired device(s) out of a maximum of 2.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=13

  1: PRO X Wireless
     Device path  : None
     WPID         : 4093
     Codename     : PRO X
     Kind         : mouse
     Protocol     : HID++ 4.2
     Polling rate : 1 ms (1000Hz)
     Serial number: XXX
     Model ID:      XX
     Unit ID:       E7C3DFD4
        Bootloader: BL1 25.00.B0013
             Other: 
          Firmware: MPM 25.00.B0013
     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 BL1 25.00.B0013 AB00BE657A82
            Firmware: Other   
            Firmware: Firmware MPM 25.00.B0013 4093BE657A82
            Unit ID: E7C3DFD4  Model ID: 4093C0940000  Transport IDs: {'wpid': '4093', 'usbid': 'C094'}
         3: DEVICE NAME            {0005}   
            Name: PRO X Wireless
            Kind: mouse
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: UNIFIED BATTERY        {1004}   
         7: COLOR LED EFFECTS      {8070}   internal, hidden
         8: ONBOARD PROFILES       {8100}   
            Device Mode: Host
         9: MOUSE BUTTON SPY       {8110}   
        10: REPORT RATE            {8060}   
            Polling Rate (ms): 1
            Polling Rate (ms) (saved): 1
            Polling Rate (ms)        : 1
        11: ADJUSTABLE DPI         {2201}   
            Sensitivity (DPI) (saved): 1600
            Sensitivity (DPI)        : 1600
        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:1891           {1891}   internal, hidden
        20: unknown:18A1           {18A1}   internal, hidden
        21: unknown:1801           {1801}   internal, hidden
        22: unknown:18B1           {18B1}   internal, hidden
        23: unknown:1E00           {1E00}   hidden
        24: unknown:1EB0           {1EB0}   internal, hidden
        25: unknown:1863           {1863}   internal, hidden
        26: unknown:1E22           {1E22}   internal, hidden
     Battery: 74%, discharging.

Lightspeed Receiver
  Device path  : /dev/hidraw7
  USB id       : 046d:C545
  Serial       : XXXX
    Firmware   : 02.00.B0001
    Bootloader : 00.01
    Other      : CF.20
  Has 1 paired device(s) out of a maximum of 2.
  Notifications: wireless, software present (0x000900)
  Device activity counters: 1=105

  1: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard
     Device path  : None
     WPID         : 408E
     Codename     : G915 TKL
     Kind         : keyboard
     Protocol     : HID++ 4.2
     Polling rate : 1 ms (1000Hz)
     Serial number: EC762B99
     Model ID:      XXXX
     Unit ID:       XXX
        Bootloader: BL1 12.00.B0017
             Other: 
          Firmware: MPK 14.00.B0017
             Other: 
             Other: 
     The power switch is located on the top left corner.
     Supports 38 HID++ 2.0 features:
         0: ROOT                   {0000}   
         1: FEATURE SET            {0001}   
         2: DEVICE FW VERSION      {0003}   
            Firmware: Bootloader BL1 12.00.B0017 00008B79E978
            Firmware: Other   
            Firmware: Firmware MPK 14.00.B0017 408E8B79E978
            Firmware: Other   
            Firmware: Other   
            Unit ID: EC762B99  Model ID: B35F408EC343  Transport IDs: {'btleid': 'B35F', 'wpid': '408E', 'usbid': 'C343'}
         3: DEVICE NAME            {0005}   
            Name: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard
            Kind: keyboard
         4: WIRELESS DEVICE STATUS {1D4B}   
         5: RESET                  {0020}   
         6: DEVICE FRIENDLY NAME   {0007}   
            Friendly Name: G915 TKL
         7: BATTERY VOLTAGE        {1001}   
            Battery: 4005mV, discharging, average.
         8: CHANGE HOST            {1814}   
            Change Host        : 1:archfixe
         9: HOSTS INFO             {1815}   
            Host 0 (paired): archfixe
            Host 1 (unpaired): 
        10: RGB EFFECTS            {8071}   
        11: PER KEY LIGHTING V2    {8081}   
        12: REPROG CONTROLS V4     {1B04}   
        13: REPORT HID USAGE       {1BC0}   
        14: ENCRYPTION             {4100}   
        15: KEYBOARD DISABLE BY USAGE {4522}   
        16: KEYBOARD LAYOUT 2      {4540}   
        17: GKEY                   {8010}   
            Divert G Keys (saved): False
            Divert G Keys        : False
        18: MKEYS                  {8020}   
        19: MR                     {8030}   
        20: BRIGHTNESS CONTROL     {8040}   
        21: ONBOARD PROFILES       {8100}   
            Device Mode: On-Board
        22: REPORT RATE            {8060}   
            Polling Rate (ms): 1
        23: DFUCONTROL SIGNED      {00C2}   
        24: DFU                    {00D0}   
        25: DEVICE RESET           {1802}   internal, hidden
        26: unknown:1803           {1803}   internal, hidden
        27: CONFIG DEVICE PROPS    {1806}   internal, hidden
        28: unknown:1813           {1813}   internal, hidden
        29: OOBSTATE               {1805}   internal, hidden
        30: unknown:1830           {1830}   internal, hidden
        31: unknown:1890           {1890}   internal, hidden
        32: unknown:1891           {1891}   internal, hidden
        33: unknown:18A1           {18A1}   internal, hidden
        34: unknown:1E00           {1E00}   hidden
        35: unknown:1EB0           {1EB0}   internal, hidden
        36: unknown:1861           {1861}   internal, hidden
        37: unknown:18B0           {18B0}   internal, hidden
     Has 2 reprogrammable keys:
         0: Host Switch Channel 1     , default: HostSwitch Channel 1        => HostSwitch Channel 1      
             divertable, persistently divertable, pos:1, group:0, group mask:empty
             reporting: default
         1: Host Switch Channel 2     , default: HostSwitch Channel 2        => HostSwitch Channel 2      
             divertable, persistently divertable, pos:2, group:0, group mask:empty
             reporting: default
     Battery: 4005mV, discharging, average.

Wired Devices
  1: PRO X Wireless
     Device path  : /dev/hidraw2
     USB id       : 046d:C547
     Codename     : PRO X
     Kind         : mouse
     Protocol     : HID++ 1.0
     Serial number: ?
          Firmware: 04.00.B0005
        Bootloader: 00.05
             Other: 84.70
     Notifications: wireless, software present (0x000900).
     Features: (none)
     Battery status unavailable.
  2: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard
     Device path  : /dev/hidraw7
     USB id       : 046d:C545
     Codename     : G915 TKL
     Kind         : keyboard
     Protocol     : HID++ 1.0
     Serial number: ?
          Firmware: 02.00.B0001
        Bootloader: 00.01
             Other: CF.20
     Notifications: wireless, software present (0x000900).
     Features: (none)
     Battery status unavailable.

here is a gist of the debug logs: https://gist.github.com/jocelynthode/8fed3ad8a34ff93c854b6e992e4f49dd

pfps commented 3 years ago

Something is going wrong. There should not be two entries for your devices. Your devices are connected via a receiver and should not also be connected via a wired connection.

What kernel and distribution are you running?

pfps commented 3 years ago

I think the problem is that you have added incorrect descriptors for your devices. Devices that connect via a receiver don't have USB IDs so you should remove this information, which is only for devices that connect directly via a USB cable. As well, the USB ID you have is the ID for the receiver.

For example, the G502 gaming mouse can connect via a USB cable. Its descriptor is

_D('G502 Hero Gaming Mouse', codename='G502 Hero', usbid=0xc08d)

But this descriptor is only needed to give a slightly better name to the mouse. Solaar doesn't need it.

This mouse can also connect via a Lightspeed receiver, which Solaar does need to know about in base_usb.py as

LIGHTSPEED_RECEIVER_C539 = _lightspeed_receiver(0xc539)
jocelynthode commented 3 years ago

Ah that's exactly what my last issue was! I thought I had to add the usbid as the mouse can be connected via a USB cable and I saw this information in the solaar show.

Here's the result! Thanks for all the help you provided! I was a bit lost around the codebase and logitech's protocols :)

image

I'll test my changes a bit more and will make a PR this evening.

jocelynthode commented 3 years ago

Also I noticed that unfortunately, when the mouse is plugged I cannot see it anymore. If I wanted to also see my mouse/keyboard when they are connected to for example to monitor the battery charge? Or is this out of scope of the project?

pfps commented 3 years ago

The problem is likely that the mouse uses USB interface 1 when connected via a USB cable. Look for lines similar to

23:50:03,021    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000XXXX INTERFACE y FILTER 2

when the mouse is connected via a USB cable. If only interface 0 and 1 show up then you probably need a descriptor like

_D('PRO X Wireless Mouse', codename='PRO X', usbid=0xXXXX, wpid='4093', interface=1)
jocelynthode commented 3 years ago

Strange, the keyboard seems to appear by itself in the gui when cabled although I did not find any descriptor for it besides mine.

When the mouse and keyboard are cabled I see the following lines:

07:43:23,782    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000C094 INTERFACE 0 FILTER 1
07:43:23,783    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000C094 INTERFACE 1 FILTER 1
07:43:24,088    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000C094 INTERFACE 2 FILTER 1
07:43:24,088    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000C343 INTERFACE 0 FILTER 2
07:43:24,089    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000C343 INTERFACE 1 FILTER 2
07:43:24,089    DEBUG [MainThread] hidapi.udev: Found device BID 0003 VID 0000046D PID 0000C343 INTERFACE 2 FILTER 2

so the usbid will be 0xC094 and 0xC343.

However I'm not sure I understand how the descriptor would work if I added these usbid and how to choose between all 3 interfaces.

and would the interface specified only be used when connecting via usb? Otherwise how is Solaar differentiating when to use which interface?

pfps commented 3 years ago

When a device is directly connected (via a USB cable or via Bluetooth) it shows up by itself, i.e., not associated with a receiver. If the receiver is also connected the receiver will still show up. The devices paired with the receiver will also show up, even if they are not currently connected to the receiver, but then they are inactive. So a device can show up twice, but only one will be active.

The reason that the 0xC094 is not showing up is that it is a new product and its USB ID is out of the range that Solaar is looking for. To fix this change the 0xC093 to 0x094 in other_device_check in base_usb.py.

The different interfaces are only for USB connections. They are a peculiarity of USB - different interfaces process different messages. It is possible to figure out which interface does HID++ but it is complicated and Solaar just uses interface 2, which is correct for most devices, unless there is descriptor providing a different interface.

You probably don't need a descriptor for either of these devices.

jocelynthode commented 3 years ago

Everything is now working perfectly. Thanks for all the help and the various explanations :)