bluest icon indicating copy to clipboard operation
bluest copied to clipboard

bluetooth advertisement support

Open davehorner opened this issue 1 year ago • 12 comments

thanks for the library. I found I was able to see my devices; I wanted to communicate with them.

I'd like to communicate with you too and see if this makes sense or if you have other ideas on implementation? Let me know if you have any thoughts on what I've got setup so far. the windows implementation is the only implementation that works now.

I'm trying to understand why I'm getting these full errors:

Windows advertisement started with company ID: 59.
2024-10-27T15:18:46.976723Z ERROR bluest::windows::adapter: Unable to send AdvertisingDevice: TrySendError { kind: Full }
2024-10-27T15:18:47.178354Z ERROR bluest::windows::adapter: Unable to send AdvertisingDevice: TrySendError { kind: Full }
2024-10-27T15:18:47.178949Z ERROR bluest::windows::adapter: Unable to send AdvertisingDevice: TrySendError { kind: Full }

I will keep playing. There's dead code and junk in there (ignore the Adapter modifications that was testing); it's a work in progress. :)

davehorner avatar Oct 27 '24 15:10 davehorner

it needs more work for review... just fyi im looking at it. if you have any suggestions or feedback let me know.

davehorner avatar Oct 27 '24 15:10 davehorner

I'd like to contribute advertisement support but based on the filtering discussion #24; I'm concerned I may be spending time on something that won't be something you're interested in. without any additional feedback on this, I am holding on additional effort.

davehorner avatar Oct 30 '24 01:10 davehorner

Sorry, I didn't look at this because it was closed. In the future, if you mark a work-in-progress PR as "draft" instead of closing it I'll be better able to review it.

Supporting host advertising is an appropriate thing for bluest to support, so I'm happy to consider PRs to add it. We just need to be careful with the API design so that it meets bluest's design goals of being cross-platform, idiomatic rust, and a thin wrapper around the OS APIs.

The biggest platform issue an advertising API is going to have is with CoreBluetooth (iOS/MacOS), which has the following wrinkles:

  • Advertising is done through a different object than interactions with remote peripherals (CBPeripheralManager vs CBCentralManager). This shouldn't be a big deal, but it means the advertising mechanism will need to be able to lazily create that object, without making users who aren't using host advertsing pay the cost of creating it.
  • The only advertisment data that is allowed is device name and service UUIDs (and I think the service UUIDs are restricted to be non-sensitive UUIDs as well). It's probably ok to just return an error if the user tries to provide disallowed advertisement data.

I'd suggest using the existing AdvertisementData struct to provide the advertising data. I'd also be inclined to use a RAII-style API where calling Adapter::start_advertising(data) returns an AdvertisingGuard that stops advertising when it is dropped.

alexmoon avatar Oct 30 '24 19:10 alexmoon

I do not have a Mac as of now but would be interested in helping with Linux and Windows implementations.

The only advertisement data that is allowed is device name and service UUIDs

huh. crazy limitation.

davehorner avatar Oct 30 '24 20:10 davehorner

I do not have a Mac as of now but would be interested in helping with Linux and Windows implementations.

That's fine, we can release an unstable version of the feature without Mac support. I just want to make sure the limitations of the Apple APIs are considered when designing the bluest API.

alexmoon avatar Oct 30 '24 20:10 alexmoon

please apologize the mess; concepts are there, I'm still using the methods I originally placed in some example code I've yet to refactor.

Are you thinking something like this on the Adapter?

    pub fn start_advertising(&self, data: AdvertisementData) -> Result<AdvertisingGuard, String> {
        self.0.start_advertising(data)
    }

I think just passing ManufacturerData would be more appropriate than AdvertismentData?; those other fields are not applicable.

#[derive(Debug, Clone)]
pub struct AdvertisingGuard {
    pub(crate) advertisement: Advertisement,
}

impl Drop for AdvertisingGuard {
    fn drop(&mut self) {
        // Stop advertising when `AdvertisingGuard` is dropped.
        self.advertisement.stop().expect("Failed to stop advertising");
    }
}

davehorner avatar Oct 31 '24 03:10 davehorner

Yes, that API is what I was thinking. We might also need a async fn stop(self) -> Result<(), Error> method on AdvertisingGuard to allow the user to asynchronously wait for the advertising to actually stop.

I think just passing ManufacturerData would be more appropriate than AdvertismentData?; those other fields are not applicable.

I’m not sure where you got that idea? Any of the fields in AdvertisingData can be included in an advertisement. In fact, as I mentioned above, ManufacturerData is not one of the allowed fields in CoreBluetooth.

alexmoon avatar Oct 31 '24 15:10 alexmoon

https://stackoverflow.com/questions/63175411/ios-omits-manufacturer-data-from-advertisement-in-background-mode "define a 16 bit or 128 bit GATT Service UUID and send out an advert with attached data bytes" wow. yuck, I don't mean to get hung up on corebluetooth but they really made simple hard.

I don't plan to advertise a name or service. Was just thinking control of the raw data would be a sufficient interface. It doesn't matter to me much if that's the struct you want to pack things in. If we're headed towards automatically adding the name or constructing the the ManufacturerData for the caller; idk about that.

corebluetooth made basic BLE advertisements unusable. Do you see doing acrobatics to accomplish "advertisements" via GATT? trying to receive advertisements in the background is basic need and I'd like my devices to work with corebluetooth. now come to find I'm likely going to need a custom solution to get the data...

davehorner avatar Oct 31 '24 15:10 davehorner

https://stackoverflow.com/questions/63175411/ios-omits-manufacturer-data-from-advertisement-in-background-mode "define a 16 bit or 128 bit GATT Service UUID and send out an advert with attached data bytes" wow. yuck, I don't mean to get hung up on corebluetooth but they really made simple hard.

Yes, CoreBluetooth is extremely locked-down for security and power management reasons. It's really designed for only two scenarios:

  • Connecting to a (non-privileged) GATT service of a remote peripheral
  • Making a GATT service available to remote clients

And right now bluest only supports the first of those.

I don't plan to advertise a name or service. Was just thinking control of the raw data would be a sufficient interface. It doesn't matter to me much if that's the struct you want to pack things in. If we're headed towards automatically adding the name or constructing the the ManufacturerData for the caller; idk about that.

You mostly don't have access to or the ability to manipulate the raw data of the advertisement packets with OS APIs (might be possible on Linux, definitely not the others). Even on Windows you have to construct an abstract Advertisement that the OS will serialize for you. bluest should not automatically add or construct anything, it should just make the OS APIs available.

corebluetooth made basic BLE advertisements unusable. Do you see doing acrobatics to accomplish "advertisements" via GATT? trying to receive advertisements in the background is basic need and I'd like my devices to work with corebluetooth. now come to find I'm likely going to need a custom solution to get the data...

I don't know your use-case, but if you want to work with CoreBluetooth your best bet is usually to just use GATT services.

alexmoon avatar Oct 31 '24 17:10 alexmoon

    let data = adv_data.to_custom_bytes();
    let advertisement_data = AdvertisementData {
        local_name: None,
        manufacturer_data: Some(ManufacturerData {
            company_id: 0x0059,
            data: data,
        }),
        services: vec![],
        service_data: HashMap::new(),
        tx_power_level: None,
        is_connectable: false,
    };
    let adapter_clone = adapter.clone();
    let advertise_task = task::spawn(async move {
        match adapter_clone.start_advertising(advertisement_data) {
            Ok(advert) => {
                println!("Advertising started successfully.");

                // Wait for a specified duration to allow it to advertise
                let advertise_duration = Duration::from_secs(5); // adjust duration as needed
                tokio::time::sleep(advertise_duration).await;

                // The `advert` (AdvertisingGuard) will stop advertising automatically on drop
                println!("Advertising stopped after waiting for the specified duration.");
            }
            Err(e) => {
                eprintln!("Failed to start advertising: {:?}", e);
            }
        }
    });

this is what using the api looks like -- or let me know where I've gone wrong; the other fields don't apply in my case (just a normal non-connectable advertisement).

davehorner avatar Nov 01 '24 19:11 davehorner

I know the documentation says only LocalName and UUIDsKey are supported. The code does work with AdvDataManufacturer on the my macbook. The manufacterer company_id comes through and the next two bytes are incorrect; usually F5 followed by another byte that doesn't match the data sent. Right now the advertisement creates a peripheral_manager per advertisement. I wonder if a lazy_static and shared manager might make more sense. the advertisements are working but not always... and if the machine sleeps; it doesn't continue scanning on wake.

I changed my hardware to read the data it needed from the later bytes and I'm happy to see it working. I may look at linux next. ownership; the guard is taking the ownership of the advertisement in the corebluetooth now. let me know if you have other thoughts.

its wip; there's cruft in there.

davehorner avatar Nov 22 '24 02:11 davehorner

The linux scanning I'm having trouble getting the advertisements. Both my windows and macos machines are showing advertisements regularly (about every second) and the linux machine shows the first one and then doesn't report anymore. I added the code for the advertisements but I can't test it or move forward while I'm unable to get the advertisements. the linux implementation makes the inner on Adapter pub; I'm not sure what to do with the adapter...

davehorner avatar Nov 23 '24 05:11 davehorner