Skip to content

fix(unifying): enable wireless notifications so paired devices enumerate#309

Open
laofun wants to merge 1 commit into
AprilNEA:masterfrom
laofun:pr/unifying-wireless-notifications
Open

fix(unifying): enable wireless notifications so paired devices enumerate#309
laofun wants to merge 1 commit into
AprilNEA:masterfrom
laofun:pr/unifying-wireless-notifications

Conversation

@laofun

@laofun laofun commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

A device on a Unifying receiver wouldn't show up even while connected. The
receiver only re-broadcasts arrival events while wireless notifications
(register 0x00) are on, and we never enabled it — so the trigger got ACK'd
but emitted nothing. Solaar does this before listing.

Changes

  • Enable wireless notifications before draining arrivals.
  • Derive online from the feature walk succeeding (event.online was always
    "offline" here).
  • Read device names at Unifying's 0x40 base instead of Bolt's 0x60, and
    clamp the reported length.
  • Dedup repeated arrivals per slot.

Testing

Tested on an MX Master 2S over a Unifying receiver. Added unit tests for the
name parser (normal, over-long length, short response).

@laofun laofun force-pushed the pr/unifying-wireless-notifications branch from 58d6a8e to f68b17d Compare June 22, 2026 15:46
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes Unifying receiver enumeration by enabling wireless notifications (register 0x00) before triggering device-arrival events, mirroring Solaar's approach. It also corrects the online-status derivation (from a wrong event bit to feature-walk success), fixes the name sub-register base (0x40 vs. Bolt's 0x60), and deduplicates repeated 0x41 arrivals per slot.

  • Wireless notification enable: set_wireless_notifications(true) is called before trigger_device_arrival; uses a read-modify-write on register 0x00 to avoid clobbering SOFTWARE_PRESENT.
  • Online flag fix: online is now derived from probe.capabilities.is_some() instead of event.online, which was always wrong for triggered arrivals (bit6 always set in wire-captured payloads).
  • Name reader: New read_codename_unifying / parse_codename_unifying functions read from sub-register base 0x40 + (n-1) with length clamping; three unit tests added to cover normal, over-long, and short-response cases.

Confidence Score: 4/5

Safe to merge for the core enumeration fix; one logic gap in error handling could transiently clear the device list on a first-cycle HID read failure.

The wireless-notification enable, name-register fix, and online-flag correction are all well-motivated and wire-verified. The one concern is drain_device_arrival_unifying: when set_wireless_notifications fails but trigger_device_arrival succeeds, the function returns Some([]) rather than None, telling the ledger 'no devices found' instead of 'probe unreliable — use last snapshot.' On a fresh attach combined with a transient HID read error, this would show all paired devices as offline for that cycle.

crates/openlogi-hid/src/inventory.rs — specifically the error handling in drain_device_arrival_unifying

Important Files Changed

Filename Overview
crates/openlogi-hid/src/inventory.rs Adds wireless-notification enable before arrival drain, dedup+sort for duplicate 0x41 events, Unifying-specific name reader with length clamping, and corrects online flag to use feature-walk success. One logic gap: a failed set_wireless_notifications is silently swallowed, causing Some([]) to be returned rather than None when the WIRELESS bit was not already set.
crates/openlogi-hidpp/src/receiver/unifying.rs Adds Register::Notifications = 0x00 enum variant and set_wireless_notifications() with read-modify-write to avoid clobbering SOFTWARE_PRESENT. Adds clarifying NOTE to DeviceCodename = 0x60 about the Bolt-vs-Unifying base address discrepancy.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Inv as inventory.rs
    participant Uni as UnifyingReceiver
    participant HW as Receiver HW

    Inv->>Uni: drain_device_arrival_unifying()
    Uni->>HW: read_register(0x00) [Notifications]
    HW-->>Uni: current flags
    Uni->>HW: "write_register(0x00, flags | WIRELESS)"
    HW-->>Uni: ACK
    Note over Inv,HW: WIRELESS bit now set — 0x41 events will flow
    Uni->>HW: trigger_device_arrival (write 0x02)
    HW-->>Uni: ACK
    HW-)Uni: 0x41 DeviceConnection (slot 1)
    HW-)Uni: 0x41 DeviceConnection (slot 1, duplicate)
    Uni-->>Inv: Some([conn_slot1, conn_slot1])
    Note over Inv: Dedup by slot index (HashMap), then sort
    Inv->>Uni: read_long_register(0xFF, 0xB5, [0x40+slot-1, …])
    HW-->>Uni: [sub, len, name bytes, padding]
    Note over Inv: parse_codename_unifying clamps len
    Inv->>HW: "probe_or_reuse(slot, online=true)"
    HW-->>Inv: capabilities (Some → online, None → offline)
Loading
%%{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"}}}%%
sequenceDiagram
    participant Inv as inventory.rs
    participant Uni as UnifyingReceiver
    participant HW as Receiver HW

    Inv->>Uni: drain_device_arrival_unifying()
    Uni->>HW: read_register(0x00) [Notifications]
    HW-->>Uni: current flags
    Uni->>HW: "write_register(0x00, flags | WIRELESS)"
    HW-->>Uni: ACK
    Note over Inv,HW: WIRELESS bit now set — 0x41 events will flow
    Uni->>HW: trigger_device_arrival (write 0x02)
    HW-->>Uni: ACK
    HW-)Uni: 0x41 DeviceConnection (slot 1)
    HW-)Uni: 0x41 DeviceConnection (slot 1, duplicate)
    Uni-->>Inv: Some([conn_slot1, conn_slot1])
    Note over Inv: Dedup by slot index (HashMap), then sort
    Inv->>Uni: read_long_register(0xFF, 0xB5, [0x40+slot-1, …])
    HW-->>Uni: [sub, len, name bytes, padding]
    Note over Inv: parse_codename_unifying clamps len
    Inv->>HW: "probe_or_reuse(slot, online=true)"
    HW-->>Inv: capabilities (Some → online, None → offline)
Loading

Reviews (4): Last reviewed commit: "fix(unifying): enable wireless notificat..." | Re-trigger Greptile

Comment thread crates/openlogi-hid/src/inventory.rs
@laofun laofun force-pushed the pr/unifying-wireless-notifications branch from f68b17d to 9aff9b7 Compare June 22, 2026 16:00
@laofun

laofun commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both review notes: the deduped connection list is now sorted by slot so device order is stable across probe cycles, and added a note on InfoSubRegister::DeviceCodename clarifying that 0x60 is the Bolt base while Unifying name reads go through the wire-verified 0x40 path in inventory.rs.

@laofun laofun closed this Jun 22, 2026
@laofun laofun reopened this Jun 22, 2026

@AprilNEA AprilNEA left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the right direction overall — enabling the Unifying notification stream before trigger_device_arrival, correcting the 0x40 name base, and deduping repeated arrivals are all well-scoped, and CI is green.

One edge I’d tighten: set_wireless_notifications(true) currently writes the whole notifications register as [0, 1, 0]. That sets the wireless bit, but it also clears any other notification flags already enabled on register 0x00. In this repo, the pairing flow writes [0x00, 0x09, 0x00] (WIRELESS | SOFTWARE_PRESENT) before streaming pairing events, so an inventory poll during/near pairing could accidentally drop the software-present bit or any future notification flag.

Could we make this read-modify-write, or at least use the same named notification flag constants as pairing.rs so the intended bitmask is explicit? The rest of the PR looks good to me.

The receiver only re-broadcasts 0x41 device-arrival events while wireless
notifications (register 0x00) are on; without it an actively-connected
device showed "no paired devices". Add set_wireless_notifications() and
enable it before draining arrivals (Solaar does the same before listing).

Derive online from the HID++ 2.0 feature walk succeeding — the crate's
event.online reads the wrong notification byte (always "offline" here) and
trigger_device_arrival re-broadcasts 0x41 for offline slots too. Read
device names at the Unifying 0x40 base (not Bolt's 0x60), clamp the
device-reported name length so a bogus byte can't over-read the register,
and dedup repeated arrivals per slot.
@laofun laofun force-pushed the pr/unifying-wireless-notifications branch from 9aff9b7 to f2b7ef5 Compare June 25, 2026 03:03
@laofun

laofun commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — switched set_wireless_notifications to read-modify-write: it now reads the notifications register first and only flips the WIRELESS bit, so SOFTWARE_PRESENT and any other flags are preserved.

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.

2 participants