commit 93dc2055cf4791bb3b9f561a0e369faf947d1dcb
parent 996abe6efb4e97ba0f9f26d51fad54466110a1bd
Author: Daniel Barlow <dan@telent.net>
Date: Sun, 16 Nov 2025 21:28:19 +0000
move BLE into its own file
Diffstat:
| M | src/main.rs | | | 207 | ++----------------------------------------------------------------------------- |
| M | src/wibble.rs | | | 200 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 203 insertions(+), 204 deletions(-)
diff --git a/src/main.rs b/src/main.rs
@@ -16,18 +16,13 @@ use esp_hal::{
gpio::{Level, Output, OutputConfig},
clock::CpuClock,
uart::{Uart,DataBits},
-// main,
};
use embassy_executor::Spawner;
-use embassy_futures::join::join;
-use embassy_futures::select::select;
use embassy_time::Timer;
-use trouble_host::prelude::*;
+use embassy_futures::join::join;
-// use esp_hal::time::{Duration, Instant};
-use esp_radio::ble::controller::BleConnector;
-use log::{warn,info};
+use log::{info};
extern crate alloc;
@@ -61,13 +56,11 @@ async fn main(_s : Spawner) {
let mut led = Output::new(peripherals.GPIO8, Level::High, output_config);
led.set_high();
- info!("thing {}", wibble::the_answer());
-
info!("let's go!");
led.set_low();
let _ = join(
- setup_ble(peripherals.BT),
+ wibble::run_ble(peripherals.BT),
run_ecu(peripherals.UART0,
peripherals.GPIO21.reborrow(),
peripherals.GPIO20)
@@ -113,197 +106,3 @@ async fn run_ecu(periph_uart : esp_hal::peripherals::UART0<'_>,
Timer::after_secs(1).await;
}
}
-
-
-/// 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)
-}
-
-/// 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;
- }
-}
diff --git a/src/wibble.rs b/src/wibble.rs
@@ -1,2 +1,202 @@
+use embassy_time::Timer;
+use trouble_host::prelude::*;
+use esp_radio::ble::controller::BleConnector;
+use log::{warn,info};
+use embassy_futures::join::join;
+use embassy_futures::select::select;
pub fn the_answer() -> i32 { 42 }
+
+
+/// 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,
+}
+
+pub async fn run_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)
+}
+
+/// 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;
+ }
+}