eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 31c9b93f9cadc40d1f9154abd366ef7e0a30fff4
parent 09d7488c6e425980985c529c8ecfa32e007dfb96
Author: Daniel Barlow <dan@telent.net>
Date:   Sun, 16 Nov 2025 10:36:57 +0000

add ble peripheral

Diffstat:
MCargo.lock | 157++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
MCargo.toml | 17+++++++++++++++--
Msrc/bin/main.rs | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
3 files changed, 410 insertions(+), 30 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -117,6 +117,9 @@ name = "btuuid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0acfef8a77a02866e04f7e2ad3f4c7b32d575696c49c4bbad742b4aecb8e4a3" +dependencies = [ + "uuid", +] [[package]] name = "bumpalo" @@ -149,6 +152,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" [[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] name = "critical-section" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -267,8 +279,13 @@ name = "ecu-logical" version = "0.1.0" dependencies = [ "bleps", - "critical-section", + "bt-hci", + "embassy-executor", + "embassy-futures", + "embassy-sync 0.7.2", + "embassy-time", "embedded-io 0.7.1", + "embedded-io-async 0.7.0", "esp-alloc", "esp-backtrace", "esp-bootloader-esp-idf", @@ -277,6 +294,8 @@ dependencies = [ "esp-radio", "esp-rtos", "log", + "static_cell", + "trouble-host", ] [[package]] @@ -297,6 +316,36 @@ dependencies = [ ] [[package]] +name = "embassy-executor" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] name = "embassy-futures" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -340,6 +389,41 @@ dependencies = [ ] [[package]] +name = "embassy-time" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" +dependencies = [ + "embassy-executor-timer-queue", + "heapless 0.8.0", +] + +[[package]] name = "embedded-can" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -679,7 +763,10 @@ dependencies = [ "allocator-api2", "cfg-if", "document-features", + "embassy-executor", "embassy-sync 0.7.2", + "embassy-time-driver", + "embassy-time-queue-utils", "esp-alloc", "esp-config", "esp-hal", @@ -1367,6 +1454,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] +name = "static_cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" +dependencies = [ + "portable-atomic", +] + +[[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1468,6 +1564,39 @@ dependencies = [ ] [[package]] +name = "trouble-host" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b5d5992d07744e464b294ccf2dd6d96285bd61c267c4e0307d1f7057a8cd7f" +dependencies = [ + "bt-hci", + "embassy-futures", + "embassy-sync 0.7.2", + "embassy-time", + "embedded-io 0.6.1", + "futures", + "heapless 0.9.2", + "rand_core 0.6.4", + "static_cell", + "trouble-host-macros", + "zerocopy", +] + +[[package]] +name = "trouble-host-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb85bec3a8393c22ca1a7c25c82c2d33689ab412f3487c492fd01a033ede7c2" +dependencies = [ + "convert_case", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.110", + "uuid", +] + +[[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1486,6 +1615,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1633,3 +1768,23 @@ dependencies = [ "quote", "syn 2.0.110", ] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] diff --git a/Cargo.toml b/Cargo.toml @@ -16,6 +16,7 @@ esp-rtos = { version = "0.2.0", features = [ "esp-radio", "esp32c3", "log-04", + "embassy", ] } esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32c3", "log-04"] } @@ -25,9 +26,12 @@ bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "a5 "async", "macros", ] } -critical-section = "1.2.0" -embedded-io = "0.7.1" esp-alloc = "0.9.0" +bt-hci = { version = "0.6.0" } +embassy-executor = { version = "0.9.1", features = [] } +embassy-time = { version = "0.5.0", features = [] } +embedded-io = { version = "0.7.1", features = [] } +embedded-io-async = { version = "0.7.0", features = [] } esp-backtrace = { version = "0.18.1", features = [ "esp32c3", "panic-handler", @@ -43,6 +47,15 @@ esp-radio = { version = "0.17.0", features = [ ] } +embassy-futures = "0.1.1" +embassy-sync = { version = "0.7" } + +static_cell = "2" +trouble-host = { version = "0.5.0", features = [ + "peripheral", "gatt", "default-packet-pool-mtu-255", + "derive", "scan", +] } + [profile.dev] # Rust debug is too slow. # For debug builds always builds with some optimization diff --git a/src/bin/main.rs b/src/bin/main.rs @@ -7,20 +7,26 @@ )] use esp_backtrace as _; +use esp_println as _; + use esp_hal::{ delay::Delay, timer::timg::TimerGroup, gpio::{Level, Output, OutputConfig}, clock::CpuClock, uart::{Uart,DataBits}, - main, +// main, }; -// use esp_hal::clock::CpuClock; -// use esp_hal::main; + +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_futures::select::select; +use embassy_time::Timer; +use trouble_host::prelude::*; + // use esp_hal::time::{Duration, Instant}; -// use esp_hal::timer::timg::TimerGroup; use esp_radio::ble::controller::BleConnector; -use log::info; +use log::{warn,info}; extern crate alloc; @@ -28,10 +34,9 @@ extern crate alloc; // For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description> esp_bootloader_esp_idf::esp_app_desc!(); -#[main] -fn main() -> ! { - // generator version: 1.0.1 - +// #[main] +#[esp_rtos::main] +async fn main(_s : Spawner) { esp_println::logger::init_logger_from_env(); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); @@ -51,34 +56,43 @@ fn main() -> ! { let sw_interrupt = esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0); - let radio_init = esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller"); - let _connector = BleConnector::new(&radio_init, peripherals.BT, Default::default()); let mut led = Output::new(peripherals.GPIO8, Level::High, output_config); led.set_high(); - let delay = Delay::new(); - - let tx = peripherals.GPIO21.reborrow(); - - let mut output = Output::new(tx, Level::High, OutputConfig::default()); info!("let's go!"); led.set_low(); + let _ = join( + setup_ble(peripherals.BT), + run_ecu(peripherals.UART0, + peripherals.GPIO21.reborrow(), + peripherals.GPIO20) + ).await; +} + +async fn run_ecu(periph_uart : esp_hal::peripherals::UART0<'_>, + mut tx_peripheral : esp_hal::peripherals::GPIO21<'_>, + rx_peripheral : esp_hal::peripherals::GPIO20<'_>) { + let tx = tx_peripheral.reborrow(); + let delay = Delay::new(); + + let mut output = Output::new(tx, Level::High, OutputConfig::default()); + output.set_low(); delay.delay_millis(70); output.set_high(); delay.delay_millis(120); let mut uart = Uart::new( - peripherals.UART0, + periph_uart, esp_hal::uart::Config::default() .with_baudrate(10_400) .with_data_bits(DataBits::_8) ) .expect("now I got a reason") - .with_rx(peripherals.GPIO20) - .with_tx(peripherals.GPIO21.reborrow()); + .with_rx(rx_peripheral) + .with_tx(tx_peripheral.reborrow()); uart.write(b"\xfe\x04\xff\xff").expect("write failed"); let _ = uart.flush(); @@ -86,15 +100,213 @@ fn main() -> ! { uart.write(b"\x72\x05\x00\xF0\x99").expect("write 2 failed"); let _ = uart.flush(); - loop { - info!("waiting"); - let mut buf = [0u8; 80]; - let bytes = uart.read(&mut buf[..]).expect("read failed"); - if bytes>0 { led.toggle(); } + // loop { + // info!("waiting"); + // let mut buf = [0u8; 80]; + // let bytes = uart.read(&mut buf[..]).expect("read failed"); + // // if bytes>0 { led.toggle(); } + + // info!("got {}", bytes); + // Timer::after_secs(1).await; + // } +} + +async fn setup_ble(peripheral : esp_hal::peripherals::BT<'_>) { + let radio_init = esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller"); + let connector = BleConnector::new(&radio_init, peripheral, Default::default()).expect("connector"); + let controller: ExternalController<_, 20> = ExternalController::new(connector); + run_ble_stack(controller).await; +} - info!("got {bytes}"); - delay.delay_millis(50); +/// Max number of connections +const CONNECTIONS_MAX: usize = 1; + +/// Max number of L2CAP channels. +const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att + +// GATT Server definition +#[gatt_server] +struct Server { + battery_service: BatteryService, +} + +/// Battery service +#[gatt_service(uuid = service::BATTERY)] +struct BatteryService { + /// Battery Level + #[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])] + #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "hello", read, value = "Battery Level")] + #[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)] + level: u8, + #[characteristic(uuid = "408813df-5dd4-1f87-ec11-cdb001100000", write, read, notify)] + status: bool, +} + +async fn setup_ble(peripheral : esp_hal::peripherals::BT<'_>) { + let radio_init = esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller"); + let connector = BleConnector::new(&radio_init, peripheral, Default::default()).expect("connector"); + let controller: ExternalController<_, 20> = ExternalController::new(connector); + + // Using a fixed "random" address can be useful for testing. In real scenarios, one would + // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform). + let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); + info!("Our address = {:?}", address); + + let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new(); + let stack = trouble_host::new(controller, &mut resources).set_random_address(address); + let Host { + mut peripheral, runner, .. + } = stack.build(); + + info!("Starting advertising and GATT service"); + let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { + name: "TrouBLE", + appearance: &appearance::power_device::GENERIC_POWER_DEVICE, + })) + .unwrap(); + + let _ = join( + ble_task(runner), + async { + loop { + match advertise("Trouble Example", &mut peripheral, &server).await { + Ok(conn) => { + // set up tasks when the connection is established to a central, so they don't run when no one is connected. + let a = gatt_events_task(&server, &conn); + let b = custom_task(&server, &conn, &stack); + // run until any task ends (usually because the connection has been closed), + // then return to advertising state. + select(a, b).await; + } + Err(e) => { + // #[cfg(feature = "defmt")] + // let e = defmt::Debug2Format(&e); + panic!("[adv] error: {:?}", e); + } + } + } + }) + .await; +} + +/// This is a background task that is required to run forever alongside any other BLE tasks. +/// +/// ## Alternative +/// +/// If you didn't require this to be generic for your application, you could statically spawn this with i.e. +/// +/// ```rust,ignore +/// +/// #[embassy_executor::task] +/// async fn ble_task(mut runner: Runner<'static, SoftdeviceController<'static>>) { +/// runner.run().await; +/// } +/// +/// spawner.must_spawn(ble_task(runner)); +/// ``` +async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) { + loop { + if let Err(e) = runner.run().await { + // #[cfg(feature = "defmt")] + // let e = defmt::Debug2Format(&e); + panic!("[ble_task] error: {:?}", e); + } } +} + +/// Stream Events until the connection closes. +/// +/// This function will handle the GATT events and process them. +/// This is how we interact with read and write requests. +async fn gatt_events_task<P: PacketPool>(server: &Server<'_>, conn: &GattConnection<'_, '_, P>) -> Result<(), Error> { + let level = server.battery_service.level; + let reason = loop { + match conn.next().await { + GattConnectionEvent::Disconnected { reason } => break reason, + GattConnectionEvent::Gatt { event } => { + match &event { + GattEvent::Read(event) => { + if event.handle() == level.handle { + let value = server.get(&level); + info!("[gatt] Read Event to Level Characteristic: {:?}", value); + } + } + GattEvent::Write(event) => { + if event.handle() == level.handle { + info!("[gatt] Write Event to Level Characteristic: {:?}", event.data()); + } + } + _ => {} + }; + // This step is also performed at drop(), but writing it explicitly is necessary + // in order to ensure reply is sent. + match event.accept() { + Ok(reply) => reply.send().await, + Err(e) => warn!("[gatt] error sending response: {:?}", e), + }; + } + _ => {} // ignore other Gatt Connection Events + } + }; + info!("[gatt] disconnected: {:?}", reason); + Ok(()) +} + +/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. +async fn advertise<'values, 'server, C: Controller>( + name: &'values str, + peripheral: &mut Peripheral<'values, C, DefaultPacketPool>, + server: &'server Server<'values>, +) -> Result<GattConnection<'values, 'server, DefaultPacketPool>, BleHostError<C::Error>> { + let mut advertiser_data = [0; 31]; + let len = AdStructure::encode_slice( + &[ + AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), + AdStructure::ServiceUuids16(&[[0x0f, 0x18]]), + AdStructure::CompleteLocalName(name.as_bytes()), + ], + &mut advertiser_data[..], + )?; + let advertiser = peripheral + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &advertiser_data[..len], + scan_data: &[], + }, + ) + .await?; + info!("[adv] advertising"); + let conn = advertiser.accept().await?.with_attribute_server(server)?; + info!("[adv] connection established"); + Ok(conn) +} - // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin +/// Example task to use the BLE notifier interface. +/// This task will notify the connected central of a counter value every 2 seconds. +/// It will also read the RSSI value every 2 seconds. +/// and will stop when the connection is closed by the central or an error occurs. +async fn custom_task<C: Controller, P: PacketPool>( + server: &Server<'_>, + conn: &GattConnection<'_, '_, P>, + stack: &Stack<'_, C, P>, +) { + let mut tick: u8 = 0; + let level = server.battery_service.level; + loop { + tick = tick.wrapping_add(1); + info!("[custom_task] notifying connection of tick {}", tick); + if level.notify(conn, &tick).await.is_err() { + info!("[custom_task] error notifying connection"); + break; + }; + // read RSSI (Received Signal Strength Indicator) of the connection. + if let Ok(rssi) = conn.raw().rssi(stack).await { + info!("[custom_task] RSSI: {:?}", rssi); + } else { + info!("[custom_task] error getting RSSI"); + break; + }; + Timer::after_secs(2).await; + } }