Compare commits

..
10 Commits
8 changed files with 357 additions and 92 deletions
Generated
+88
View File
@@ -158,6 +158,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "const-default"
version = "1.0.0"
@@ -186,9 +195,13 @@ version = "0.1.0"
dependencies = [
"critical-section",
"defmt 1.1.1",
"embassy-embedded-hal",
"embassy-executor",
"embassy-futures",
"embassy-net",
"embassy-sync 0.8.0",
"embassy-time",
"embedded-rpc",
"esp-alloc",
"esp-backtrace",
"esp-bootloader-esp-idf",
@@ -196,8 +209,13 @@ dependencies = [
"esp-println",
"esp-radio",
"esp-rtos",
"esp-storage",
"int-enum",
"picoserve",
"portable-atomic",
"postcard",
"sequential-storage",
"serde",
"sntpc",
"sntpc-net-embassy",
"sntpc-time-embassy",
@@ -428,9 +446,11 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce"
dependencies = [
"defmt 1.1.1",
"embassy-futures",
"embassy-hal-internal",
"embassy-sync 0.8.0",
"embassy-time",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-async",
@@ -686,6 +706,15 @@ dependencies = [
"embedded-nal",
]
[[package]]
name = "embedded-rpc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "691855ab7f974e2aaa87433e0f6e5967a959f312c486f6f929ff1cda14c7f80d"
dependencies = [
"embassy-sync 0.8.0",
]
[[package]]
name = "embedded-storage"
version = "0.3.1"
@@ -1009,6 +1038,22 @@ dependencies = [
"xtensa-lx",
]
[[package]]
name = "esp-storage"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf2b3f00c9f94b27c62a4f5296b19470c3a083c687c8146e554a459f9bee596"
dependencies = [
"defmt 1.1.1",
"document-features",
"embedded-storage",
"esp-hal",
"esp-hal-procmacros",
"esp-metadata-generated",
"esp-rom-sys",
"esp-sync",
]
[[package]]
name = "esp-sync"
version = "0.2.1"
@@ -1387,6 +1432,18 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "int-enum"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e366a1634cccc76b4cfd3e7580de9b605e4d93f1edac48d786c1f867c0def495"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.117",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -1648,6 +1705,16 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"serde",
]
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
@@ -1666,6 +1733,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"version_check",
]
[[package]]
name = "quote"
version = "1.0.45"
@@ -1807,6 +1886,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sequential-storage"
version = "8.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e750f14f4f6e5a81277c66311713a56106c8df0196df62a634d1fc806556832b"
dependencies = [
"embedded-storage-async",
]
[[package]]
name = "serde"
version = "1.0.228"
+12 -1
View File
@@ -31,8 +31,13 @@ esp-backtrace = { version = "0.19.0", features = [
] }
esp-println = { version = "0.17.0", features = ["defmt-espflash", "esp32c6"] }
embassy-sync = { version = "0.8.0", features = ["defmt"] }
embassy-futures = { version = "0.1.2", features = ["defmt"] }
embassy-embedded-hal = { version = "0.6.0", features = ["defmt", "time"] }
embedded-rpc = "0.2.0"
critical-section = "1.2.0"
static_cell = "2.1.1"
static_cell = { version = "2.1.1", features = ["nightly"] }
embassy-net = { version = "0.9.1", features = [
"defmt",
@@ -57,6 +62,12 @@ portable-atomic = "1.15.0"
picoserve = { version = "0.19", features = ["defmt", "embassy", "json"] }
sequential-storage = "8.0"
esp-storage = { version = "0.9", features = ["defmt", "esp32c6"] }
postcard = { version = "1.1", default-features = false }
serde = { version = "1.0", default-features = false, features = ["derive"] }
int-enum = "1.2.0"
# For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html
[profile.dev]
+16 -87
View File
@@ -6,20 +6,20 @@
holding buffers for the duration of a data transfer."
)]
#![deny(clippy::large_stack_frames)]
#![feature(impl_trait_in_assoc_type)]
use defmt::{error, info};
use cover_theif::wifi::start_wifi;
use defmt::info;
use embassy_executor::Spawner;
use embassy_net::Runner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::clock::CpuClock;
use esp_hal::timer::timg::TimerGroup;
use esp_println as _;
use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{ControllerConfig, Interface, WifiController};
use cover_theif::{http_api::{self, HttpApiProps, HttpApiService}, clock};
use cover_theif::{
clock,
http_api::{HttpApiProps, HttpApiService},
};
extern crate alloc;
@@ -27,19 +27,6 @@ extern crate alloc;
// 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!();
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write($val);
x
}};
}
const SSID: &str = env!("SSID");
const PASSWORD: &str = env!("PASSWORD");
#[allow(
clippy::large_stack_frames,
reason = "it's not unusual to allocate larger buffers etc. in main"
@@ -61,47 +48,18 @@ async fn main(spawner: Spawner) -> ! {
info!("Embassy initialized!");
let wifi_config = esp_radio::wifi::Config::Station(
StationConfig::default()
.with_ssid(SSID)
.with_password(PASSWORD.into()),
);
let (wifi_controller, wifi_interfaces) = esp_radio::wifi::new(
peripherals.WIFI,
ControllerConfig::default().with_initial_config(wifi_config),
)
.expect("Failed to initialize Wi-Fi controller");
let mut dhcp_config = embassy_net::DhcpConfig::default();
dhcp_config.hostname = Some("cover-theif".try_into().unwrap());
let net_config = embassy_net::Config::dhcpv4(dhcp_config);
let rng = esp_hal::rng::Rng::new();
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
let (net_stack, net_runner) = embassy_net::new(
wifi_interfaces.station,
net_config,
mk_static!(
embassy_net::StackResources<3>,
embassy_net::StackResources::<3>::new()
),
seed,
);
spawner.spawn(connection(wifi_controller).unwrap());
spawner.spawn(net_task(net_runner).unwrap());
net_stack.wait_config_up().await;
if let Some(config) = net_stack.config_v4() {
info!("Got IP: {}", config.address);
}
let net_stack = start_wifi(spawner, peripherals.WIFI);
spawner.spawn(clock::ntp_upkeep_service(net_stack).unwrap());
spawner.spawn(http_api_serve_task(HttpApiService::new(net_stack, Default::default(), Default::default())).unwrap());
spawner.spawn(
http_api_serve_task(HttpApiService::new(
net_stack,
Default::default(),
Default::default(),
))
.unwrap(),
);
loop {
info!("Hello world");
@@ -111,36 +69,7 @@ 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
}
#[embassy_executor::task]
async fn connection(mut controller: WifiController<'static>) {
info!("start connection task");
loop {
info!("About to connect...");
match controller.connect_async().await {
Ok(info) => {
info!("Wifi connected to {:?}", info);
// wait until we're no longer connected
let info = controller.wait_for_disconnect_async().await.ok();
info!("Disconnected: {:?}", info);
}
Err(e) => {
error!("Failed to connect to wifi: {:?}", e);
}
}
Timer::after(Duration::from_millis(5000)).await
}
}
#[embassy_executor::task]
async fn net_task(mut runner: Runner<'static, Interface<'static>>) {
runner.run().await
}
#[embassy_executor::task]
async fn http_api_serve_task(mut service: HttpApiService<'static, HttpApiProps>) {
service.serve().await;
service.run().await;
}
+5 -2
View File
@@ -1,10 +1,10 @@
use core::net::SocketAddr;
use embassy_net::udp::{PacketMetadata, UdpSocket};
use embassy_time::{Duration, Timer};
use portable_atomic::{AtomicU64, Ordering};
use sntpc::{NtpContext, get_time};
use sntpc_net_embassy::UdpSocketWrapper;
use sntpc_time_embassy::EmbassyTimestampGenerator;
use portable_atomic::{AtomicU64, Ordering};
// NTP server (pool.ntp.org is the usual choice)
const NTP_SERVER: &str = "pool.ntp.org";
@@ -57,7 +57,10 @@ 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);
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) => {
+77
View File
@@ -0,0 +1,77 @@
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embedded_rpc::RpcService;
use esp_hal::peripherals::FLASH;
use esp_storage::FlashStorage;
use int_enum::IntEnum;
use sequential_storage::{
cache::{Cache, Uncached},
map::{Key, MapConfig, MapStorage, SerializationError},
};
pub enum ConfigCommand {
SetStationCreds(),
}
impl ConfigCommand {
pub async fn set_station_creds() {}
}
pub static CONFIG_SERVICE: RpcService<CriticalSectionRawMutex, ConfigCommand, ()> =
RpcService::new();
pub async fn start_config(spawner: Spawner, flash: FLASH<'static>) {
let storage = MapStorage::<ConfigKey, _, _>::new(
BlockingAsync::new(FlashStorage::new(flash)),
const { MapConfig::new(0x3F_0000..0x40_0000) },
Cache::new_uncached(),
);
spawner.spawn(config_handler(storage).unwrap());
}
#[embassy_executor::task]
async fn config_handler(mut storage: MapStorage<ConfigKey, BlockingAsync<FlashStorage<'static>>, Cache<Uncached, Uncached, Uncached, ConfigKey>>) {
let mut data_buffer = [0u8; 256];
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)]
#[repr(u8)]
enum ConfigKey {
Wifi,
}
impl Key for ConfigKey {
fn get_len(_: &[u8]) -> Result<usize, SerializationError> {
Ok(1)
}
fn serialize_into(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
let val = buffer.get_mut(0).ok_or(SerializationError::BufferTooSmall)?;
*val = self.clone().into();
Ok(1)
}
fn deserialize_from(buffer: &[u8]) -> Result<(Self, usize), SerializationError> {
let val = *buffer.get(0).ok_or(SerializationError::BufferTooSmall)?;
Ok((val.try_into().map_err(|_| {SerializationError::InvalidData})?, 1))
}
}
+2 -2
View File
@@ -28,7 +28,7 @@ impl Default for HttpApiConfig {
}
pub struct HttpApiService<'a, AB: AppBuilder> {
pub config: HttpApiConfig,
config: HttpApiConfig,
net_stack: Stack<'a>,
app: AppRouter<AB>,
@@ -52,7 +52,7 @@ impl<'a, AB: AppBuilder> HttpApiService<'a, AB> {
}
}
pub async fn serve(&mut self) {
pub async fn run(&mut self) {
picoserve::Server::new(
&self.app,
&self.config.web_serve_config,
+15
View File
@@ -10,3 +10,18 @@
pub mod http_api;
pub mod clock;
pub mod wifi;
pub mod config;
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
#[macro_export]
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write($val);
x
}};
}
+142
View File
@@ -0,0 +1,142 @@
use defmt::{error, info};
use embassy_executor::Spawner;
use embassy_futures::select::{Either, select};
use embassy_net::{Runner, Stack};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Duration, Timer};
use embedded_rpc::{RequestDroppedError, RpcService};
use esp_hal::peripherals::WIFI;
use esp_radio::wifi::sta::StationConfig;
use esp_radio::wifi::{ControllerConfig, Interface, WifiController};
pub enum WifiCommand {
SetStationConfig(esp_radio::wifi::Config),
SetDhcpConfig(embassy_net::DhcpConfig),
}
impl WifiCommand {
pub async fn set_station_config(
config: esp_radio::wifi::Config,
) -> Result<(), RequestDroppedError> {
WIFI_SERVICE
.request(WifiCommand::SetStationConfig(config))
.await
}
}
pub static WIFI_SERVICE: RpcService<CriticalSectionRawMutex, WifiCommand, ()> = RpcService::new();
pub fn start_wifi(spawner: Spawner, wifi: WIFI<'static>) -> Stack<'static> {
let wifi_config = esp_radio::wifi::Config::Station(StationConfig::default());
let (wifi_controller, wifi_interfaces) = esp_radio::wifi::new(
wifi,
ControllerConfig::default().with_initial_config(wifi_config),
)
.expect("Failed to initialize Wi-Fi controller");
let mut dhcp_config = embassy_net::DhcpConfig::default();
dhcp_config.hostname = Some("cover-theif".try_into().unwrap());
let net_config = embassy_net::Config::dhcpv4(dhcp_config);
let rng = esp_hal::rng::Rng::new();
let seed = (rng.random() as u64) << 32 | rng.random() as u64;
let (net_stack, net_runner) = embassy_net::new(
wifi_interfaces.station,
net_config,
crate::mk_static!(
embassy_net::StackResources<3>,
embassy_net::StackResources::<3>::new()
),
seed,
);
spawner.spawn(net_task(net_runner).unwrap());
spawner.spawn(wifi_handler(wifi_controller).unwrap());
spawner.spawn(ip_logger(net_stack).unwrap());
spawner.spawn(handler(net_stack).unwrap());
net_stack
}
#[embassy_executor::task]
async fn handler(net_stack: Stack<'static>) {
loop {
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]
async fn wifi_handler(mut controller: WifiController<'static>) {
let connection = async |controller: &mut WifiController<'static>| {
info!("Connecting to WiFi...");
match controller.connect_async().await {
Ok(info) => {
info!("Wifi connected to {:?}", info);
// wait until we're no longer connected
let info = controller.wait_for_disconnect_async().await.ok();
info!("Disconnected: {:?}", info);
}
Err(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]
async fn ip_logger(net_stack: Stack<'static>) {
loop {
info!("Getting IP address...");
net_stack.wait_config_up().await;
match net_stack.config_v4() {
Some(info) => {
info!("Got IP: {:?}", info);
// wait until we're no longer connected
net_stack.wait_config_down().await;
info!("Address no longer configured");
}
None => {
error!("Failed to configure an address");
}
}
Timer::after(Duration::from_millis(5000)).await
}
}
#[embassy_executor::task]
async fn net_task(mut runner: Runner<'static, Interface<'static>>) {
runner.run().await
}