Skip to content

scan: unnamed peripherals are silently filtered out, so a bonded ring is never found #13

Description

@tuusberg

Summary

oura scan silently drops any peripheral that advertises without a local name. Since a ring that is bonded to the official Oura app commonly advertises with no local_name, the very first command a new user runs reports "No Oura rings found." while the ring is sitting a foot away.

Cause

crates/oura-link/src/ble.rs:66-69

let name = props.local_name.unwrap_or_default();
if !name.to_lowercase().contains(&name_contains.to_lowercase()) {
    continue;
}

With the default --name Oura, a missing local_name becomes "", and "".contains("oura") is false, so the device is skipped with no output.

The same filter runs at ble.rs:114-117, on the connect path — and there it is applied before the --address check at ble.rs:118-120. So --address <id> alone does not work around it either; you must also pass --name "".

Reproduce

Ring 4, bonded to the official app, macOS 26.5, btleplug 0.11, HEAD c5106bd:

$ oura scan
No Oura rings found.

$ oura scan --name "" --scan-timeout 40
  -60 dBm    (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)

One device, strong RSSI, empty name field — the ring, excluded by the default filter.

Suggested fix

Keep unnamed peripherals when the user has not explicitly narrowed the search, so discovery degrades to "show me what's there" rather than to silence:

let name = props.local_name.unwrap_or_default();
if !name_contains.is_empty()
    && !name.is_empty()
    && !name.to_lowercase().contains(&name_contains.to_lowercase())
{
    continue;
}

Alternatives that would also solve it, if you would rather not widen the default:

  • Filter on the Oura service UUID instead of the advertised name — robust regardless of whether a name is present.
  • Have the empty result mention the filter: No devices matching "Oura". Try --name "" to list everything. That alone would have saved the debugging session.
  • Apply --address before --name on the connect path, so an explicit address bypasses name filtering.

Related, possibly worth its own issue

There appears to be no timeout on the request/response path. ble.rs sets deadlines for scanning (ble.rs:47, ble.rs:103) but the notification pump (ble.rs:166-171) has none, so if a ring accepts the connection and then never replies, oura info hangs indefinitely with no error or diagnostic. Happy to file that separately if useful.

Thanks for open-sourcing this — the protocol work is impressive, and everything above is from a first-hour attempt to get a Ring 4 talking.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions