From 45ce330daf6d9192ee17418a071a134581ed026f Mon Sep 17 00:00:00 2001 From: Jakob Dalsgaard Date: Sat, 23 Sep 2023 21:37:11 +0200 Subject: [PATCH] More code --- .cargo/{config => config.toml} | 13 ++ Cargo.toml | 20 ++- env.sh | 3 +- sdkconfig.defaults | 4 + src/main.rs | 303 ++++++++++++++++++++++++++++++++- 5 files changed, 332 insertions(+), 11 deletions(-) rename .cargo/{config => config.toml} (65%) diff --git a/.cargo/config b/.cargo/config.toml similarity index 65% rename from .cargo/config rename to .cargo/config.toml index d84a073..872fb53 100644 --- a/.cargo/config +++ b/.cargo/config.toml @@ -4,6 +4,15 @@ target = "xtensa-esp32-espidf" [target.xtensa-esp32-espidf] linker = "ldproxy" +[profile.release] +# symbols are nice and they don't increase the size on Flash +debug = true +opt-level = "z" + +[profile.dev] +opt-level = "s" + + [unstable] build-std = ["std", "panic_abort"] build-std-features = ["panic_immediate_abort"] @@ -13,3 +22,7 @@ extra-link-arg = true # No longer necessary since 1.56, as it was stabilized: [env] ESP_IDF_SYS_GLOB_BASE = { value = ".", relative = true } +ESP_IDF_SYS_GLOB_0 = { value = "/sdkconfig.release" } +ESP_IDF_SYS_GLOB_1 = { value = "/sdkconfig.debug" } +ESP_IDF_SYS_GLOB_2 = { value = "/sdkconfig.defaults" } + diff --git a/Cargo.toml b/Cargo.toml index f920ff1..ce85f20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,24 @@ + [package] name = "rusty-espresso" version = "0.1.0" -edition = "2018" - - +edition = "2021" [features] bind = [] [dependencies] +log = "0.4.14" +time = { version = "0.3.5", features = ["formatting"] } anyhow = {version = "1", features = ["backtrace"]} -esp-idf-sys = { version = "0.20" } -embedded-svc = "0.8.3" -esp-idf-svc = { version = "0.20", features = ["binstart"] } -esp-idf-hal = "0.20" +esp-idf-sys = { version = "0.31.5", features = ["binstart"] } +embedded-svc = "0.14" +esp-idf-svc = { version = "0.41.2" } +esp-idf-hal = "0.37.3" +maud = "0.22.3" +quick-protobuf = "0.8.0" [build-dependencies] -embuild = "0.24" +embuild = "0.29.0" anyhow = "1" + diff --git a/env.sh b/env.sh index 3818eb7..86da182 100644 --- a/env.sh +++ b/env.sh @@ -1,7 +1,8 @@ #!/bin/bash -export PATH=$HOME/xtensa-esp32-elf-clang/bin:$PATH +# export PATH=/home/jakob/esp/llvm-project/build/bin/clang:$PATH . $HOME/esp/esp-idf/export.sh +export PATH=/home/jakob/esp/xtensa-esp32-elf-clang/bin:$PATH rustup default esp diff --git a/sdkconfig.defaults b/sdkconfig.defaults index fde0066..013d397 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -4,3 +4,7 @@ CONFIG_LWIP_IPV4_NAPT=y #CONFIG_ESP_SYSTEM_USE_EH_FRAME=y #CONFIG_COMPILER_CXX_EXCEPTIONS=y +# Workaround for https://github.com/espressif/esp-idf/issues/7631 +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n +CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 diff --git a/src/main.rs b/src/main.rs index 7da3a48..2235568 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,303 @@ +use embedded_svc::httpd::registry::*; +use embedded_svc::httpd::*; +use embedded_svc::ipv4::ClientConfiguration as Ipv4ClientConfiguration; +use embedded_svc::ipv4::DHCPClientSettings; +use embedded_svc::storage::Storage; +use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, Wifi}; +use esp_idf_svc::httpd::Server; +use esp_idf_svc::netif::EspNetifStack; +use esp_idf_svc::nvs::EspDefaultNvs; +use esp_idf_svc::nvs_storage::EspNvsStorage; +use esp_idf_svc::sntp::*; +use log::*; -fn main() { - println!("Hello, world!"); +use std::env; +use std::fmt; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::SystemTime; +use time::{ + format_description::well_known::Rfc3339, + OffsetDateTime }; +// use embedded_svc::anyerror::*; +// use esp_idf_hal::prelude::*; +use esp_idf_svc::httpd as idf_httpd; +use esp_idf_svc::sysloop::*; +use esp_idf_svc::wifi::*; +use esp_idf_sys::EspError; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::time::Duration; +use maud::{Markup, html, DOCTYPE}; + +mod backup_manager; +mod pid_manager; +mod s11n; + +use crate::backup_manager::BackupManagerTx; + +// use std::{thread, time::Duration}; + +#[derive(Debug)] +struct WifiData { + ssid: String, + password: String, + auth: AuthMethod, +} + +impl fmt::Display for WifiData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "WifiData: ({}, [password not displayed], {})", + self.ssid, + WifiData::authmethod_to_str(self.auth) + ) + } +} + +impl WifiData { + const auth_methods: [(&'static str, AuthMethod); 9] = [ + ("None", AuthMethod::None), + ("WEP", AuthMethod::WEP), + ("WPA", AuthMethod::WPA), + ("WPA2Personal", AuthMethod::WPA2Personal), + ("WPAWPA2Personal", AuthMethod::WPAWPA2Personal), + ("WPA2Enterprise", AuthMethod::WPA2Enterprise), + ("WPA3Personal", AuthMethod::WPA3Personal), + ("WPA2WPA3Personal", AuthMethod::WPA2WPA3Personal), + ("WAPIPersonal", AuthMethod::WAPIPersonal), + ]; + + fn str_to_authmethod(s: &str) -> AuthMethod { + WifiData::auth_methods + .iter() + .find(|(n, _)| s.eq(*n)) + .unwrap_or(&WifiData::auth_methods[0]) + .1 + } + + fn authmethod_to_str(a: AuthMethod) -> &'static str { + WifiData::auth_methods + .iter() + .find(|(_, k)| a == *k) + .unwrap_or(&WifiData::auth_methods[0]) + .0 + } + + pub fn from_nvs(nvs: BackupManagerTx) -> anyhow::Result { + let ssid_b = get_nvs_key(&nvs, "ssid".to_string()).unwrap_or([].to_vec()); + let password_b = get_nvs_key(&nvs, "password".to_string()).unwrap_or([].to_vec()); + let auth_b = get_nvs_key(&nvs, "authmethod".to_string()).unwrap_or([].to_vec()); + let ssid = String::from_utf8(ssid_b).unwrap(); + let password = String::from_utf8(password_b).unwrap(); + let auth = WifiData::str_to_authmethod(std::str::from_utf8(&auth_b).unwrap()); + + Ok(WifiData { ssid: ssid, password: password, auth: auth }) + + } + + /* + pub fn from_nvs(storage: &mut EspNvsStorage) -> anyhow::Result { + Ok(WifiData { + ssid: String::from_utf8(storage.get_raw("ssid")?.unwrap_or([].to_vec()))?, + password: String::from_utf8(storage.get_raw("password")?.unwrap_or([].to_vec()))?, + auth: WifiData::str_to_authmethod( + std::str::from_utf8(&storage.get_raw("authmethod")?.unwrap_or([].to_vec())) + .unwrap_or(""), + ), + }) + } + */ + + pub fn to_nvs(self, storage: &mut EspNvsStorage) -> anyhow::Result<()> { + storage.put_raw("ssid", self.ssid.as_bytes())?; + storage.put_raw("password", self.password.as_bytes())?; + storage.put_raw( + "authmethod", + WifiData::authmethod_to_str(self.auth).as_bytes(), + )?; + Ok(()) + } +} + +fn get_nvs_key (nvs: &BackupManagerTx, key: String) -> Option> { + let (tx, rx) = channel(); + nvs.send(backup_manager::BackupManagerMessage::Load(key, tx)); + rx.recv_timeout(Duration::from_millis(100)).unwrap() +} + +fn get_nvs (nvs: &BackupManagerTx) -> Arc { + let (tx, rx) = channel(); + nvs.send(backup_manager::BackupManagerMessage::GetNvs(tx)); + rx.recv().unwrap() +} + + +fn to_rfc3339(dt: T) -> String where T: Into { + dt.into().format(&Rfc3339).unwrap() +} + + +struct HttpdState { + pid_data_library: pid_manager::PidManager, + nvs_store: backup_manager::BackupManagerTx, +} + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + + env::set_var("RUST_BACKTRACE", "1"); + + esp_idf_svc::log::EspLogger::initialize_default(); + + //let mut nvs = Arc::new(EspDefaultNvs::new()?); + //let mut nvs_store = EspNvsStorage::new_default(nvs.clone(), "data", true)?; + + info!("EspNvsStorage done"); + thread::sleep(Duration::from_secs(10)); + let mut nvs_backup_manager = backup_manager::BackupManager::new(); + let wd = WifiData::from_nvs(nvs_backup_manager.get_sender())?; + info!("wifidata is: {}", &wd); + thread::sleep(Duration::from_secs(1)); + + + let mut w = wifi(wd, get_nvs(&nvs_backup_manager.get_sender()))?; + + info!("We now have wifi -- trying to establish NTP"); + + let sntp_conf = SntpConf { + servers: [ "0.dk.pool.ntp.org".to_string() ], + operating_mode: OperatingMode::Poll, + sync_mode: SyncMode::Immediate + }; + let ntp = EspSntp::new(&sntp_conf)?; + + thread::sleep(Duration::from_secs(1)); + + info!("Current time is: {:?}", to_rfc3339(SystemTime::now())); + + let mut httpd_state = HttpdState { + pid_data_library: pid_manager::PidManager::new(&nvs_backup_manager.get_sender()), + nvs_store: nvs_backup_manager.get_sender(), + }; + let mut h = httpd(httpd_state)?; + + thread::sleep(Duration::from_secs(300)); + + Ok(()) +} + +struct MyServer { + state: i32, + server: Option, +} + +fn httpd(mut state: HttpdState) -> anyhow::Result { + + fn header (page_title: &str) -> Markup { + html! { + head { + title { (page_title) } + link rel="stylesheet" href="/static/styles.css"; + link rel="icon" type="image/png" href="/favicon.png"; + } + } + } + + fn web_frontpage_get(req: Request) -> anyhow::Result { + Ok(Response::ok().body(include_str!("index.html").into())) + } + + let api_pid_post = move |_| { + Ok(Response::ok() + .content_type("application/json") + .body(include_str!("api_pid_post.json").into())) + }; + + fn api_pid_get(req: Request) -> anyhow::Result { + Ok(Response::ok() + .content_type("appliation/json") + .body(include_str!("api_pid_get.json").into())) + } + + fn web_create_pid_form(req: Request) -> anyhow::Result { + let heading = &"Create a PID Data"; + let markup = html! { + (DOCTYPE) + html { + (header(heading)) + h1 { (heading) } + body { + form action="create-pid" method="post" { + label for="name" { "name" } + input type="text" name="name"; + input type="submit" value="create"; + } + } + } + }; + Ok(Response::ok().content_type("text/html").body(markup.into_string().into())) + } + + fn web_pid_get(req: Request, mut pid_manager_tx: pid_manager::PidManagerTx) -> anyhow::Result { + let (tx, rx) = channel(); + pid_manager_tx.send(pid_manager::PidManagerMessage::GetLibraryItemsList(tx)); + let v = rx.recv_timeout(Duration::from_millis(100)).unwrap(); + + let heading = &"Test Maud page"; + + // try out the maud templating language + let markup = html! { + html { + (header(heading)) + h1 { (heading) } + } + }; + Ok(Response::ok().content_type("text/html").body(markup.into_string().into())) + } + + fn static_png(req: Request, b: &[u8]) -> anyhow::Result { + Ok(Response::ok().content_type("image/png").header("Cache-Control", "public, s-maxage=28800, max-age=28800").body(b.to_vec().into())) + } + + fn static_css_gz(req: Request, b: &[u8]) -> anyhow::Result { + Ok(Response::ok().content_type("text/css").header("Cache-Control", "public, s-maxage=28800, max-age=28800").header("Content-Encoding", "gzip").body(b.to_vec().into())) + } + + let server = idf_httpd::ServerRegistry::new() + .at("/") + .get(web_frontpage_get)? + .at("/favicon.png") + .get(move |r| static_png(r, include_bytes!("static/favicon.png")))? + .at("/static/styles.css") + .get(move |r| static_css_gz(r, include_bytes!("static/styles.css.gz")))? + .at("/create-pid-form") + .get(web_create_pid_form)? + .at("/pid") + .get(move |r| web_pid_get(r, state.pid_data_library.tx.clone()))? + .at("/api/v1/pid") + .get(api_pid_get)? + .at("/api/v1/pid") + .post(api_pid_post)?; + + server.start(&idf_httpd::Configuration::default()) +} + +fn wifi(wifidata: WifiData, nvs: Arc) -> anyhow::Result { + let mut wifi = EspWifi::new( + Arc::new(EspNetifStack::new()?), + Arc::new(EspSysLoopStack::new()?), + nvs, + )?; + + wifi.set_configuration(&Configuration::Client(ClientConfiguration { + ssid: wifidata.ssid.into(), + password: wifidata.password.into(), + bssid: None, + channel: None, // unknown channel, let's see how that works :-) + ip_conf: Some(Ipv4ClientConfiguration::DHCP(DHCPClientSettings { hostname: Some(String::from("rusty-espress-XX")) } )), + auth_method: wifidata.auth, + }))?; + + Ok(wifi) }