eculocate

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

commit 6439bdb3df95a382896716e0de2cf35d631e8327
parent f8080a5c05d0e8b4b5ae789820b76f888a579e85
Author: Daniel Barlow <dan@telent.net>
Date:   Sun, 16 Nov 2025 21:17:50 +0000

adding a second file

Diffstat:
MCargo.lock | 2+-
MCargo.toml | 5+----
Dsrc/bin/main.rs | 305------------------------------------------------------------------------------
Dsrc/lib.rs | 1-
Asrc/main.rs | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/wibble.rs | 2++
6 files changed, 313 insertions(+), 311 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -275,7 +275,7 @@ dependencies = [ ] [[package]] -name = "ecu-logical" +name = "eculogical" version = "0.1.0" dependencies = [ "bleps", diff --git a/Cargo.toml b/Cargo.toml @@ -1,12 +1,9 @@ [package] edition = "2024" -name = "ecu-logical" +name = "eculogical" rust-version = "1.88" version = "0.1.0" -[[bin]] -name = "ecu-logical" -path = "./src/bin/main.rs" [dependencies] esp-hal = { version = "1.0.0", features = ["esp32c3", "log-04", "unstable"] } diff --git a/src/bin/main.rs b/src/bin/main.rs @@ -1,305 +0,0 @@ -#![no_std] -#![no_main] -#![deny( - clippy::mem_forget, - reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ - holding buffers for the duration of a data transfer." -)] - -use esp_backtrace as _; -use esp_println as _; - -use esp_hal::{ - timer::timg::TimerGroup, - 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 esp_hal::time::{Duration, Instant}; -use esp_radio::ble::controller::BleConnector; -use log::{warn,info}; - -extern crate alloc; - -// This creates a default app-descriptor required by the esp-idf bootloader. -// 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] -#[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()); - let mut peripherals = esp_hal::init(config); - - // XXX what is this for? - // apparently it's copyable whatever it is as no complaints - // when I call >1 function with it as param - let output_config = OutputConfig::default(); - - esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 66320); - - // handwaving: we need the timer to schedule tasks for the rtos, - // and we need the rtos so that the ble works (assumption: - // it needs to receive packets) - let timg0 = TimerGroup::new(peripherals.TIMG0); - let sw_interrupt = - esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); - esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0); - - let mut led = Output::new(peripherals.GPIO8, Level::High, output_config); - led.set_high(); - - 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 mut output = Output::new(tx, Level::High, OutputConfig::default()); - - output.set_low(); - Timer::after_millis(70).await; - output.set_high(); - Timer::after_millis(120).await; - - let mut uart = Uart::new( - periph_uart, - esp_hal::uart::Config::default() - .with_baudrate(10_400) - .with_data_bits(DataBits::_8) - ) - .expect("now I got a reason") - .with_rx(rx_peripheral) - .with_tx(tx_peripheral.reborrow()) - .into_async(); - - uart.write(b"\xfe\x04\xff\xff").expect("write failed"); - let _ = uart.flush(); - Timer::after_millis(200).await; - uart.write(b"\x72\x05\x00\xF0\x99").expect("write 2 failed"); - let _ = uart.flush(); - - loop { - info!("waiting for input"); - let mut buf = [0u8; 80]; - let bytes = uart.read_async(&mut buf[..]).await.expect("read failed"); - // if bytes>0 { led.toggle(); } - - info!("got {}", bytes); - 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/lib.rs b/src/lib.rs @@ -1 +0,0 @@ -#![no_std] diff --git a/src/main.rs b/src/main.rs @@ -0,0 +1,309 @@ +#![no_std] +#![no_main] +#![deny( + clippy::mem_forget, + reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ + holding buffers for the duration of a data transfer." +)] + +use esp_backtrace as _; +use esp_println as _; + +pub mod wibble; + +use esp_hal::{ + timer::timg::TimerGroup, + 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 esp_hal::time::{Duration, Instant}; +use esp_radio::ble::controller::BleConnector; +use log::{warn,info}; + +extern crate alloc; + +// This creates a default app-descriptor required by the esp-idf bootloader. +// 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] +#[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()); + let mut peripherals = esp_hal::init(config); + + // XXX what is this for? + // apparently it's copyable whatever it is as no complaints + // when I call >1 function with it as param + let output_config = OutputConfig::default(); + + esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 66320); + + // handwaving: we need the timer to schedule tasks for the rtos, + // and we need the rtos so that the ble works (assumption: + // it needs to receive packets) + let timg0 = TimerGroup::new(peripherals.TIMG0); + let sw_interrupt = + esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); + esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0); + + 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), + 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 mut output = Output::new(tx, Level::High, OutputConfig::default()); + + output.set_low(); + Timer::after_millis(70).await; + output.set_high(); + Timer::after_millis(120).await; + + let mut uart = Uart::new( + periph_uart, + esp_hal::uart::Config::default() + .with_baudrate(10_400) + .with_data_bits(DataBits::_8) + ) + .expect("now I got a reason") + .with_rx(rx_peripheral) + .with_tx(tx_peripheral.reborrow()) + .into_async(); + + uart.write(b"\xfe\x04\xff\xff").expect("write failed"); + let _ = uart.flush(); + Timer::after_millis(200).await; + uart.write(b"\x72\x05\x00\xF0\x99").expect("write 2 failed"); + let _ = uart.flush(); + + loop { + info!("waiting for input"); + let mut buf = [0u8; 80]; + let bytes = uart.read_async(&mut buf[..]).await.expect("read failed"); + // if bytes>0 { led.toggle(); } + + info!("got {}", bytes); + 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 @@ -0,0 +1,2 @@ + +pub fn the_answer() -> i32 { 42 }