Compare commits

...
4 Commits
7 changed files with 439 additions and 320 deletions
Generated
+241 -175
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -31,10 +31,9 @@ esp-backtrace = { version = "0.19.0", features = [
] } ] }
esp-println = { version = "0.17.0", features = ["defmt-espflash", "esp32c6"] } esp-println = { version = "0.17.0", features = ["defmt-espflash", "esp32c6"] }
embassy-sync = { version = "0.8.0", features = ["defmt"] } embassy-sync = { git = "https://github.com/embassy-rs/embassy", branch = "main" }
embassy-futures = { version = "0.1.2", features = ["defmt"] } embassy-futures = { version = "0.1.2", features = ["defmt"] }
embassy-embedded-hal = { version = "0.6.0", features = ["defmt", "time"] } embassy-embedded-hal = { version = "0.6.0", features = ["defmt", "time"] }
embedded-rpc = "0.2.0"
critical-section = "1.2.0" critical-section = "1.2.0"
static_cell = { version = "2.1.1", features = ["nightly"] } static_cell = { version = "2.1.1", features = ["nightly"] }
@@ -64,9 +63,11 @@ picoserve = { version = "0.19", features = ["defmt", "embassy", "json"] }
sequential-storage = "8.0" sequential-storage = "8.0"
esp-storage = { version = "0.9", features = ["defmt", "esp32c6"] } esp-storage = { version = "0.9", features = ["defmt", "esp32c6"] }
postcard = { version = "1.1", default-features = false } postcard = { version = "1.1", default-features = false, features = ["defmt", "heapless"] }
serde = { version = "1.0", default-features = false, features = ["derive"] } serde = { version = "1.0", default-features = false, features = ["derive"] }
int-enum = "1.2.0" int-enum = "1.2.0"
heapless = { version = "0.9.3", features = ["defmt", "serde"] }
thiserror = { version = "2.0.20", default-features = false }
# For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html # For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html
+3 -21
View File
@@ -7,7 +7,7 @@
)] )]
#![deny(clippy::large_stack_frames)] #![deny(clippy::large_stack_frames)]
use cover_theif::wifi::start_wifi; use cover_theif::{clock::start_ntp_sync, http_api::start_http_api, wifi::start_wifi};
use defmt::info; use defmt::info;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
@@ -16,11 +16,6 @@ use esp_hal::clock::CpuClock;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
use esp_println as _; use esp_println as _;
use cover_theif::{
clock,
http_api::{HttpApiProps, HttpApiService},
};
extern crate alloc; extern crate alloc;
// This creates a default app-descriptor required by the esp-idf bootloader. // This creates a default app-descriptor required by the esp-idf bootloader.
@@ -50,16 +45,8 @@ async fn main(spawner: Spawner) -> ! {
let net_stack = start_wifi(spawner, peripherals.WIFI); let net_stack = start_wifi(spawner, peripherals.WIFI);
spawner.spawn(clock::ntp_upkeep_service(net_stack).unwrap()); start_ntp_sync(spawner, net_stack);
start_http_api(spawner, net_stack);
spawner.spawn(
http_api_serve_task(HttpApiService::new(
net_stack,
Default::default(),
Default::default(),
))
.unwrap(),
);
loop { loop {
info!("Hello world"); info!("Hello world");
@@ -68,8 +55,3 @@ async fn main(spawner: Spawner) -> ! {
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.1.0/examples // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.1.0/examples
} }
#[embassy_executor::task]
async fn http_api_serve_task(mut service: HttpApiService<'static, HttpApiProps>) {
service.run().await;
}
+41 -27
View File
@@ -1,5 +1,9 @@
use core::net::SocketAddr; use core::net::SocketAddr;
use embassy_net::udp::{PacketMetadata, UdpSocket}; use embassy_executor::Spawner;
use embassy_net::{
Stack,
udp::{PacketMetadata, UdpSocket},
};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use portable_atomic::{AtomicU64, Ordering}; use portable_atomic::{AtomicU64, Ordering};
use sntpc::{NtpContext, get_time}; use sntpc::{NtpContext, get_time};
@@ -10,11 +14,38 @@ use sntpc_time_embassy::EmbassyTimestampGenerator;
const NTP_SERVER: &str = "pool.ntp.org"; const NTP_SERVER: &str = "pool.ntp.org";
const NTP_PORT: u16 = 123; const NTP_PORT: u16 = 123;
pub fn now() -> embassy_time::Instant {
embassy_time::Instant::now() + Duration::from_secs(NTP_BOOT_OFFSET.load(Ordering::Relaxed))
}
pub fn start_ntp_sync(spawner: Spawner, net_stack: Stack<'static>) {
spawner.spawn(handler(net_stack).unwrap());
}
#[embassy_executor::task]
async fn handler(net_stack: Stack<'static>) {
loop {
match fetch_ntp_time(net_stack).await {
Ok(unix_secs) => {
NTP_BOOT_OFFSET.store(
unix_secs - embassy_time::Instant::now().as_secs(),
portable_atomic::Ordering::Relaxed,
);
Timer::after(Duration::from_secs(6 * 60 * 60)).await
}
Err(err) => {
defmt::error!("Failed to sync the clock from NTP: {}", err);
Timer::after(Duration::from_secs(10)).await
}
}
}
}
static NTP_BOOT_OFFSET: AtomicU64 = AtomicU64::new(0); static NTP_BOOT_OFFSET: AtomicU64 = AtomicU64::new(0);
pub async fn fetch_ntp_time(stack: embassy_net::Stack<'static>) -> Result<u64, &'static str> { pub async fn fetch_ntp_time(net_stack: Stack<'static>) -> Result<u64, &'static str> {
// Resolve the NTP server hostname // Resolve the NTP server hostname
let server_addr = stack let server_addr = net_stack
.dns_query(NTP_SERVER, embassy_net::dns::DnsQueryType::A) .dns_query(NTP_SERVER, embassy_net::dns::DnsQueryType::A)
.await .await
.map_err(|_| "DNS resolution failed")? .map_err(|_| "DNS resolution failed")?
@@ -30,7 +61,13 @@ pub async fn fetch_ntp_time(stack: embassy_net::Stack<'static>) -> Result<u64, &
let mut tx_meta = [PacketMetadata::EMPTY; 16]; let mut tx_meta = [PacketMetadata::EMPTY; 16];
let mut tx_buf = [0u8; 48]; let mut tx_buf = [0u8; 48];
let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buf, &mut tx_meta, &mut tx_buf); let mut socket = UdpSocket::new(
net_stack,
&mut rx_meta,
&mut rx_buf,
&mut tx_meta,
&mut tx_buf,
);
socket.bind(0).map_err(|_| "bind failed")?; socket.bind(0).map_err(|_| "bind failed")?;
let wrapped = UdpSocketWrapper::from(socket); let wrapped = UdpSocketWrapper::from(socket);
@@ -47,26 +84,3 @@ pub async fn fetch_ntp_time(stack: embassy_net::Stack<'static>) -> Result<u64, &
Ok(unix_secs) Ok(unix_secs)
} }
pub fn now() -> embassy_time::Instant {
embassy_time::Instant::now() + Duration::from_secs(NTP_BOOT_OFFSET.load(Ordering::Relaxed))
}
#[embassy_executor::task]
pub async fn ntp_upkeep_service(stack: embassy_net::Stack<'static>) {
loop {
match fetch_ntp_time(stack).await {
Ok(unix_secs) => {
NTP_BOOT_OFFSET.store(
unix_secs - embassy_time::Instant::now().as_secs(),
portable_atomic::Ordering::Relaxed,
);
Timer::after(Duration::from_secs(6 * 60 * 60)).await
}
Err(err) => {
defmt::error!("Failed to sync the clock from NTP: {}", err);
Timer::after(Duration::from_secs(10)).await
}
}
}
}
+86 -38
View File
@@ -1,26 +1,64 @@
use embassy_embedded_hal::adapter::BlockingAsync; use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, rpc_service::RpcService};
use embedded_rpc::RpcService;
use esp_hal::peripherals::FLASH; use esp_hal::peripherals::FLASH;
use esp_radio::wifi::sta::StationConfig;
use esp_storage::FlashStorage; use esp_storage::FlashStorage;
use heapless::String;
use int_enum::IntEnum; use int_enum::IntEnum;
use postcard::to_slice;
use sequential_storage::{ use sequential_storage::{
cache::{Cache, Uncached}, cache::{Cache, Uncached},
map::{Key, MapConfig, MapStorage, SerializationError}, map::{Key, MapConfig, MapStorage},
}; };
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub enum ConfigCommand { pub static CONFIG_SERVICE: RpcService<CriticalSectionRawMutex, ConfigService, 64> =
SetStationCreds(),
}
impl ConfigCommand {
pub async fn set_station_creds() {}
}
pub static CONFIG_SERVICE: RpcService<CriticalSectionRawMutex, ConfigCommand, ()> =
RpcService::new(); RpcService::new();
pub struct ConfigService {
storage: MapStorage<
ConfigKey,
BlockingAsync<FlashStorage<'static>>,
Cache<Uncached, Uncached, Uncached, ConfigKey>,
>,
data_buffer: [u8; 256],
}
impl ConfigService {
pub async fn set_wifi_creds(&mut self, value: WifiCredentials) -> Result<(), ConfigError> {
let mut buf = [0u8; 256];
to_slice(&value, &mut buf)?;
self.storage
.store_item(&mut self.data_buffer, &ConfigKey::Wifi, &buf)
.await
.map_err(|e| ConfigError::FlashError(e))?;
// TODO: send the change to the wifi service
// WIFI_SERVICE
// .call(|s| s.set_station_config(esp_radio::wifi::Config::Station(value.into())))
// .await;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct WifiCredentials {
pub ssid: String<32>,
pub password: String<32>,
}
impl From<WifiCredentials> for StationConfig {
fn from(value: WifiCredentials) -> Self {
Self::default()
.with_ssid(value.ssid.as_str())
.with_password(value.password.as_str().into())
}
}
pub async fn start_config(spawner: Spawner, flash: FLASH<'static>) { pub async fn start_config(spawner: Spawner, flash: FLASH<'static>) {
let storage = MapStorage::<ConfigKey, _, _>::new( let storage = MapStorage::<ConfigKey, _, _>::new(
BlockingAsync::new(FlashStorage::new(flash)), BlockingAsync::new(FlashStorage::new(flash)),
@@ -28,29 +66,17 @@ pub async fn start_config(spawner: Spawner, flash: FLASH<'static>) {
Cache::new_uncached(), Cache::new_uncached(),
); );
spawner.spawn(config_handler(storage).unwrap()); let service = ConfigService {
storage: storage,
data_buffer: [0u8; 256],
};
spawner.spawn(handler(service).unwrap());
} }
#[embassy_executor::task] #[embassy_executor::task]
async fn config_handler(mut storage: MapStorage<ConfigKey, BlockingAsync<FlashStorage<'static>>, Cache<Uncached, Uncached, Uncached, ConfigKey>>) { async fn handler(mut service: ConfigService) -> ! {
let mut data_buffer = [0u8; 256]; CONFIG_SERVICE.run(&mut service).await
loop {
let (req, served) = CONFIG_SERVICE.serve().await;
match req {
ConfigCommand::SetStationCreds() => {
todo!()
// if storage
// .store_item(&mut data_buffer, &ConfigKey::Wifi, )
// .await
// .is_ok()
// {
// served.respond(());
// }
}
}
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntEnum)] #[derive(Debug, Clone, Copy, PartialEq, Eq, IntEnum)]
@@ -60,18 +86,40 @@ enum ConfigKey {
} }
impl Key for ConfigKey { impl Key for ConfigKey {
fn get_len(_: &[u8]) -> Result<usize, SerializationError> { fn get_len(_: &[u8]) -> Result<usize, sequential_storage::map::SerializationError> {
Ok(1) Ok(1)
} }
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> { fn serialize_into(
let val = buffer.get_mut(0).ok_or(SerializationError::BufferTooSmall)?; &self,
buffer: &mut [u8],
) -> Result<usize, sequential_storage::map::SerializationError> {
let val = buffer
.get_mut(0)
.ok_or(sequential_storage::map::SerializationError::BufferTooSmall)?;
*val = self.clone().into(); *val = self.clone().into();
Ok(1) Ok(1)
} }
fn deserialize_from(buffer: &[u8]) -> Result<(Self, usize), SerializationError> { fn deserialize_from(
let val = *buffer.get(0).ok_or(SerializationError::BufferTooSmall)?; buffer: &[u8],
Ok((val.try_into().map_err(|_| {SerializationError::InvalidData})?, 1)) ) -> Result<(Self, usize), sequential_storage::map::SerializationError> {
let val = *buffer
.get(0)
.ok_or(sequential_storage::map::SerializationError::BufferTooSmall)?;
Ok((
val.try_into()
.map_err(|_| sequential_storage::map::SerializationError::InvalidData)?,
1,
))
} }
} }
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Error serializing data: {0}")]
SerializationError(#[from] postcard::Error),
#[error("Error storing data on the flash: {0:?}")]
FlashError(sequential_storage::Error<esp_storage::FlashStorageError>),
}
+19 -2
View File
@@ -1,3 +1,4 @@
use embassy_executor::Spawner;
use embassy_net::Stack; use embassy_net::Stack;
use picoserve::routing::get; use picoserve::routing::get;
use picoserve::{AppBuilder, AppRouter, Config}; use picoserve::{AppBuilder, AppRouter, Config};
@@ -13,6 +14,22 @@ impl AppBuilder for HttpApiProps {
} }
} }
pub fn start_http_api(spawner: Spawner, net_stack: Stack<'static>) {
spawner.spawn(
handler(HttpApiApp::new(
net_stack,
Default::default(),
Default::default(),
))
.unwrap(),
);
}
#[embassy_executor::task]
async fn handler(mut service: HttpApiApp<'static, HttpApiProps>) {
service.run().await;
}
pub struct HttpApiConfig { pub struct HttpApiConfig {
pub port: u16, pub port: u16,
pub web_serve_config: Config, pub web_serve_config: Config,
@@ -27,7 +44,7 @@ impl Default for HttpApiConfig {
} }
} }
pub struct HttpApiService<'a, AB: AppBuilder> { pub struct HttpApiApp<'a, AB: AppBuilder> {
config: HttpApiConfig, config: HttpApiConfig,
net_stack: Stack<'a>, net_stack: Stack<'a>,
@@ -38,7 +55,7 @@ pub struct HttpApiService<'a, AB: AppBuilder> {
http_buffer: [u8; 2048], http_buffer: [u8; 2048],
} }
impl<'a, AB: AppBuilder> HttpApiService<'a, AB> { impl<'a, AB: AppBuilder> HttpApiApp<'a, AB> {
pub fn new(net_stack: Stack<'a>, config: HttpApiConfig, app: AB) -> Self { pub fn new(net_stack: Stack<'a>, config: HttpApiConfig, app: AB) -> Self {
Self { Self {
config: config, config: config,
+45 -54
View File
@@ -1,30 +1,36 @@
use defmt::{error, info}; use defmt::{error, info};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_futures::select::{Either, select};
use embassy_net::{Runner, Stack}; use embassy_net::{Runner, Stack};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::{
blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, rpc_service::RpcService,
};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use embedded_rpc::{RequestDroppedError, RpcService};
use esp_hal::peripherals::WIFI; use esp_hal::peripherals::WIFI;
use esp_radio::wifi::sta::StationConfig; use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{ControllerConfig, Interface, WifiController}; use esp_radio::wifi::{ControllerConfig, Interface, WifiController, WifiError};
pub enum WifiCommand { pub static WIFI_SERVICE: RpcService<CriticalSectionRawMutex, WifiService, 64> = RpcService::new();
SetStationConfig(esp_radio::wifi::Config),
SetDhcpConfig(embassy_net::DhcpConfig), pub struct WifiService {
pub net_stack: Stack<'static>,
wifi_controller: &'static Mutex<CriticalSectionRawMutex, WifiController<'static>>,
} }
impl WifiCommand { impl WifiService {
pub async fn set_station_config( pub async fn set_station_config(
&self,
config: esp_radio::wifi::Config, config: esp_radio::wifi::Config,
) -> Result<(), RequestDroppedError> { ) -> Result<(), WifiError> {
WIFI_SERVICE let mut controller = self.wifi_controller.lock().await;
.request(WifiCommand::SetStationConfig(config)) controller.set_config(&config)?;
.await Ok(())
}
} }
pub static WIFI_SERVICE: RpcService<CriticalSectionRawMutex, WifiCommand, ()> = RpcService::new(); pub fn set_dhcp_config(&self, config: embassy_net::DhcpConfig) {
self.net_stack
.set_config_v4(embassy_net::ConfigV4::Dhcp(config));
}
}
pub fn start_wifi(spawner: Spawner, wifi: WIFI<'static>) -> Stack<'static> { pub fn start_wifi(spawner: Spawner, wifi: WIFI<'static>) -> Stack<'static> {
let wifi_config = esp_radio::wifi::Config::Station(StationConfig::default()); let wifi_config = esp_radio::wifi::Config::Station(StationConfig::default());
@@ -52,69 +58,54 @@ pub fn start_wifi(spawner: Spawner, wifi: WIFI<'static>) -> Stack<'static> {
seed, seed,
); );
spawner.spawn(net_task(net_runner).unwrap()); let service = WifiService {
spawner.spawn(wifi_handler(wifi_controller).unwrap()); net_stack: net_stack,
wifi_controller: crate::mk_static!(Mutex<CriticalSectionRawMutex, WifiController<'static>>, Mutex::new(wifi_controller)),
};
spawner.spawn(net_handler(net_runner).unwrap());
spawner.spawn(connection_handler(service.wifi_controller).unwrap());
spawner.spawn(handler(service).unwrap());
spawner.spawn(ip_logger(net_stack).unwrap()); spawner.spawn(ip_logger(net_stack).unwrap());
spawner.spawn(handler(net_stack).unwrap());
net_stack net_stack
} }
#[embassy_executor::task] #[embassy_executor::task]
async fn handler(net_stack: Stack<'static>) { async fn handler(mut service: WifiService) -> ! {
loop { WIFI_SERVICE.run(&mut service).await
let (req, served) = WIFI_SERVICE.serve().await;
match req {
WifiCommand::SetStationConfig(config) => {
if WIFI_HANDLER_RPC.request(config).await.is_err() {
continue;
} }
}
WifiCommand::SetDhcpConfig(dhcp_config) => {
net_stack.set_config_v4(embassy_net::ConfigV4::Dhcp(dhcp_config));
}
}
served.respond(());
}
}
static WIFI_HANDLER_RPC: RpcService<CriticalSectionRawMutex, esp_radio::wifi::Config, ()> =
RpcService::new();
#[embassy_executor::task] #[embassy_executor::task]
async fn wifi_handler(mut controller: WifiController<'static>) { async fn connection_handler(
let connection = async |controller: &mut WifiController<'static>| { controller: &'static Mutex<CriticalSectionRawMutex, WifiController<'static>>,
) -> ! {
loop {
info!("Connecting to WiFi..."); info!("Connecting to WiFi...");
match controller.connect_async().await { match controller.lock().await.connect_async().await {
Ok(info) => { Ok(info) => {
info!("Wifi connected to {:?}", info); info!("Wifi connected to {:?}", info);
// wait until we're no longer connected // wait until we're no longer connected
let info = controller.wait_for_disconnect_async().await.ok(); // TODO: reimplement the wait for disconnect
info!("Disconnected: {:?}", info); // let info = controller
// .lock()
// .await
// .wait_for_disconnect_async()
// .await
// .ok();
// info!("Disconnected: {:?}", info);
} }
Err(e) => { Err(e) => {
error!("Failed to connect to wifi: {:?}", e); error!("Failed to connect to wifi: {:?}", e);
} }
} }
};
loop {
if let Either::First((req, served)) =
select(WIFI_HANDLER_RPC.serve(), connection(&mut controller)).await
{
if controller.set_config(&req).is_ok() {
served.respond(());
}
}
} }
} }
#[embassy_executor::task] #[embassy_executor::task]
async fn ip_logger(net_stack: Stack<'static>) { async fn ip_logger(net_stack: Stack<'static>) -> ! {
loop { loop {
info!("Getting IP address..."); info!("Getting IP address...");
net_stack.wait_config_up().await; net_stack.wait_config_up().await;
@@ -137,6 +128,6 @@ async fn ip_logger(net_stack: Stack<'static>) {
} }
#[embassy_executor::task] #[embassy_executor::task]
async fn net_task(mut runner: Runner<'static, Interface<'static>>) { async fn net_handler(mut runner: Runner<'static, Interface<'static>>) -> ! {
runner.run().await runner.run().await
} }