Initial working exampel of test console

This commit is contained in:
2024-08-12 14:04:09 -04:00
parent 143b3b057b
commit 3633f636c6
5 changed files with 336 additions and 70 deletions

View File

@ -18,7 +18,7 @@ debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"
[features]
default = ["std", "embassy", "esp-idf-svc/native"]
default = ["std", "esp-idf-svc/native"]
pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
@ -33,6 +33,17 @@ esp-idf-svc = { version = "0.49", default-features = false }
esp32-nimble = "0.7.0"
esp-idf-hal = "0.44.1"
esp-idf-sys = "0.35.0"
embedded-cli = "0.2.1"
embedded-io = "0.6.1"
anyhow = "1.0.86"
futures-lite = "2.3.0"
async-executor = "1.13.0"
[build-dependencies]
embuild = "0.32.0"
# Cargo udeps can check for unused dependencies; but if there is a dependency we want it to ignore put it here.
[package.metadata.cargo-udeps.ignore]
#normal = []
#development = []
#build = []

View File

@ -1,9 +1,13 @@
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
# USB drivers from the ESP IDF, to enable stdin. First one is for sx chips, second is for cx chips (risc)
CONFIG_ESP_CONSOLE_USB_CDC=y # for s, s3, etc. chips
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y # For RISC based c3 chips
# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
#CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_HZ=1000
# Workaround for https://github.com/espressif/esp-idf/issues/7631
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n

View File

@ -0,0 +1,83 @@
// Example for reference; not ready to use at all.
use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEDevice, NimbleProperties};
use esp_idf_hal::delay::FreeRtos;
use esp_idf_sys as _;
fn main() {
esp_idf_sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
// Take ownership of device
let ble_device = BLEDevice::take();
// Obtain handle for peripheral advertiser
let ble_advertiser = ble_device.get_advertising();
// Configure Device Security
ble_device
.security()
.set_auth(AuthReq::all())
.set_passkey(123456)
.set_io_cap(SecurityIOCap::DisplayOnly)
.resolve_rpa();
// Obtain handle for server
let server = ble_device.get_server();
// Define server connect behaviour
server.on_connect(|server, clntdesc| {
// Print connected client data
println!("{:?}", clntdesc);
// Update connection parameters
server
.update_conn_params(clntdesc.conn_handle(), 24, 48, 0, 60)
.unwrap();
});
// Define server disconnect behaviour
server.on_disconnect(|_desc, _reason| {
println!("Disconnected, back to advertising");
});
// Create a service with custom UUID
let my_service = server.create_service(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1"));
// Create a characteristic to associate with created service
let my_service_characteristic = my_service.lock().create_characteristic(
uuid128!("681285a6-247f-48c6-80ad-68c3dce18585"),
NimbleProperties::READ | NimbleProperties::READ_ENC,
);
// Modify characteristic value
my_service_characteristic.lock().set_value(b"Start Value");
// Configure Advertiser Data
ble_advertiser
.lock()
.set_data(
BLEAdvertisementData::new()
.name("ESP32 Server")
.add_service_uuid(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1")),
)
.unwrap();
// Start Advertising
ble_advertiser.lock().start().unwrap();
// (Optional) Print dump of local GATT table
// server.ble_gatts_show_local();
// Init a value to pass to characteristic
let mut val = 0;
loop {
FreeRtos::delay_ms(1000);
my_service_characteristic.lock().set_value(&[val]).notify();
val = val.wrapping_add(1);
}
}

View File

@ -1,81 +1,54 @@
//use esp_idf_hal::peripherals::Peripherals;
use esp_idf_svc::timer::EspTaskTimerService;
use core::time::Duration;
use log::{info, error};
use anyhow::Result;
use async_executor::Executor;
use futures_lite::future;
use esp32_nimble::{enums::*, uuid128, BLEAdvertisementData, BLEDevice, NimbleProperties};
use esp_idf_hal::delay::FreeRtos;
use esp_idf_sys as _;
mod test_console;
fn main() {
esp_idf_sys::link_patches();
// Bind the log crate to the ESP Logging facilities
// Do basic initialization
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();
info!("Logging initialized");
// Take ownership of device
let ble_device = BLEDevice::take();
//let peripherals = Peripherals::take().unwrap();
// Obtain handle for peripheral advertiser
let ble_advertiser = ble_device.get_advertising();
match future::block_on(main_loop()) {
Ok(_) => {error!("Exited main loop normally, but this should be impossible.")}
Err(e) => {error!("Exited main loop with error {}", e)}
};
// Configure Device Security
ble_device
.security()
.set_auth(AuthReq::all())
.set_passkey(123456)
.set_io_cap(SecurityIOCap::DisplayOnly)
.resolve_rpa();
}
// Obtain handle for server
let server = ble_device.get_server();
// Define server connect behaviour
server.on_connect(|server, clntdesc| {
// Print connected client data
println!("{:?}", clntdesc);
// Update connection parameters
server
.update_conn_params(clntdesc.conn_handle(), 24, 48, 0, 60)
.unwrap();
});
// Define server disconnect behaviour
server.on_disconnect(|_desc, _reason| {
println!("Disconnected, back to advertising");
});
// Create a service with custom UUID
let my_service = server.create_service(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1"));
// Create a characteristic to associate with created service
let my_service_characteristic = my_service.lock().create_characteristic(
uuid128!("681285a6-247f-48c6-80ad-68c3dce18585"),
NimbleProperties::READ | NimbleProperties::READ_ENC,
);
// Modify characteristic value
my_service_characteristic.lock().set_value(b"Start Value");
// Configure Advertiser Data
ble_advertiser
.lock()
.set_data(
BLEAdvertisementData::new()
.name("ESP32 Server")
.add_service_uuid(uuid128!("9b574847-f706-436c-bed7-fc01eb0965c1")),
)
.unwrap();
// Start Advertising
ble_advertiser.lock().start().unwrap();
// (Optional) Print dump of local GATT table
// server.ble_gatts_show_local();
// Init a value to pass to characteristic
let mut val = 0;
async fn test_loop() -> Result<()> {
let timer_service = EspTaskTimerService::new()?;
let mut async_timer = timer_service.timer_async()?;
loop {
FreeRtos::delay_ms(1000);
my_service_characteristic.lock().set_value(&[val]).notify();
val = val.wrapping_add(1);
async_timer.after(Duration::from_secs(2)).await?;
}
}
async fn main_loop() -> Result<()> {
info!("Entering main loop");
let executor = Executor::new();
let mut tasks:Vec<_> = Vec::new();
//Queueu up our async tasks
tasks.push(executor.spawn(test_console::start_cli()));
tasks.push(executor.spawn(test_loop()));
//Once we have all our tasks, await on them all to run them in parallel.
for task in tasks {
executor.run(task).await?;
}
info!("exiting main loop");
Ok(())
}

View File

@ -0,0 +1,195 @@
// === Constants ==============================================================
const MAX_COMMAND_SIZE: usize = 64; // in bytes; we want to test wifi ssid which
// can be 32 bytes.
const MAX_HISTORY_SIZE: usize = 128; // in bytes
const SLEEP_TIME_MS: u64 = 10; // should be > 1/CONFIG_FREERTOS_HZ in sdkconfig
// currently this is 1ms, which is also about as
// long as it would take uart buffers to overflow
// at maximum rate. But this is intended for
// human input only. It's noticeably laggy at 50.
// ============================================================================
use ::{
anyhow::Result,
embedded_cli::{
cli::{CliBuilder, CliHandle},
Command,
},
esp_idf_svc::timer::EspTaskTimerService,
std::{
convert::Infallible,
io::{stdin, stdout, Read, Write},
//time::Duration, // could also use core::time::Duration?
},
core::time::Duration,
log::{info, warn},
};
#[derive(Command)]
pub enum Menu<'a> {
/// Send a button press: Up
ButtonUp,
/// Send a button press: Down
ButtonDown,
/// Send a button press: Auto toggle on/off
ButtonAuto,
/// Send a button press: Pair/forget
ButtonPair,
/// Send a bluetooth characteristic: Up
BluetoothUp {
/// 0 for not pressed, 1 for pressed
data: u8,
},
/// Send a bluetooth characteristic: Down
BluetoothDown {
/// 0 for not pressed, 1 for pressed
data: u8,
},
/// Send a bluetooth characteristic: Stop
BluetoothStop {
/// 0 for not pressed, 1 for pressed
data: u8,
},
/// Send a bluetooth characteristic: Learn
BluetoothLearn {
/// 0 for not pressed, 1 for pressed
data: u8,
},
/// Send a bluetooth characteristic: Auto
BluetoothAuto {
/// 0 for not pressed, 1 for pressed
data: u8,
},
/// Send a bluetooth characteristic: Limits
BluetoothTopLimit { data: u8 },
/// Send a bluetooth characteristic: Limits
BluetoothBottomLimit { data: u8 },
/// Send a bluetooth characteristic: Wifi SSID
BluetoothWifiSsid { ssid: &'a str },
/// Send a bluetooth characteristic: Wifi Password
BluetoothWifiPassword { wifipass: &'a str },
}
pub fn process_menu(
cli: &mut CliHandle<'_, SimpleWriter, Infallible>,
command: Menu<'_>,
) -> Result<(), Infallible> {
match command {
Menu::ButtonUp => {
cli.writer().write_str("TODO: simulate button")?;
}
Menu::ButtonDown => {
cli.writer().write_str("TODO: simulate button")?;
}
Menu::ButtonAuto => {
cli.writer().write_str("TODO: simulate button")?;
}
Menu::ButtonPair => {
cli.writer().write_str("TODO: simulate button")?;
}
Menu::BluetoothUp { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothDown { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothStop { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothLearn { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothAuto { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothTopLimit { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothBottomLimit { data } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = data;
}
Menu::BluetoothWifiSsid { ssid } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change")?;
let _ = ssid;
}
Menu::BluetoothWifiPassword { wifipass } => {
cli.writer()
.write_str("TODO: simulate bluetooth characteristic change {}")?;
let _ = wifipass;
}
}
Ok(())
}
// === SimpleWriter ===========================================================
pub struct SimpleWriter {}
impl embedded_io::ErrorType for SimpleWriter {
type Error = Infallible;
}
impl embedded_io::Write for SimpleWriter {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
stdout().write(&buf).unwrap(); //TODO: handle errors?
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), Self::Error> {
let _ = stdout().flush(); //TODO currently ignoring this error
Ok(())
}
}
pub async fn start_cli() -> anyhow::Result<()> {
let timer_service = EspTaskTimerService::new().unwrap(); //TODO: handle this error
info!("Setting up command line listern and processor");
let writer = SimpleWriter {};
let mut cli = CliBuilder::default()
.writer(writer)
.command_buffer([0_u8; MAX_COMMAND_SIZE])
.history_buffer([0_u8; MAX_HISTORY_SIZE])
.prompt("$> ")
.build()?;
let mut reader = stdin();
let mut buf = [0_u8; 1];
let mut async_timer = timer_service.timer_async()?;
loop {
async_timer.after(Duration::from_millis(SLEEP_TIME_MS)).await?;
match reader.read_exact(&mut buf) {
Ok(_) => {
cli.process_byte::<Menu, _>(buf[0], &mut Menu::processor(process_menu))?;
}
Err(e) => match e.kind() {
std::io::ErrorKind::WouldBlock => {} // This is expected if there is no input
_ => {warn!("Error waiting for input: {}", e)}
}
};
}
}