Repository navigation
[Device Request]: Logitech G502 Lightspeed #21
Description
Device
Logitech G502 Lightspeed
How does the plugin process reach it?
USB HID (wired or dongle)
Battery protocol details, if you know any
Hey, first thanks for the nice project!
I just had some tries to make the G502LS works since I thought it was using the same protocol as either the G Pro X Wireless or the G Pro X Superlight 2.
The G502 LS relies also on Voltages instead of direct %, so I took a similar approach as the Headset.
From there 2 options are possible, we do the interpolation math for each device in their own class or we only implement them in the base class to avoid duplication (that's what I did in the files shared here, logic stays in LogitechHidppProtocol.cs). Your choice here ^^
I didn't want to make a pull request directly since it was not all straigt foward + we might need some tweaking for the ramping in the % curve as I only used few points as reference (Compared the results from the plugin with Logi GHub).
To make it work, I had to had 2 files and modify 6 existing ones, here are all the changes :
New:
src/DeviceBatteryInfo/Sources/Logitech/LogitechVoltageBatteryProtocol.cs >> the G502 protocol
tests/DeviceBatteryInfo.Tests/LogitechVoltageBatteryProtocolTests.cs >> its unit tests
Modifications :
src/DeviceBatteryInfo/Sources/Logitech/LogitechHidppProtocol.cs - add PercentFromMillivolts at the end of the class (before the closing }):
// after ThrowOnError, add:
internal static int PercentFromMillivolts(
int millivolts,
ReadOnlySpan<(int Millivolts, int Percent)> curve
)
{ /* the interpolation loop */ }
src/DeviceBatteryInfo/Sources/Logitech/LogitechHeadsetProtocol.cs - replace the 20-line PercentFromMillivolts method with a one-liner:
internal static int PercentFromMillivolts(int millivolts) =>
LogitechHidppProtocol.PercentFromMillivolts(millivolts, Curve);
tests/DeviceBatteryInfo.Tests/HardwareTests.cs - add one line to the Protocols array:
new LogitechVoltageBatteryProtocol(),
README.md - add one row to the "Specific devices" table:
| Logitech | G502 Lightspeed | Mouse | Receiver or cable | approx. | yes | Reports a voltage, so the percentage is an estimate.
packaging/linux/70-device-battery-info.rules - add two lines after the Superlight 2 entries:
# Logitech G502 Lightspeed
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c539", TAG+="uaccess"
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c08d", TAG+="uaccess"
docs/linux-setup.md - add the same three lines to the inline printf block, after the Superlight 2 lines.
Contribution
- I own this device and am willing to help test a backend for it.
- I'd like to implement this myself and am opening this issue to track it.
Activity
PyFlat-JR commentedon Oct 9, 2026
Hey, thanks a lot for this, and for the recorded frames! They made it much easier.
I've put your changes on the branch
feature/g502-lightspeed. Most of it is as you wrote it, including movingPercentFromMillivoltsintoLogitechHidppProtocol. I changed two things after comparing with the Linux kernel driver (hid-logitech-hidpp.c), Solaar and LGSTrayBattery:0x1001flags byte means critical level, not "charge complete". Bits 0–2 only mean something while bit 7 (external power) is set:0charging,1full,2not charging, anything else is an error. The status now follows that.0x1001table, at the points Solaar samples. Your reference point (4133 mV ≈ 97% in G Hub) falls exactly on that table, but in the middle range your curve and the kernel's differ by up to ~45% (3900 mV: ~22% vs ~67%). G Hub uses its own per-device table, so a gap of a few percent is still expected.Could you test the branch thoroughly on your G502? These are the things I'm most interested in:
C539) and on the cable (C08D)?dotnet test --filter Category=Hardwareintests/DeviceBatteryInfo.Testsshows the raw reads.If anything looks wrong, a log or the raw frames would help a lot. Thanks again!
Luckylucasv commentedon Oct 10, 2026
Hey,
Sure no worries, I'm a bit rusty so I figured adding some details might help :)
Receiver and Cable: Both are working,
Receiver (C539) - wireless - C539 - 96% Discharging - Pass
Cable (C08D) -wired - C08D - 100% Charging - Pass
GHub comparaison: I am currently trying to test at multiple ranges but it takes ages to drop down, for now on really higher percentages (90-100 in Ghub), I have around 2-4% difference (GHub shows lower values as the plugin). The weird this is that after playing a bit, the battery dropped to 72% while the plugin remains at 97%. I need to let my battery drain and test further to find out exactly how its behaving and maybe find out a better discharge curve.
Charging : When connecting the mouse via cable, it does show charging in the plugin and the % almost instantly jumps to 100, when switching back to dongle, the value goes back to normal. As we are reading voltages, this is Ok for now. I'm still trying to find out how GHub is available to estimate the charge when charging, my guess would be that they are using a charging curve in addition to the discharge one.
When I charge the mouse as it's turned off, "No signals" shows up (looks like a normal behavior as GHub looks also not able to communicate with it at all).
Sleep and Off : When unplugged completly >> The plugin shows No Signal and --- (greyed out). When the mouse is turned off, we see the old percentage greyed out with no signal after few seconds.
Both Connected: The mouse appears once and reads correctly. With both connected, the plugin probes both interfaces, the receiver doesn't answer (because the mouse is on the cable), so it falls through to the cable path (C08D) and reads from there 100% Charging.
In MD3: The variables and widget seems to work fine for now after 1-2hrs, no big gaps or jumps. I will give feedback when I'll reach the Low-Battery phase to see what shows up.
I think the only thing left to verify are the values for discharge with your table and compare with GHub.
I will try once fully discharged, to get multiple reference points to maybe build a better curve while also building in parralel a charging curve, seems to be the most efficient her :).
Then we will have the flexibility after either to keep your table and accept the small gaps or completly use an other one that will also have some gaps ^^
Luckylucasv commentedon Oct 10, 2026
Also instead of extrapolating from the Kernel values each 10%, I have made some tries with a complete 100value table to reduce the gap that I had in the mid range since it doesn't look completly linear. Again this is not really a clean solution as I had to extrapolate myself but it might help to solve some inconsistencies.
Before:
private static readonly (int Millivolts, int Percent)[] Curve = [ (4186, 100), (4067, 90), (3989, 80), (3922, 70), (3859, 60), (3811, 50), (3778, 40), (3751, 30), (3717, 20), (3671, 10), (3646, 5), (3579, 2), (3500, 0), ];After:
private static readonly int[] VoltageTable = [ 4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075, 4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997, 3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929, 3922, 3916, 3909, 3902, 3896, 3890, 3883, 3877, 3870, 3865, 3859, 3853, 3848, 3842, 3837, 3833, 3828, 3824, 3819, 3815, 3811, 3808, 3804, 3800, 3797, 3793, 3790, 3787, 3784, 3781, 3778, 3775, 3772, 3770, 3767, 3764, 3762, 3759, 3757, 3754, 3751, 3748, 3744, 3741, 3737, 3734, 3730, 3726, 3724, 3720, 3717, 3714, 3710, 3706, 3702, 3697, 3693, 3688, 3683, 3677, 3671, 3666, 3662, 3658, 3654, 3646, 3633, 3612, 3579, 3537, ];PyFlat-JR commentedon Oct 10, 2026
Thanks for the detailed test run, that's really helpful!
Full table: good call. I've switched to the kernel's full 100-entry table (your "After" table matches the kernel's
hidpp20_map_battery_capacityexactly), still interpolating linearly between entries. It now hits your G Hub reference point exactly (4133 mV → 97%). One caveat: the full table and the old 13 points differ by at most 2% anywhere between 3500 and 4200 mV, so it can't explain the 72% (G Hub) vs 97% (plugin) gap you saw. Both curves give about the same number there. So either G Hub isn't going by voltage alone, or the plugin was showing an old value at that moment.To find out, I added a hardware test that prints the raw voltage:
from
tests/DeviceBatteryInfo.Tests. It prints a line likeHH:mm:ss 4133 mV flags 0x00 = 97% Discharging. Close G Hub before running it, in case it holds the interface.What would help most while the battery drains:
Charging: the jump to ~100% while charging is because the voltage is higher on the cable. We'll leave it like this for now and decide once we have your data.
PyFlat-JR commentedon Oct 15, 2026
Quick note: I rebased
feature/g502-lightspeedonto the currentmain, so a plaingit pullwill fail. To update, rungit fetchand thengit reset --hard origin/feature/g502-lightspeed.PyFlat-JR commentedon Oct 16, 2026
Thanks again for the code and for all the testing! The G502 Lightspeed is merged in #30.
Two things changed since you last tested: the curve is now the kernel's full 100-entry table (your "After" table), and while the mouse charges the plugin now shows only "charging" without a percentage, because the voltage reads too high on the cable (the jump to ~100% you saw).
It will be in the next release, which should come soon. If you notice anything off until then, just tell me.
Luckylucasv commentedon Oct 16, 2026
Hey man,
Just received the Merge infos and wanted to give you a small update.
Btw I had a local build runing all that time with the complete 100 Table on my end so it's corresponding exactly to the merge that wou did.
I have been testing a bit in the last days and so far I could only notice a maximal gap between GHub and the plugin of 1-3%. So overall I would say that this is quite an acceptable range for me. (Even by playing all week I only dropped to 72% battery, so still far from the low battery status). Also I litteraly cannot explain the massive gap from last time and to be honnest, I would even say here that GHub was in the wrong here as I'm only reaching the 72% mark today on both the plugin and GHub!
Let's say this closes the whole integration of it, if I find anything else in the meantime, I'll just write you up so that we can figure it out.
Thanks again, was a pleasure, and have a good one !
PyFlat-JR commentedon Oct 16, 2026
Great, thanks for testing it over the whole week and for the code in the first place! If anything comes up, just write here or open a new issue.