feat(battery): support legacy 0x1000 BatteryStatus and its charging quirk#312
feat(battery): support legacy 0x1000 BatteryStatus and its charging quirk#312laofun wants to merge 1 commit into
Conversation
Greptile SummaryThis PR adds support for the legacy
Confidence Score: 5/5Safe to merge — all four issues from the previous review round have been addressed, and the new code is well-scoped with good test coverage. The hold-on-zero logic is correctly gated to Legacy probes only. The loop-overflow path now uses break instead of ?-returning None. Value 7 is an explicit enum variant so TryFromPrimitive no longer silently drops it. The fallthrough error in read_battery_raw reports 0x1004 rather than the misleading 0x1000. Tests cover the four hold branches, the Unified passthrough, bucket boundaries, and the tray/card GUI suppression. The two-layer design (server-side hold + GUI cold-start guard) is consistent and the interaction between layers is correct. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[probe_or_reuse called] --> B{Cache stale or empty?}
B -- Yes --> C[probe_features: enumerate feature table]
C --> D{0x1004 in table?}
D -- Yes --> E[BatteryProbe::Unified index]
D -- No --> F{0x1000 in table?}
F -- Yes --> G[BatteryProbe::Legacy index]
F -- No --> H[battery = None]
E --> I[read_battery via 0x1004]
G --> J[read_battery via 0x1000]
I --> K[hold_percentage_while_charging]
J --> K
K --> L{probe == Legacy AND\npercentage==0 AND charging\nAND prev > 0?}
L -- Yes --> M[Substitute prev percentage+level\nkeep fresh status]
L -- No --> N[Return fresh BatteryInfo]
M --> O[Store in Cached]
N --> O
O --> P[GUI render]
B -- No, cache hit --> Q[read_battery only\ncheap 1 round-trip]
Q --> K
P --> R{battery_charging_no_reading?\ncharging AND percentage==0}
R -- Yes --> S[Show Charging without %\nleave progress bar empty]
R -- No --> T[Show percentage% and fill bar]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[probe_or_reuse called] --> B{Cache stale or empty?}
B -- Yes --> C[probe_features: enumerate feature table]
C --> D{0x1004 in table?}
D -- Yes --> E[BatteryProbe::Unified index]
D -- No --> F{0x1000 in table?}
F -- Yes --> G[BatteryProbe::Legacy index]
F -- No --> H[battery = None]
E --> I[read_battery via 0x1004]
G --> J[read_battery via 0x1000]
I --> K[hold_percentage_while_charging]
J --> K
K --> L{probe == Legacy AND\npercentage==0 AND charging\nAND prev > 0?}
L -- Yes --> M[Substitute prev percentage+level\nkeep fresh status]
L -- No --> N[Return fresh BatteryInfo]
M --> O[Store in Cached]
N --> O
O --> P[GUI render]
B -- No, cache hit --> Q[read_battery only\ncheap 1 round-trip]
Q --> K
P --> R{battery_charging_no_reading?\ncharging AND percentage==0}
R -- Yes --> S[Show Charging without %\nleave progress bar empty]
R -- No --> T[Show percentage% and fill bar]
Reviews (4): Last reviewed commit: "feat(battery): support legacy 0x1000 Bat..." | Re-trigger Greptile |
4cffc1a to
86e4154
Compare
|
Thanks @greptile-apps — applied all three:
|
|
All three changes look correct and well-executed. A few observations: 1. 2. 3. let other = HidppLegacyBatteryStatus::try_from(7u8).expect("7 is a known variant");
assert_eq!(map_legacy_battery_status(other), BatteryStatus::Unknown);This validates both that the One minor nit on The PR is in good shape. LGTM. Tip: You can customize Greptile's behavior for this repo with |
86e4154 to
ffa4f23
Compare
AprilNEA
left a comment
There was a problem hiding this comment.
Thanks for the careful follow-up here — the 0x1000 fallback, diagnostic command, and regression tests all look well-scoped, and CI is green.
I found two edge cases worth tightening before merge:
-
hold_percentage_while_chargingshould probably be scoped toBatteryProbe::Legacy. The helper and comments describe the MX2S /0x1000charging quirk, but it is currently applied afterprobe_featuresand on cache refresh regardless of whether the selected probe isUnifiedorLegacy(crates/openlogi-hid/src/inventory.rs, around the calls after fresh reads). On a0x1004device that legitimately reportscharging_percentage = 0while charging, or reports no percentage support, a previous non-zero cached value could be carried forward even though the legacy quirk is not involved. Matching onBatteryProbe::Legacyat the call site would keep the workaround tied to the feature that needs it. -
The cold-start GUI suppression hides the
0%text, but the detail summary progress bar still renders frombattery.percentage(relative_percent(0)andbattery_color(0)). That means the summary can still show an empty/critical-looking bar for the same “Charging, no reliable reading” case. It may be worth hiding the bar, using a neutral/charging style, or otherwise routingbattery_charging_no_reading(battery)through the progress-bar rendering too.
Otherwise this is a nice, focused addition — especially the raw openlogi diag battery path for verifying the firmware behavior on real devices.
ffa4f23 to
7e11836
Compare
|
Both addressed:
|
…uirk Older devices such as a Bluetooth-direct MX Master 2S expose only the legacy BatteryStatus feature (0x1000), never the unified 0x1004, so they showed no battery at all. Implement 0x1000 mirroring the SmartShift 0x2110/0x2111 design and have the inventory probe prefer 0x1004, falling back to 0x1000 — the same enhanced-then-legacy order SmartShift uses. 0x1000 reports a percentage but no level bitmask, so the coarse BatteryLevel is derived from fixed display buckets. The 0x1000 firmware can't gauge charge under load: it reports discharge_level=0 status=Recharging while charging, which surfaced as a misleading "Charging · 0%". Two layers handle it: - hold_percentage_while_charging() carries the last-known percentage forward through a charge session (frozen pre-charge value, cache only) so the reading stays trackable once one discharge read exists. - On a cold start (charger plugged before the app opens) there's no prior to hold, so the GUI shows "Charging" instead of the bogus 0% across the card, summary, and tray menu. Adds `openlogi diag battery`, which prints the raw 0x1004/0x1000 report so a claim like "MX2S shows 0% while charging" can be confirmed against the wire. Verified on hardware: MX2S reports 50% (discharging) over BT-direct, and 0% status=Recharging while charging.
7e11836 to
d54b363
Compare
Summary
Older mice like a Bluetooth-direct MX Master 2S only expose the legacy BatteryStatus feature (0x1000), never the unified 0x1004, so they showed no battery at all. This adds 0x1000 support and handles its quirk: the firmware reports 0% while charging.
Changes
openlogi diag batteryto print the raw 0x1004/0x1000 report.Testing
cargo fmt --all -- --check,cargo clippy --workspace,cargo test(bucket mapping, hold-% across all four branches, GUI label).