Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for passive BLE scanning #27

Closed
Redrield opened this issue Mar 11, 2022 · 20 comments · Fixed by #80
Closed

Add support for passive BLE scanning #27

Redrield opened this issue Mar 11, 2022 · 20 comments · Fixed by #80
Labels
enhancement New feature or request

Comments

@Redrield
Copy link

I'm trying to use bluer on a raspberry pi to scan for advertising BLE devices. In doing some research, I'm fairly certain that bluer only supports active (ie. scan request) discovery, and that there is code in BlueZ to support passive scanning (used in hcitool lescan, for example), but there isn't anything in bluer to give access to passive scanning.

@surban
Copy link
Collaborator

surban commented Mar 11, 2022

This would probably be provided by the advertisement monitoring api which is indeed not yet implemented in BlueR.

@surban surban added the enhancement New feature or request label Mar 11, 2022
@otaviojr
Copy link
Contributor

otaviojr commented Sep 22, 2022

I'm working on this for a few days now, it's not working yet. It should, but bluez is not calling the callback functions.

Maybe something with bluez?

I compiled the latest version manually since monitoring API is something new.

I forked bluer:
https://github.com/otaviojr/bluer/tree/monitor

The code is simple:

callbacks:

    pub fn activate_fn() -> Pin<Box<dyn Future<Output = bluer::monitor::ReqResult<()>> + Send>> {
        println!("Activate funcion called(1)");
        Box::pin(async {
            println!("Activate funcion called(2)");
            Ok(())
        })
    }

    pub fn release_fn() -> Pin<Box<dyn Future<Output = bluer::monitor::ReqResult<()>> + Send>> {
        println!("Release funcion called(1)");
        Box::pin(async {
            println!("Release funcion called(2)");
            Ok(())
        })
    }

    pub fn device_found_fn(device: DeviceFound) -> Pin<Box<dyn Future<Output = bluer::monitor::ReqResult<()>> + Send>> {
        println!("DeviceFound funcion called(1)");
        Box::pin(async {
            println!("DeviceFound funcion called(2)");
            Ok(())
        })
    }

code:

if let Ok(monitor_handle) = adapter.register_monitor(Monitor {
                        activate: Some(Box::new(BlueZ::activate_fn)),
                        release: Some(Box::new(BlueZ::release_fn)),
                        device_found: Some(Box::new(BlueZ::device_found_fn)),
                        patterns: Some((0x00, 0xff,vec![0xbe,0xac])),
                        ..Default::default()
                    }).await { ...

Now, a few things on bluer may complicate things further.

BlueR changes some filters on discover_devices on the user's behalf, it doesn't allow duplicates, which could be a problem with beacons, and it does not allow us to choose a LE scan only.

I think discover_devices is not necessary with passive scan, but if they work simultaneously, it could be a problem with beacons. I don't know.

And sorry if my Rust code is not state of the art. I'm relatively new to Rust.. just trying to do my best here.

regards,
Otávio Ribeiro

@otaviojr
Copy link
Contributor

Oh, and I'm having a hard time with those or_patterns as well... but I will figure them out eventually.

@lopsided98
Copy link

I also started a prototype a while ago, but forgot to post it here: lopsided98/bluer@master...adv-mon

It works, but currently only supports one monitor at a time. The monitor root can only contain a single monitor right now because dbus-crossroads seems to only support sending added/removed signals for the root ObjectManager, so I can't tell BlueZ when a new monitor was added.

I also found that adjusting any of the RSSI settings broke the monitor (I forget the exact details, but most of the time I would just get no notifications), and I was never able to get the DeviceLost callback to work reliably. These both appear to be BlueZ issues.

Despite these issues, I was to get it working well for my application. To reliably get notifications when a device starts reappears after disappearing for a while, I have to re-register the monitor. My application code can be found here: https://github.com/lopsided98/WaterLevelMonitor/blob/54e8bc2219d0ef33d84bb9c4fb57ed287cb848a3/base_station/src/sensor.rs#L185

@otaviojr
Copy link
Contributor

I managed to make it to work. At least receiving the Activate/Release callbacks.

It was the object manager indeed. Once I implemented it on the monitor interface things started to work.

I managed to implement object manager at the monitor path, so, in my version, you can have more than one monitor, as many as bluez allows. At least, in theory, I will make more tests on it. If you want to get a look at it, maybe you could allow more than one monitor on your version as well.

Now, I need to finish the DeviceFound and DeviceLost endpoints and I think it will be good to go.

regards,
Otávio Ribeiro

@lopsided98
Copy link

It looks like you are still calling RegisterMonitor and UnregisterMonitor for each monitor, right? If the object manager signals are working, you should be able to register the root once (with RegisterMonitor) and then just add monitors under that root through the object manager.

@otaviojr
Copy link
Contributor

I made the last commit with those changes....

I'm implementing the object manager at the root level, but my root level is the uuid. The AdvertisementMonitor1 interface is one level above the uuid...

that allows you to have multiple monitors.

object manager -> /org/bluez/bluer/monitor/4e7adc5601864010bf393486740bbe92
AdvertisementMonitor1 -> /org/bluez/bluer/monitor/4e7adc5601864010bf393486740bbe92/app

    pub(crate) async fn register(self, inner: Arc<SessionInner>, adapter_name: &str) -> Result<MonitorHandle> {
        let manager_path = dbus::Path::new(format!("{}/{}", MANAGER_PATH, adapter_name)).unwrap();
        let uuid = Uuid::new_v4().as_simple().to_string();
        let root = dbus::Path::new(format!("{}/{}",MONITOR_PREFIX,uuid)).unwrap();
        let name = dbus::Path::new(format!("{}/{}/app",MONITOR_PREFIX,uuid)).unwrap();

        log::trace!("Publishing monitor at {}", &name);

        {
            let mut cr = inner.crossroads.lock().await;
            let object_manager_token = cr.object_manager();
            let introspectable_token = cr.introspectable();
            let properties_token = cr.properties();
            cr.insert(root.clone(), [&object_manager_token, &introspectable_token, &properties_token], {});
            cr.insert(name.clone(), [&inner.monitor_token], Arc::new(self));
        }

        log::trace!("Registering monitor at {}", &name);
        let proxy = Proxy::new(SERVICE_NAME, manager_path, TIMEOUT, inner.connection.clone());
        proxy.method_call(MANAGER_INTERFACE, "RegisterMonitor", (root.clone(),)).await?;

        let (drop_tx, drop_rx) = oneshot::channel();
        let unreg_name = root.clone();
        tokio::spawn(async move {
            let _ = drop_rx.await;

            log::trace!("Unregistering monitor at {}", &unreg_name);
            let _: std::result::Result<(), dbus::Error> =
                proxy.method_call(MANAGER_INTERFACE, "UnregisterMonitor", (unreg_name.clone(),)).await;

            log::trace!("Unpublishing monitor at {}", &unreg_name);
            let mut cr = inner.crossroads.lock().await;
            let _: Option<Self> = cr.remove(&unreg_name);
        });

        Ok(MonitorHandle { name, _drop_tx: drop_tx })
    }

regards,
Otávio Ribeiro

@lopsided98
Copy link

Yeah, I guess that works, but it doesn't seem to be the how the advertisement monitor API is intended to be used. The documentation implies that you are supposed to create a single root object manager per application and then create multiple monitors underneath that, using the object manager signals to tell BlueZ that they were added/removed.

@otaviojr
Copy link
Contributor

otaviojr commented Sep 23, 2022

You are right, and you made me see things from a different perspective.

Looking at the API documentation, we must register the root endpoint once and add the AdvertisementMonitor1 interface underneath it multiple times.

Instead, we are adding the root endpoint for every monitor we register.

And this is also the problem with the crossroad. Every time we add the same path again, it replaces the old one and loses the previous child structure. In the end, it will have only the last monitor as a child, and the previous ones will stop working.

With that in mind, I made another proposal, one where we register the monitor once and then add as many rules as we want.

I created a new branch for it:
https://github.com/otaviojr/bluer/tree/monitor1

And here is the code I used to try it out.

if let Ok(mut monitor_handle) = adapter.register_monitor().await {
    monitor_handle.add_monitor(Monitor {
        activate: Some(Box::new(move || {
            Box::pin(async {
                println!("Monitor 1: Activate funcion called");
                Ok(())
            })
        })),
        release: Some(Box::new(move || {
            Box::pin(async {
                println!("Monitor 1: Release funcion called");
                Ok(())
            })
        })),
        device_found: Some(Box::new(move |device| {
            Box::pin(async move {
                println!("Monitor 1: DeviceFound funcion called: {}",device.addr);
                Ok(())
            })
        })),
        patterns: Some(vec!(Pattern {
            start_position: 2,
            ad_data_type: 0xff,
            content_of_pattern: vec!(190, 172, 57, 237, 152, 255, 41, 0, 68, 26, 128, 47, 156, 57, 143, 193, 153, 210, 0, 1, 0, 100, 197, 100)
        })),
        rssi_low_threshold: Some(127),
        rssi_high_threshold: Some(127),
        rssi_low_timeout: Some(0),
        rssi_high_timeout: Some(0),
        ..Default::default()
    }).await;
    monitor_handle.add_monitor(Monitor {
        activate: Some(Box::new( move || {
            Box::pin(async {
                println!("Monitor 2: Activate funcion called");
                Ok(())
            })
        })),
        release: Some(Box::new( move || {
            Box::pin(async {
                println!("Monitor 2: Release funcion called");
                Ok(())
            })
        })),
        device_found: Some(Box::new(move |device| {
            Box::pin(async move {
                println!("Monitor 2: DeviceFound funcion called: {}",device.addr);
                Ok(())
            })
        })),
        patterns: Some(vec!(Pattern {
            start_position: 2,
            ad_data_type: 0xff,
            content_of_pattern: vec!(190, 172, 57, 237, 152, 255, 41, 0, 68, 26, 128, 47, 156, 57, 143, 193, 153, 210, 0, 1, 0, 100, 197, 100)
        })),
        rssi_low_threshold: Some(127),
        rssi_high_threshold: Some(127),
        rssi_low_timeout: Some(0),
        rssi_high_timeout: Some(0),
        ..Default::default()
    }).await;
}

With this code, I have been able to receive events for both monitors.

Monitor 1: DeviceFound funcion called: 60:68:4E:15:18:25
Monitor 2: DeviceFound funcion called: 60:68:4E:15:18:25

And the dbus objects's paths are now following what has been suggested in the API documentation.

PS: I borrowed some pieces of your code, especially the one that handles the or_patterns, which are much better than mine. Thanks for that.

regards,
Otávio Ribeiro

@hpux735
Copy link

hpux735 commented Mar 13, 2023

Is this issue still open? I'm hoping to use this lib for essentially beacon advertisements, so it seems like this issue might be impactful for me?

@otaviojr
Copy link
Contributor

I believe it is still open.

I am still waiting for feedback.

You could get my branch, update it and use it.

I've been using it for a while now, and it works well.

Regards

@jhartzell42
Copy link

jhartzell42 commented Mar 28, 2023

@otaviojr I got feedback pretty quickly for my (unrelated) change once I actually made a MR. Would you be willing to make a MR of your branch? If not, I can make an MR from a copy of your branch on my fork, so I can handle the feedback and make appropriate updates.

@surban
Copy link
Collaborator

surban commented Mar 28, 2023

Could you please send a PR if you would like a review? This makes it easier for everyone.

@jhartzell42
Copy link

OK, I made an MR on @otaviojr 's behalf...

@jhartzell42
Copy link

If @surban would be interested, I can move this MR to my own fork and clean up the commit log as well :-)

@otaviojr
Copy link
Contributor

otaviojr commented May 10, 2023

OK, I made an MR on @otaviojr 's behalf...

Sorry for the delay, but thanks. If you need something, let me know...

About the commit logs, I developed them on a remote host and used git to sync code... :-(

Can be cleaned

otaviojr added a commit to otaviojr/bluer that referenced this issue May 10, 2023
# This is the 1st commit message:

BLE Passive Scanning

# This is the commit message #2:

monitor

# This is the commit message bluez#3:

monitor

# This is the commit message bluez#4:

monitor

# This is the commit message bluez#5:

monitor

# This is the commit message bluez#6:

monitor

# This is the commit message bluez#7:

monitor

# This is the commit message bluez#8:

monitor

# This is the commit message bluez#9:

monitor

# This is the commit message bluez#10:

monitor

# This is the commit message bluez#11:

monitor

# This is the commit message bluez#12:

monitor

# This is the commit message bluez#13:

monitor

# This is the commit message bluez#14:

monitor

# This is the commit message bluez#15:

monitor

# This is the commit message bluez#16:

monitor

# This is the commit message bluez#17:

monitor

# This is the commit message bluez#18:

monitor

# This is the commit message bluez#19:

monitor

# This is the commit message bluez#20:

monitor

# This is the commit message bluez#21:

monitor

# This is the commit message bluez#22:

monitor

# This is the commit message bluez#23:

monitor

# This is the commit message bluez#24:

monitor

# This is the commit message bluez#25:

monitor

# This is the commit message bluez#26:

monitor

# This is the commit message bluez#27:

monitor

# This is the commit message bluez#28:

monitor

# This is the commit message bluez#29:

monitor

# This is the commit message bluez#30:

monitor

# This is the commit message bluez#31:

monitor

# This is the commit message bluez#32:

monitor

# This is the commit message bluez#33:

monitor

# This is the commit message bluez#34:

monitor

# This is the commit message bluez#35:

monitor

# This is the commit message bluez#36:

monitor

# This is the commit message bluez#37:

monitor

# This is the commit message bluez#38:

monitor

# This is the commit message bluez#39:

monitor

# This is the commit message bluez#40:

monitor

# This is the commit message bluez#41:

monitor

# This is the commit message bluez#42:

monitor

# This is the commit message bluez#43:

monitor

# This is the commit message bluez#44:

monitor

# This is the commit message bluez#45:

monitor

# This is the commit message bluez#46:

monitor

# This is the commit message bluez#47:

monitor

# This is the commit message bluez#48:

monitor

# This is the commit message bluez#49:

monitor

# This is the commit message bluez#50:

monitor

# This is the commit message bluez#51:

monitor

# This is the commit message bluez#52:

monitor

# This is the commit message bluez#53:

monitor

# This is the commit message bluez#54:

monitor

# This is the commit message bluez#55:

monitor

# This is the commit message bluez#56:

monitor

# This is the commit message bluez#57:

monitor

# This is the commit message bluez#58:

monitor

# This is the commit message bluez#59:

monitor

# This is the commit message bluez#60:

monitor

# This is the commit message bluez#61:

monitor

# This is the commit message bluez#62:

monitor
@otaviojr
Copy link
Contributor

@jhartzell42 I've cleaned all commit logs at https://github.com/otaviojr/bluer/tree/monitor1 branch

Can you change your pull request?

@jhartzell42
Copy link

I can't retarget it to a different branch, and I can't modify the branch I'm using, as it's a pull request literally from your fork. Could you possibly make your own pull request from monitor1?? If not, I'll delete the one I have, make my own fork, migrate your new branch over, and make a new pull request.

@otaviojr
Copy link
Contributor

@jhartzell42 done

@surban surban linked a pull request Jun 13, 2023 that will close this issue
@surban
Copy link
Collaborator

surban commented Jun 13, 2023

PR merged.

@surban surban closed this as completed Jun 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants