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

BLE Device disconnects when second embassy task starts #222

Closed
WJKPK opened this issue Jul 29, 2023 · 4 comments
Closed

BLE Device disconnects when second embassy task starts #222

WJKPK opened this issue Jul 29, 2023 · 4 comments

Comments

@WJKPK
Copy link

WJKPK commented Jul 29, 2023

Problem: The BLE device disconnects when the second embassy task starts.

Hardware: WeAct ESP32-C3FH4 & Android 11 Phone with nrfConnect app.

Code: The code I am using can be found at: Thermo-esp Repo

Logs: Logs from ESP and nrfConnect can be found here: Logs

Description: I am creating a simple hotplate with two-state control, predefined heating modes, and a BLE connection. To implement this, I decided to use the embassy and created a separate task to control the hotplate. The control task does not perform any heavy lifting; it waits, reads data from the MAX6675 thermocouple frontend via SPI, performs some easy calculations, and toggles a pin. However, I have observed that when the control task is unblocked, it seems like the BLE stack task gets starved. The pooling of events appears to be unpredictable, with some events stopping, and then suddenly all events being processed at once with a DisconnectComplete event at the end. This behavior is clearly visible in the logs. After disconnect, I am still unable to reconnect to the device.

@bjoernQ
Copy link
Contributor

bjoernQ commented Aug 7, 2023

That's an interesting project!

Seems like something is blocking (for too long) and the BLE stack doesn't get a chance to process the HCI packets.

I used the async_ble example and just added another (very simple) task:

#[embassy_executor::task]
async fn second_task() {
    loop {
        Timer::after(Duration::from_secs(1)).await;
        println!("ping");
    }
}

And launch the task together with the BLE task (spawner.spawn(second_task()).ok();)

I see the output printed from the second_task and can interact with the device via BLE without issues.

Maybe you could try to simplify your second task step by step until to get back to a "working" state.

@WJKPK
Copy link
Author

WJKPK commented Aug 7, 2023

Thank you very much for your answer!
I attempted to address the problem by implementing the following simplified task

#[embassy_executor::task]
pub async fn run(thermo: ThermoControl<ThermoToggler>) {
    loop {
        let requested_state = SHARED.wait().await;
        let mut i = 0;
        loop {
            i += 1;
            TEMPERATURE_SIGNAL.signal(i);
            Timer::after(Duration::from_secs(1)).await;
        }
    }
}

in order to signal notifier closure called by AttributeServer

let mut notifier = async || {
    let temperature = TEMPERATURE_SIGNAL.wait().await;
    let mut data = [0u8; 2];
    let temperature: &[u8] = unsafe { any_as_u8_slice(&temperature)};
    data.copy_from_slice(temperature);
    NotificationData::new(temperature_handle, &data)
};
srv.run(&mut notifier).await.unwrap();

Despite implementing this approach, the problem still persists - the symptoms are the same as before

@bjoernQ
Copy link
Contributor

bjoernQ commented Aug 8, 2023

I tried to resemble what your code does by modifying the async_ble.rs example to use a second task and using Signal ... still works for me

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(async_closure)]

use core::cell::RefCell;

use bleps::{
    ad_structure::{
        create_advertising_data, AdStructure, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE,
    },
    async_attribute_server::AttributeServer,
    asynch::Ble,
    attribute_server::NotificationData,
    gatt,
};
use embassy_executor::Executor;
use embassy_executor::_export::StaticCell;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use embassy_time::{Duration, Timer};
use embedded_hal_async::digital::Wait;
use esp_backtrace as _;
use esp_println::{logger::init_logger, println};
use esp_wifi::{
    ble::controller::asynch::BleConnector, initialize, EspWifiInitFor, EspWifiInitialization,
};
use examples_util::hal;
use examples_util::BootButton;
use hal::{
    clock::{ClockControl, CpuClock},
    embassy,
    peripherals::*,
    prelude::*,
    radio::Bluetooth,
    timer::TimerGroup,
    Rng, Rtc, IO,
};

static SIGNAL: Signal<CriticalSectionRawMutex, u32> = Signal::new();

#[embassy_executor::task]
async fn second_task(mut pin: BootButton) {
    let mut i = 0;

    loop {
        pin.wait_for_rising_edge().await.unwrap();
        //Timer::after(Duration::from_millis(3000)).await;
        println!("ping");
        SIGNAL.signal(i);
        i += 1;
    }
}

#[embassy_executor::task]
async fn run(init: EspWifiInitialization, mut bluetooth: Bluetooth) {
    let connector = BleConnector::new(&init, &mut bluetooth);
    let mut ble = Ble::new(connector, esp_wifi::current_millis);
    println!("Connector created");

    loop {
        println!("{:?}", ble.init().await);
        println!("{:?}", ble.cmd_set_le_advertising_parameters().await);
        println!(
            "{:?}",
            ble.cmd_set_le_advertising_data(
                create_advertising_data(&[
                    AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
                    AdStructure::ServiceUuids16(&[Uuid::Uuid16(0x1809)]),
                    AdStructure::CompleteLocalName(examples_util::SOC_NAME),
                ])
                .unwrap()
            )
            .await
        );
        println!("{:?}", ble.cmd_set_le_advertise_enable(true).await);

        println!("started advertising");

        let mut rf = |_offset: usize, data: &mut [u8]| {
            data[..20].copy_from_slice(&b"Hello Bare-Metal BLE"[..]);
            17
        };
        let mut wf = |offset: usize, data: &[u8]| {
            println!("RECEIVED: {} {:x?}", offset, data);
        };

        let mut wf2 = |offset: usize, data: &[u8]| {
            println!("RECEIVED: {} {:x?}", offset, data);
        };

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

        gatt!([service {
            uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38",
            characteristics: [
                characteristic {
                    uuid: "937312e0-2354-11eb-9f10-fbc30a62cf38",
                    read: rf,
                    write: wf,
                },
                characteristic {
                    uuid: "957312e0-2354-11eb-9f10-fbc30a62cf38",
                    write: wf2,
                },
                characteristic {
                    name: "my_characteristic",
                    uuid: "987312e0-2354-11eb-9f10-fbc30a62cf38",
                    notify: true,
                    read: rf3,
                    write: wf3,
                },
            ],
        },]);

        let mut srv = AttributeServer::new(&mut ble, &mut gatt_attributes);

        let mut notifier = async || {
            let x = SIGNAL.wait().await;
            let mut data = [0u8; 13];
            data.copy_from_slice(b"Notification0");
            data[data.len() - 1] = (x as u8) + b'0';
            NotificationData::new(my_characteristic_handle, &data)
        };

        srv.run(&mut notifier).await.unwrap();
    }
}

static EXECUTOR: StaticCell<Executor> = StaticCell::new();

#[entry]
fn main() -> ! {
    init_logger(log::LevelFilter::Info);

    let peripherals = Peripherals::take();

    let system = examples_util::system!(peripherals);
    let mut peripheral_clock_control = system.peripheral_clock_control;
    let clocks = examples_util::clocks!(system);
    examples_util::rtc!(peripherals);

    let timer = examples_util::timer!(peripherals, clocks, peripheral_clock_control);
    let init = initialize(
        EspWifiInitFor::Ble,
        timer,
        Rng::new(peripherals.RNG),
        system.radio_clock_control,
        &clocks,
    )
    .unwrap();

    let button = examples_util::boot_button!(peripherals);

    // Async requires the GPIO interrupt to wake futures
    hal::interrupt::enable(
        hal::peripherals::Interrupt::GPIO,
        hal::interrupt::Priority::Priority1,
    )
    .unwrap();

    let bluetooth = examples_util::get_bluetooth!(peripherals);

    let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks, &mut peripheral_clock_control);
    embassy::init(&clocks, timer_group0.timer0);
    let executor = EXECUTOR.init(Executor::new());
    executor.run(|spawner| {
        spawner.spawn(run(init, bluetooth)).ok();
        spawner.spawn(second_task(button)).ok();
    });
}

@WJKPK
Copy link
Author

WJKPK commented Aug 9, 2023

Thank you! Thanks to you, I realized that I hadn't enabled async and ble features while building the software. When I incorporated these, everything worked as expected.

@WJKPK WJKPK closed this as completed Aug 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

2 participants