Skip to content

feat(hid,hidpp): read battery over BatteryVoltage (0x1001) - #575

Open
bobaoapae wants to merge 1 commit into
AprilNEA:masterfrom
bobaoapae:feat/battery-voltage
Open

feat(hid,hidpp): read battery over BatteryVoltage (0x1001)#575
bobaoapae wants to merge 1 commit into
AprilNEA:masterfrom
bobaoapae:feat/battery-voltage

Conversation

@bobaoapae

Copy link
Copy Markdown

Summary

Implements the BatteryVoltage feature (0x1001) so G-series wireless gaming devices get a battery reading. These devices (G915, G903 LS, G502 LIGHTSPEED) expose neither the legacy 0x1000 nor the unified 0x1004 — battery is reported only as a measured voltage via 0x1001, which until now was a name-only row in the feature registry. The probe found no battery feature and the device showed battery=— in list and no indicator in the GUI, even once it enumerated fine.

This is a read-only slice, mirroring the scope BatteryStatus (0x1000) keeps: getBatteryInfo (function 0) only, no broadcast event — the inventory's per-tick refresh already covers updates.

Changes

  • openlogi-hidpp / feature/battery_voltage — new typed wrapper for 0x1001: voltage as a big-endian millivolt u16 plus a charging-flags byte. The wire layout is not in a public Logitech spec; the decoding follows Solaar (decipher_battery_voltage) and libratbag's consensus and is marked as reverse-engineered, per the repo's hidpp rules. Flag decoding is total on purpose — a contradictory or future flag combination falls into the nearest charging bucket rather than failing, so a battery reading never vanishes over an unknown bit (the same reasoning as 0x1000's value-7 handling). Unit tests cover each decode path.
  • feature/registry.rs — bind the existing 0x1001 "BatteryVoltage" row to the new wrapper.
  • openlogi-hid / mappings.rsvoltage_battery_percentage: the feature reports millivolt, not percent, so the percentage is estimated with Solaar's measured Li-Po discharge curve (13 points, 3500–4186 mV), linearly interpolated and clamped at the ends; marked reverse-engineered. map_voltage_battery_status folds the charging states into our BatteryStatus exactly as the 0x1000/0x1004 mappings do (fast → charging, NotCharging fault → error). Tests: curve endpoints, exact curve points, interpolation, monotonicity across the full 3400–4300 mV range.
  • openlogi-hid / inventory/features.rsBatteryProbe::Voltage: found by the same feature-table walk, ranked after 0x1004 and 0x1000 since those report a real percentage and the voltage path only estimates one. The firmware's critical flag (bit 5) outranks the estimated level bucket. Tests cover the new ranking.

No IPC change: the reading lands in the existing BatteryInfo (percentage/level/status), so no new wire variants and no PROTOCOL_VERSION bump.

Testing

Verified on Windows 11 with a real G915 (wpid 0x407c) on its Lightspeed receiver:

# before
  └─ slot 1 ● G915 KEYBOARD (keyboard, wpid=407c, battery=—)

# after
  └─ slot 1 ● G915 KEYBOARD (keyboard, wpid=407c, battery=27% low (discharging))

The agent registers the same reading and the GUI shows the battery indicator. Note: the hardware run had #574's receiver recognition (046d:c547) applied locally, since a G915 is otherwise unreachable — this PR is receiver-agnostic and independent of it; on current master the immediate beneficiaries are 0x1001-only devices behind already-recognised receivers (e.g. a G903 LS on 046d:c539).

Local gate on this branch:

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

Notes

  • The estimate is a generic single-cell Li-Po curve, not per-device calibration — the same trade-off Solaar and libratbag ship with. Devices in the field sit on this curve within a few percent, which beats no reading at all.
  • Function 1 (getShowBatteryStatus) and the broadcast event are intentionally out of scope, as is any UI change — the existing battery indicator lights up through the standard BatteryInfo path.

Refs #512

G-series wireless gaming devices (G915, G903 LS) expose neither the
legacy 0x1000 BatteryStatus nor the unified 0x1004 UnifiedBattery:
battery is reported only as a measured voltage via 0x1001, which was a
name-only row in the feature registry. Their battery therefore showed
as "-" even once the device itself enumerated.

Implement the 0x1001 wrapper (getBatteryInfo: a big-endian millivolt
reading plus a charging-flags byte, decoded as Solaar and libratbag do),
register it, and let the inventory probe fall back to it when no
percentage-reporting battery feature is present. The displayed
percentage is estimated from the voltage with Solaar's measured Li-Po
discharge curve, interpolated linearly; the firmware's critical flag
outranks the estimated bucket.

Verified on real hardware with a G915 (wpid 0x407c): battery=27% low
(discharging).

Refs AprilNEA#512
@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown

Greptile Summary

Implements read-only HID++ BatteryVoltage (0x1001) support for devices that expose voltage rather than a direct battery percentage.

  • Adds and registers a typed 0x1001 feature wrapper with voltage, charging-state, and critical-flag decoding.
  • Converts millivolt readings to an estimated percentage using an interpolated Li-Po discharge curve.
  • Integrates voltage battery probing after unified and legacy percentage sources while refreshing readings through the existing inventory path.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issues identified.

The new feature is registered, uses the established HID++ endpoint framing and feature-index convention, preserves existing source precedence, and maps bounded voltage readings into the current battery contract.

Important Files Changed

Filename Overview
crates/openlogi-hid/src/inventory/features.rs Adds voltage-source discovery, source precedence, and conversion into the existing BatteryInfo refresh path without an identified defect.
crates/openlogi-hid/src/mappings.rs Adds bounded, monotonic voltage interpolation and charging-status mapping with focused curve tests.
crates/openlogi-hidpp/src/feature/battery_voltage/mod.rs Adds the typed 0x1001 request and response decoder, including tests for voltage, status precedence, charging rates, and the critical bit.
crates/openlogi-hidpp/src/feature/mod.rs Exposes the new battery_voltage feature module.
crates/openlogi-hidpp/src/feature/registry.rs Associates feature ID 0x1001 with the new typed producer so feature enumeration can instantiate it.

Sequence Diagram

sequenceDiagram
  participant Inventory
  participant FeatureTable
  participant Voltage as BatteryVoltage 0x1001
  participant Mapping
  participant Consumer as CLI / GUI

  Inventory->>FeatureTable: Enumerate battery feature IDs
  FeatureTable-->>Inventory: Runtime index for 0x1001
  Inventory->>Voltage: getBatteryInfo (function 0)
  Voltage-->>Inventory: voltage_mv, charging flags, critical
  Inventory->>Mapping: Estimate percentage and map status
  Mapping-->>Inventory: BatteryInfo
  Inventory-->>Consumer: Existing percentage, level, status shape
Loading

Reviews (1): Last reviewed commit: "feat(hid,hidpp): read battery over Batte..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant