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

Service discovery request resolves to empty list #123

Open
peteskubiak opened this issue Sep 24, 2024 · 10 comments
Open

Service discovery request resolves to empty list #123

peteskubiak opened this issue Sep 24, 2024 · 10 comments

Comments

@peteskubiak
Copy link

This issue was identified running the esp32 example on an ESP32-C3-DevKit-RUST-1 board @ 29dabe7 (the esp32 example is currently broken beyond this point, see #122).

Attempts to discover services on the device take a long time to resolve, and when they do they resolve to an empty list.

I have tested this against examples in two different adapter crates running on Ubuntu 20.04:

The results from these tests are the same. When the Linux application discover services on the device, I see the following trace from the device:

INFO - [adv] connection established
INFO - [gatt] Read event on Characteristic { cccd_handle: Some(35), handle: 34 }        // NOTE: This line doesn't always appear!
ERROR - [gatt] Error processing GATT events: NotFound
INFO - [adv] notifying connection of tick 1
INFO - [adv] notifying connection of tick 2
INFO - [adv] notifying connection of tick 3
(continues)

Service UUIDs can be read from the advertising data, but no service data is associated with them.

Running a bleps-based application on the same dev board, I can successfully discover services with both blteplug and bluest.

Confusingly however, I can connect to the device with the NRFConnect app on my phone and this does seem to be able to resolve the services successfully. I don't know how NRFConnect goes about service discovery, but I don't see either the Read event on Characteristic { cccd_handle: Some(35), handle: 34 } line or the Error processing GATT events: NotFound line. Instead when NRFConnect connects I just see:

INFO - [adv] connection established
INFO - [adv] notifying connection of tick 1
INFO - [adv] notifying connection of tick 2
INFO - [adv] notifying connection of tick 3
INFO - [adv] notifying connection of tick 4
@lulf
Copy link
Member

lulf commented Sep 24, 2024

Several issues are fixed, so we should try to get main branch working on esp32 first.

@lulf
Copy link
Member

lulf commented Sep 24, 2024

For the record, I tried to run the btleplug example using the nrf-sdc example, so I think this issue is fixed on main:

Peripheral "Trouble" is connected: false
Connecting to peripheral "Trouble"...
Now connected (true) to peripheral "Trouble"...
Discover peripheral "Trouble" services...
Service UUID 0000180f-0000-1000-8000-00805f9b34fb, primary: true
  Characteristic { uuid: 00002a19-0000-1000-8000-00805f9b34fb, service_uuid: 0000180f-0000-1000-8000-00805f9b34fb, properties: CharPropFlags(READ | NOTIFY), descriptors: {Descriptor { uuid: 00002902-0000-1000-8000-00805f9b34fb, service_uuid: 0000180f-0000-1000-8000-00805f9b34fb, characteristic_uuid: 00002a19-0000-1000-8000-00805f9b34fb }} }
Disconnecting from peripheral "Trouble"...

I will re-run that once having esp32 working.

@lulf
Copy link
Member

lulf commented Sep 25, 2024

@peteskubiak Now with #124 merged, could you retry your testing?

@peteskubiak
Copy link
Author

peteskubiak commented Sep 25, 2024

Sure thing. First off, I can confirm the esp32 example is now working.

Blteplug test

Trace from the example shows something slightly different:

INFO - [adv] connection established
INFO - [gatt] Read event on Characteristic { cccd_handle: None, handle: 3 }
INFO - [adv] notifying connection of tick 1
INFO - [adv] notifying connection of tick 2

So the error message has disappeared and the read event report is now reporting different values for cccd_handle and handle.
As for blteplug, it connects and attempts to discover services, but doesn't find any:

Peripheral "Trouble" is connected: false
Connecting to peripheral "Trouble"...
Now connected (true) to peripheral "Trouble"...
Discover peripheral "Trouble" services...
Disconnecting from peripheral "Trouble"...

Bluest

The code I've put together to exercise the bluest adapter performs the following tasks:

  1. Scan for devices until "Trouble" device is found
  2. Report service UUIDs and service data from advertising packet
  3. Connect to "Trouble" device
  4. Attempt service discovery

The trace from my code is the following:

2024-09-25T11:32:05.245597Z  INFO ble: starting scan
2024-09-25T11:32:05.636852Z  INFO ble: scan started
2024-09-25T11:32:05.647892Z  INFO ble: Trouble: [00001800-0000-1000-8000-00805f9b34fb, 0000180f-0000-1000-8000-00805f9b34fb]; {}
2024-09-25T11:32:05.648029Z  INFO ble: Found trouble!
2024-09-25T11:32:16.575876Z  INFO ble: Conneted to trouble!
2024-09-25T11:32:16.584583Z  INFO ble: Discovering services...
2024-09-25T11:32:18.295256Z  INFO ble: Services discovered: []

The trace from the TrouBLE example is as follows:

INFO - [adv] advertising
INFO - [adv] connection established
INFO - [gatt] Read event on Characteristic { cccd_handle: Some(35), handle: 34 }
INFO - [gatt] Read event on Characteristic { cccd_handle: None, handle: 3 }
INFO - [gatt] Read event on Characteristic { cccd_handle: None, handle: 5 }
INFO - [gatt] Read event on Characteristic { cccd_handle: None, handle: 3 }
INFO - [adv] notifying connection of tick 1
INFO - [adv] notifying connection of tick 2

Again, the error is no longer reported, but no services are discovered.

NRF Connect

NRF Connect is still able to successfully connect and report all expected services and subscribe to the notify characteristic. As before, there is no Read event on Characteristic {...} trace when connecting with NRF Connect:

INFO - [adv] advertising
INFO - [adv] connection established
INFO - [adv] notifying connection of tick 1
INFO - [adv] notifying connection of tick 2
INFO - [adv] notifying connection of tick 3
INFO - [adv] notifying connection of tick 4

@lulf
Copy link
Member

lulf commented Sep 25, 2024

@peteskubiak Thanks for testing!

I only tried btleplug with esp32, and for me it does discover all the services and characteristics as when I tested nrf-sdc. I wonder if this could be related to bluez version on the system, could you share Linux version, version of bluez etc? One thing to keep in mind, bluez does some caching of earlier discoveries, so you might want to try to change the device id to something else than 'Trouble' in case it's caching some old state.

Regarding the Read Event present or not, this event is only returned when a 'read' is requested. In the nrf connect app, you have to press 'read characteristic' icon to trigger a read event. Subscribing to a value will not trigger a a read event, so this is expected. The other examples probably do explicit reads.

Would you mind sharing your 'bluest' code so I can try to reproduce it?

@peteskubiak
Copy link
Author

peteskubiak commented Sep 25, 2024

Interesting! Yes my OS version is Ubuntu 20.04.6 LTS and bluez version is 5.53. It could be related to OS/bluez version; the fact that it can discover services on a bleps-based device but not TrouBLE might help to track down the source of the issue...

Testing btleplug and bluest with an updated device ID each time have the same results: no services.

Here's my bluest code:

use bluest::Adapter;
use futures::StreamExt;
use std::error::Error;
use tracing::info;
use tracing::metadata::LevelFilter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    use tracing_subscriber::prelude::*;
    use tracing_subscriber::{fmt, EnvFilter};

    tracing_subscriber::registry()
        .with(fmt::layer())
        .with(
            EnvFilter::builder()
                .with_default_directive(LevelFilter::INFO.into())
                .from_env_lossy(),
        )
        .init();

    let adapter = Adapter::default()
        .await
        .ok_or("Bluetooth adapter not found")?;
    adapter.wait_available().await?;

    info!("starting scan");
    let mut scan = adapter.scan(&[]).await?;
    info!("scan started");
    let mut trouble = None;
    let mut services = Vec::new();
    while trouble.is_none() {
        if let Some(discovered_device) = scan.next().await {
            info!(
                "{}{}: {:?}; {:?}",
                discovered_device
                    .device
                    .name()
                    .as_deref()
                    .unwrap_or("(unknown)"),
                discovered_device
                    .rssi
                    .map(|x| format!(" ({}dBm)", x))
                    .unwrap_or_default(),
                discovered_device.adv_data.services,
                discovered_device.adv_data.service_data
            );
            // NOTE: Doing simple string comparison to identity device
            if discovered_device.device.name().unwrap() == *"Trouble3" {
                trouble = Some(discovered_device.device);
                services.extend(discovered_device.adv_data.services);
                info!("Found trouble!");
            }
        }
    }
    adapter.connect_device(trouble.as_ref().unwrap()).await?;
    info!("Connected to trouble!");

    info!("Discovering services...");
    info!(
        "Services discovered: {:?}",
        trouble.as_ref().unwrap().services().await?
    );

    adapter.disconnect_device(&trouble.unwrap()).await?;

    Ok(())
}

@lulf
Copy link
Member

lulf commented Sep 25, 2024

@peteskubiak Thanks! What seems to happen is that bluez aborts the connection due to the supervision timeout (bluez sets this to 420ms). I've used the bluetoothctl tool manually to connect, and I'm getting a le-connection-abort-by-local.

Sometimes it gets a bit further and starts making GATT requests, but ultimately it sends up disconnecting sometimes.

I've tried the following as well:

  • Running the wifi_ble example in esp-hal repo, same behavior
  • Running the wifi_embassy_ble example in esp-hal repo, same behavior
  • Running the nrf-sdc examples, this works every time

So I think this might not be related to trouble, but some timing handling in the esp32 hci controller and/or the hci integration, since the bleps examples exhibit the same behavior. Do you have a bleps example that works for you? I've been running the examples from main in esp-hal, maybe you could try those as well.

@lulf
Copy link
Member

lulf commented Sep 25, 2024

I tried on a different machine, debian based, and in that case all the esp examples worked, all services discovered etc. If you have a bleps-based example that works for you, I'd be interested to test it on these two systems to understand more what's different.

You can also try to increase the supervision timeout to see if that helps, echo -n 200 > /sys/kernel/debug/bluetooth/hci0/supervision_timeout

@peteskubiak
Copy link
Author

Ahh that would explain the long pause when attempting to connect to the TrouBLE example. Here is the hacked together bleps example I've been using - I've stuck it into one file for easy sharing, please excuse the scrappy code!

//! Embassy BLE Example
//!

#![no_std]
#![no_main]

use bleps::ad_structure::{
    create_advertising_data, AdStructure, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE,
};
use bleps::async_attribute_server::AttributeServer;
use bleps::asynch::Ble;
use bleps::att::Uuid;
use bleps::attribute_server::{NotificationData, WorkResult};
use bleps::event::EventType;
use bleps::no_rng::NoRng;
use bleps::PollResult;
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::clock::{ClockControl, Clocks};
use esp_hal::peripherals::{Peripherals, BT, RADIO_CLK, SYSTIMER};
use esp_hal::rng::Rng;
use esp_hal::system::SystemControl;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::timer::{systimer, ErasedTimer, PeriodicTimer};
use esp_hal_embassy::main;
use esp_wifi::ble::controller::asynch::BleConnector;
use esp_wifi::{initialize, EspWifiInitFor};
use log::info;

#[global_allocator]
static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();

#[main]
async fn main(_spawner: Spawner) -> ! {
    // Initialize the logger
    esp_println::logger::init_logger(log::LevelFilter::Info);

    init_heap();

    // Initialize the hardware
    let b = Board::init();

    // Initialize the BLE stack
    let ble_init = initialize(
        EspWifiInitFor::Ble,
        b.timer,
        b.rng,
        b.radio_clocks,
        &b.clocks,
    )
    .unwrap();
    let connector = BleConnector::new(&ble_init, b.ble);
    let mut ble = Ble::new(connector, esp_wifi::current_millis);

    let service_uuids = &[bleps::att::Uuid::Uuid16(0x1809)];

    let mut rf3 = |_offset: usize, data: &mut [u8]| {
        data[..5].copy_from_slice(&b"Hola!"[..]);
        5
    };
    let mut wf3 = |offset: usize, data: &[u8]| {
        info!("RECEIVED: Offset {}, data {:?}", offset, data);
    };

    let mut my_descriptor_read_function = |_offset: usize, data: &mut [u8]| {
        data[..5].copy_from_slice(&b"Desc!"[..]);
        5
    };

    let mut notify = |enabled: bool| {
        info!("enabled = {enabled}");
    };

    let mut rf = rf3;

    let mut notify2 = notify;

    bleps::gatt!([service {
        uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38",
        characteristics: [
            characteristic {
                name: "my_characteristic",
                description: "Sine wave notifier",
                descriptors: [descriptor {
                    uuid: "dfe57a9f-4495-45ba-9e02-df1a010b618c",
                    read: my_descriptor_read_function,
                },],
                uuid: "987312e0-2354-11eb-9f10-fbc30a62cf38",
                notify: true,
                read: rf3,
                write: wf3,
                notify_cb: notify,
            },
            characteristic {
                name: "triangle_wave",
                description: "Triangle wave notifier",
                uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38",
                notify: true,
                read: rf,
                notify_cb: notify2,
            }
        ],
    },]);

    advertise(&mut ble, service_uuids, "BLEPS DEMO").await;
    let mut rng = NoRng;
    let mut srv = AttributeServer::new(&mut ble, &mut gatt_attributes, &mut rng);
    let mut counter = 0.0;
    let mut tri_counter: f32 = 1.0;
    let mut tri_adder = 1.0;

    loop {
        let mut cccd = [0u8; 1];
        srv.get_characteristic_value(my_characteristic_notify_enable_handle, 0, &mut cccd);
        if cccd[0] == 1 {
            match srv
                .do_work_with_notification(Some(NotificationData::new(
                    my_characteristic_handle,
                    &libm::sinf(counter).to_le_bytes(),
                )))
                .await
            {
                Ok(res) => match res {
                    WorkResult::DidWork => {
                        info!("Notify {counter}");
                    }
                    WorkResult::GotDisconnected => {
                        info!("Disconnected!");
                    }
                },
                Err(_) => todo!(),
            }
            counter += 0.1;
            Timer::after(Duration::from_millis(200)).await;
        } else {
            let _ = srv.do_work().await;
        }

        let mut cccd = [0u8; 1];
        srv.get_characteristic_value(triangle_wave_notify_enable_handle, 0, &mut cccd);
        if cccd[0] == 1 {
            match srv
                .do_work_with_notification(Some(NotificationData::new(triangle_wave_handle, {
                    if tri_counter as i32 % 25 == 0 {
                        tri_adder = -tri_adder;
                    }
                    &tri_counter.to_le_bytes()
                })))
                .await
            {
                Ok(res) => {
                    if let WorkResult::GotDisconnected = res {
                        info!("Disconnected (TW)")
                    }
                }
                Err(_) => todo!(),
            }
            tri_counter += tri_adder;
        }
    }
}

fn init_heap() {
    const HEAP_SIZE: usize = 32 * 1024;
    static mut HEAP: core::mem::MaybeUninit<[u8; HEAP_SIZE]> = core::mem::MaybeUninit::uninit();

    unsafe {
        ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE);
    }
}

async fn advertise(
    ble: &mut Ble<BleConnector<'static>>,
    service_uuids: &[Uuid],
    name: &str,
) -> bool {
    info!("{:?}", ble.init().await);
    info!("{:?}", ble.cmd_set_le_advertising_parameters().await);
    info!(
        "{:?}",
        ble.cmd_set_le_advertising_data(
            create_advertising_data(&[
                AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
                AdStructure::ServiceUuids16(service_uuids),
                AdStructure::CompleteLocalName(name),
            ])
            .unwrap()
        )
        .await
    );
    info!("{:?}", ble.cmd_set_le_advertise_enable(true).await);
    info!("started advertising");
    loop {
        if let Some(PollResult::Event(event)) = ble.poll().await {
            // info!("{:?}", event);
            match event {
                EventType::DisconnectComplete { reason, .. } => {
                    info!("Disconnected: {:?}", reason);
                    break false;
                }
                EventType::ConnectionComplete { .. } => {
                    info!("Connected BLE!");
                    break true;
                }
                EventType::LongTermKeyRequest { .. } => unimplemented!(),
                other => info!("Unhandled event: {:?}", other),
            }
        };
    }
}

struct Board {
    pub timer: PeriodicTimer<'static, ErasedTimer>,
    pub clocks: Clocks<'static>,
    pub rng: Rng,
    pub radio_clocks: RADIO_CLK,
    pub ble: BT,
}

impl Board {
    pub fn init() -> Self {
        let p = Peripherals::take();
        let system = SystemControl::new(p.SYSTEM);
        let clocks = ClockControl::max(system.clock_control).freeze();

        let timg0 = TimerGroup::new(p.TIMG0, &clocks);
        let timer0: ErasedTimer = timg0.timer0.into();
        let timer = PeriodicTimer::new(timer0);

        Board::init_embassy(p.SYSTIMER, &clocks);

        Self {
            timer,
            clocks,
            rng: Rng::new(p.RNG),
            radio_clocks: p.RADIO_CLK,
            ble: p.BT,
        }
    }

    /// Initialize the embassy runtime
    fn init_embassy(systimer: SYSTIMER, clocks: &Clocks) {
        info!("Initializing embassy runtime");
        let systimer = systimer::SystemTimer::new(systimer);
        let alarms = systimer.split::<esp_hal::timer::systimer::Target>();
        esp_hal_embassy::init(clocks, alarms.alarm0);
    }
}

Please note - the device will not go back to advertising on disconnect.

Discovery of services seems to work fine with both Btleplug and Bluest with default bluez supervision timeout using this demo. I notice the connection happens considerably more quickly in this case too.

I'm about out of time today but I wonder if bleps is using a different version of esp-hal or one of the other crates, and the controller is being configured differently? I'll have at look at this tomorrow if still relevant.

I haven't had a chance yet to try out changing the supervision timeout, but I'll try that tomorrow too.

@lulf
Copy link
Member

lulf commented Sep 26, 2024

Which versions/revisions of esp crates are you using?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants