BlueZ Discovery Filters with Multiple Clients: Explain the Effective Scan Results
A BlueZ discovery filter expresses one client’s interest in a shared adapter. It does not create a private stream containing only that client’s devices. Make the application repeat its own eligibility checks, then investigate which active clients broaden the shared discovery work.
Applicability: BlueZ 5.79 Adapter1 and its tagged adapter.c implementation. The proposed experiment uses two persistent D-Bus connections on one authorized lab adapter. It does not tune controller scan windows or claim measured discovery probability. All devices, thresholds and observations below are hypothetical.

1. Identify the owner of a discovery session
The Adapter API allows one discovery session per client and adapter. StartDiscovery acquires that client’s session; StopDiscovery releases it. The underlying procedure can continue while another client still owns a session.
In the 5.79 source, discovery clients are looked up by D-Bus sender. Two modules sharing one D-Bus connection are therefore not independent filter owners. Their SetDiscoveryFilter calls replace the same client’s settings, and a second StartDiscovery is not another reference to count locally.
Assign one component to own discovery for each application connection. Let other components request interests through that owner, which builds a deliberate combined application policy. Store session state separately from the adapter’s Discovering property: that property reports shared activity, not proof that your own StartDiscovery succeeded.
A one-shot command that sets a filter and exits is unsuitable for this ownership experiment. Its bus owner disappears. Keep each test client’s connection alive across filter setup, start, observation and stop.
2. Separate requested filters from effective discovery work
BlueZ documents that filters from multiple clients are merged and device notifications are visible to all clients. Each recipient must check updates against its own intended criteria. Do not interpret another client’s matching device as evidence that SetDiscoveryFilter was ignored.
The tagged merge_discovery_filters function combines active discovery clients for the management-layer filter. With only RSSI-filtered clients, it selects the lowest threshold. UUID interests are collected together; an empty UUID list broadens that part of the filter to any UUID.
If a client has no proximity criterion, the merged kernel proximity filter is disabled. Pathloss cannot be computed by that kernel filter, so the implementation also avoids relying on a kernel RSSI threshold for that case. Ordinary unfiltered discovery alongside filtered discovery broadens the merged filter further.
These are management-filter facts, not a promise that every qualifying radio packet reaches every process. Controller behavior, daemon processing and available Device properties remain additional boundaries. The final application predicate is still necessary.
Pattern, Pathloss and transport combinations deserve separate qualification if the product uses them. This fixture deliberately uses LE, UUID and RSSI criteria, avoiding a claim that every filter field merges by one universal Boolean rule.
3. Define a local acceptance predicate
For each update, first identify the adapter and current discovery generation. Then evaluate the product’s required service or payload evidence, required property availability and signal policy. Treat missing fields as unknown rather than inventing a value that passes.
In the hypothetical fixture, client A wants service U and a valid RSSI of at least −65 dBm. Client B wants service V and at least −85 dBm. These thresholds are chosen test values, not recommended coverage limits or distance estimates.
A service-V update at −75 dBm can be useful to B while A rejects it. A service-U update at −80 dBm still fails A’s local signal rule even if the shared daemon exposes it. Record the rejection reason without presenting the device as an eligible product.
Device properties form a cache and updates need not contain every field together. Preserve a bounded local view with per-field observation information. Do not assume an old cached UUID, name and newly changed RSSI came from one simultaneous advertisement.
If the workflow requires fresh payload evidence, require a suitable new observation during the current attempt. An existing object returned at startup does not alone prove a new radio observation. Keep discovery eligibility separate from authenticated device ownership.
4. Run a controlled two-client experiment
- Record BlueZ, kernel, controller firmware, adapter identity and enabled transports. List known discovery users without terminating unrelated software.
- Create two persistent D-Bus connections, A and B. Subscribe to relevant object/property changes before starting the test.
- Ask GetDiscoveryFilters which keys are available. Set A’s complete filter, including Transport=”le”, its UUID list, RSSI and an explicit DuplicateData choice.
- Start A and wait for its method result. Capture both daemon-visible updates and A’s accepted/rejected decisions.
- Set B’s complete filter and start B. Capture the changed management filter with authorized diagnostics and compare A’s local acceptance decisions.
- Stop B, leaving A active. Verify B released its own session and the remaining discovery/filter behavior is explainable.
- Stop A. Interpret any continuing Discovering state as a reason to inspect remaining owners, not permission to power-cycle the adapter.
Do not assume omitted options have identical behavior across documentation and implementation revisions. Explicit DuplicateData selection makes this experiment reproducible without turning it into a controller duplicate-filter benchmark.
For an additional phase, start a third ordinary discovery client without a filter. The exact source broadens aggregate filtering in that case. A must continue applying the same local eligibility predicate despite the larger visible set.
5. Test updates, shutdown and reconnect
A repeated SetDiscoveryFilter replaces the caller’s previous filter; it does not append another private rule. Send the whole desired dictionary and keep a revision number in application logs. In 5.79, an empty dictionary removes that caller’s filter.
Changing a filter while discovery is active can update shared work. Preserve method failures and the last successfully installed application intent. Do not claim a new filter took effect merely because the request was queued. In this exact implementation, an active-client update replaces stored intent, calls update_discovery_filter but ignores its return, and then returns D-Bus success; that successful reply alone does not confirm effective management-layer filtering. Verify the resulting filter with the planned diagnostic capture.
On normal shutdown, release only the session your owner acquired. If the D-Bus connection disappears, BlueZ’s disconnect watch invokes discovery cleanup. After daemon or bus reconnection, establish a fresh owner and reinstall intent; do not reuse a stale local “started” flag.
Acceptance requires an ownership log, effective-filter evidence and a deterministic application decision for each test update. A broader shared scan is acceptable when local product selection remains correct. The failure is accepting unintended devices or disrupting another client’s session to make a private assumption appear true.