commit 6439bdb3df95a382896716e0de2cf35d631e8327
parent f8080a5c05d0e8b4b5ae789820b76f888a579e85
Author: Daniel Barlow <dan@telent.net>
Date: Sun, 16 Nov 2025 21:17:50 +0000
adding a second file
Diffstat:
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 }