Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Hey! I have scale that will submit measurements over bluetooth. I'm trying to log that on a Raspberry Pi using this crate.
I have made a basic program that discovers nearby devices, and here is some example output for the scale:

Ok([Name("MIBFS"), AddressType(LePublic), Uuids({0000181b-0000-1000-8000-00805f9b34fb}), Paired(false), Connected(false), Trusted(false), Blocked(false), Alias("MIBFS"), LegacyPairing(false), Rssi(-65), ManufacturerData({343: [216, 231, 47, 166, 17, 33]}), ServiceData({0000181b-0000-1000-8000-00805f9b34fb: [2, 164, 178, 7, 1, 8, 6, 93, 48, 255, 253, 63, 57]}), ServicesResolved(false)])

Now, I just want to log the ServiceData({0000181b-0000-1000-8000-00805f9b34fb: [2, 164, 178, 7, 1, 8, 6, 93, 48, 255, 253, 63, 57]}) part, whenever the scale submits a new measurement.
I've found a tiny Python example here: https://github.com/led788/bodyscale2/blob/main/main.py

From the bluer examples, I think this one is relevant: https://github.com/bluez/bluer/blob/master/bluer/examples/le_passive_scan.rs

Specifically:

let mm = adapter.monitor().await?;
   let mut monitor_handle = mm
       .register(Monitor {
           monitor_type: bluer::monitor::Type::OrPatterns,
           rssi_low_threshold: None,
           rssi_high_threshold: None,
           rssi_low_timeout: None,
           rssi_high_timeout: None,
           rssi_sampling_period: Some(RssiSamplingPeriod::First),
           patterns: Some(vec![pattern]),
           ..Default::default()
       })
       .await?;

   while let Some(mevt) = &monitor_handle.next().await {
       if let MonitorEvent::DeviceFound(devid) = mevt {
           println!("Discovered device {:?}", devid);
           let dev = adapter.device(devid.device)?;
           tokio::spawn(async move {
               let mut events = dev.events().await.unwrap();
               while let Some(ev) = events.next().await {
                   println!("On device {:?}, received event {:?}", dev, ev);
               }
           });
       }
   }

Since I know the adress of exactly the device I'm looking for, I'm wondering whether this example is the way to go, though. It seems to continuously look for all nearby devices, which seems redundant to me. I don't think I can skip directly to the let mut events = dev.events().await.unwrap(); part either, though, since that would fail when the scale turns off after a measurement. Is there a better way to approach this?

Also, I'm wondering how to use the the Patterns for filtering. I've taken a look here and here, but I can't quite figure out how to use that for my use case.

You must be logged in to vote

Replies: 2 comments · 3 replies

Comment options

Your code seems reasonable to me.

I have not used LE passive monitoring with filters myself. The filtering is performed by the Bluetooth controller, so this is a low-level feature. Maybe @kjetijor can help.

You must be logged in to vote
3 replies
@LeCyberDucky
Comment options

Having tinkered around with this a bit, I can't actually get monitoring to work, though.

I have made this little example:

use std::str::FromStr;
use color_eyre::Result;

#[tokio::main]
async fn main() -> Result<()> {    
    let bluetooth = bluer::Session::new().await?;
    let mut adapter = bluetooth.default_adapter().await?;
    adapter.set_powered(true).await?;

    let mut monitor = adapter.monitor().await?.register(
        bluer::monitor::Monitor::default()
    ).await?;

    Ok(())
}

Running this code results in

Error: advertisement monitor could not be activated

Location:
    src/main.rs:10:23

Is this something I should create an issue about?

Initially, I ran into these UnknownMethod errors, but I got rid of those by upgrading my Raspberry Pi OS to Bookworm and building and installing bluez from source with experimental features enabled. I now have bluetoothctl: 5.76, but I am left with the problem mentioned above. Could this be related to #110?

@LeCyberDucky
Comment options

I believe using this bluer::monitor::Monitor::default() for my monitor is what prevented me from activating the monitor.

Specifically, the patterns field of a monitor::Monitor is listed as being

Required if monitor_type is Type::OrPatterns.

Naturally, this is the case, since Type::OrPatterns is the only available monitor_type.

Ideally, I think this requirement should be encoded in the type system somehow. Perhaps patterns could be a mandatory member of Type::OrPatterns, instead of an optional member of monitor::Monitor.
Alternatively, this requirement could at least be clearly stated in an error message.

Would a pull request for one of these options be appreciated?

Now, with the above out of the way, I still have no clue how to actually use these patterns. I think
this https://docs.rs/bluer/latest/bluer/monitor/struct.Pattern.html#structfield.data_type and this https://docs.rs/bluer/latest/bluer/monitor/data_type/index.html assumes some knowledge about the patterns that I don't have. Is there a more beginner friendly description of the patterns? I only found this https://man.archlinux.org/man/org.bluez.AdvertisementMonitor.5.en, which is more or less the same information.

Currently, my best bet is this description generated by ChatGPT, but I don't think that sounds quite right:

Pattern Structure:
Each pattern consists of a tuple with three elements: (uint8, uint8, array{byte}).
The three elements are as follows:
Pattern Type (uint8): Specifies the type of pattern. It can be one of the following:
0: Match the entire advertisement data.
1: Match the manufacturer data.
2: Match the service UUIDs.
3: Match the service data.
Pattern Flags (uint8): Indicates the flags for the pattern. These flags control how the pattern is applied:
0: No flags (exact match).
1: Use a bitmask for matching.
2: Use a bitmask for matching and apply it to the data.
Pattern Data (array{byte}): Contains the actual pattern data to match against.
Examples:
Suppose we want to monitor advertisements from devices with a specific manufacturer ID (e.g., Apple). We would create a pattern like this:
(1, 0, [0x4C, 0x00]) (where 0x4C corresponds to Apple’s manufacturer ID).
To monitor specific service UUIDs (e.g., Bluetooth Low Energy heart rate service), we’d use:
(2, 0, [0x18, 0x0D]) (where 0x18 0x0D represents the heart rate service UUID).
For service data matching (e.g., temperature data from a specific sensor):
(3, 0, [0x18, 0x1C, 0x02, 0x0A, 0x00]) (where 0x18 0x1C is the service UUID, and 0x02 0x0A 0x00 is the specific service data).

@surban
Comment options

Naturally, this is the case, since Type::OrPatterns is the only available monitor_type.

Ideally, I think this requirement should be encoded in the type system somehow. Perhaps patterns could be a mandatory member of Type::OrPatterns, instead of an optional member of monitor::Monitor. Alternatively, this requirement could at least be clearly stated in an error message.

Would a pull request for one of these options be appreciated?

Yes, making the patterns a part of Type::OrPatterns would make sense and a PR would be welcome.

Now, with the above out of the way, I still have no clue how to actually use these patterns. I think this https://docs.rs/bluer/latest/bluer/monitor/struct.Pattern.html#structfield.data_type and this https://docs.rs/bluer/latest/bluer/monitor/data_type/index.html assumes some knowledge about the patterns that I don't have. Is there a more beginner friendly description of the patterns? I only found this https://man.archlinux.org/man/org.bluez.AdvertisementMonitor.5.en, which is more or less the same information.

I think you set Pattern::data_type to the data type you want to match and Pattern::content to the byte values that should be matched. This is done by the controller firmware, so it is low level.

Comment options

Generally, I am not sure if using the advertisement monitor is the right approach. It is pretty low-level and the API is marked as experimental.

Wouldn't it be easier to use Adapter::discover_devices_with_changes to listen for all received LE advertisements and Device::service_data to read the desired data? You can see this approach in the blumon.rs example.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.