This commit is contained in:
think.overdrive
2026-02-02 13:38:02 +01:00
parent 2c0b7f9381
commit 9f6d52c802
17 changed files with 710 additions and 0 deletions

41
.cargo/config.toml Normal file
View File

@@ -0,0 +1,41 @@
[build]
# Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3)
#target = "xtensa-esp32-espidf"
#target = "xtensa-esp32s2-espidf"
#target = "xtensa-esp32s3-espidf"
target = "riscv32imc-esp-espidf"
[target.xtensa-esp32-espidf]
linker = "ldproxy"
runner = "espflash --monitor"
#rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[target.xtensa-esp32s2-espidf]
linker = "ldproxy"
runner = "espflash --monitor"
#rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[target.xtensa-esp32s3-espidf]
linker = "ldproxy"
runner = "espflash --monitor"
#rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[target.riscv32imc-esp-espidf]
linker = "ldproxy"
runner = "espflash --monitor"
# Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3. See also https://github.com/ivmarkov/embuild/issues/16
# For ESP-IDF 5 add `espidf_time64` and for earlier versions - remove this flag: https://github.com/esp-rs/rust/issues/110
#rustflags = ["-C", "default-linker-libraries"]
rustflags = ["--cfg", "espidf_time64", "-C", "default-linker-libraries"]
[unstable]
build-std = ["std", "panic_abort"]
#build-std-features = ["panic_immediate_abort"] # Required for older ESP-IDF versions without a realpath implementation
[env]
# Note: these variables are not used when using pio builder (`cargo build --features pio`)
# Builds against ESP-IDF stable (v4.4)
ESP_IDF_VERSION = "release/v5.1"
# Builds against ESP-IDF master (mainline)
#ESP_IDF_VERSION = "master"

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/.vscode
/.embuild
/target
/Cargo.lock
/cfg.toml

49
Cargo.toml Normal file
View File

@@ -0,0 +1,49 @@
[package]
name = "esp32-c3-rust-std"
version = "0.1.0"
authors = ["Gerard CL <gerardcl@gmail.com>"]
edition = "2021"
resolver = "2"
[profile.release]
#opt-level = "s"
codegen-units = 1 # LLVM can perform better optimizations using a single thread
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 's'
overflow-checks = false
[profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"
[features]
default = ["native"]
native = ["esp-idf-sys/native"]
[dependencies]
esp-idf-sys = { version = "0.36.1", features = ["binstart"] }
esp-idf-svc = { version = "0.51.0", features = ["experimental", "alloc", "std"] }
esp-idf-hal = { version = "0.45.2", features = ["critical-section"]}
ssd1306 = "0.10.0"
display-interface = "0.5.0"
display-interface-i2c = "0.5.0"
display-interface-spi = "0.5.0"
embedded-hal = "1.0.0"
embedded-svc = "0.28.1"
embedded-graphics = "0.8.1"
embedded-graphics-core = "0.4.0"
toml-cfg = { version = "0.2.0" }
anyhow = "=1.0.69"
log = { version = "0.4", features = [] }
heapless = {version = "0.8", features = ["serde"]}
spin = "0.10.0"
#lazy_static = "1.5.0"
# smol = "1.3"
[build-dependencies]
embuild = { version = "0.33.1", features = ["elf"] }
toml-cfg = { version = "0.2.0" }
anyhow = "1"

28
build.rs Normal file
View File

@@ -0,0 +1,28 @@
use std::error::Error;
#[toml_cfg::toml_config]
pub struct Config {
#[default("")]
wifi_ssid: &'static str,
#[default("")]
wifi_psk: &'static str,
}
fn main() -> Result<(), Box<dyn Error>> {
// Check if the `cfg.toml` file exists and has been filled out.
if !std::path::Path::new("cfg.toml").exists() {
return Err("You need to create a `cfg.toml` file with your Wi-Fi credentials! Start from `cfg.toml.template`.".into());
}
// The constant `CONFIG` is auto-generated by `toml_config`.
let app_config = CONFIG;
if app_config.wifi_ssid == "SET_ME" || app_config.wifi_psk == "SET_ME" {
return Err("You need to set the Wi-Fi credentials in `cfg.toml`!".into());
}
// Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641
embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
embuild::build::LinkArgs::output_propagated("ESP_IDF")?;
Ok(())
}

3
cfg.toml.template Normal file
View File

@@ -0,0 +1,3 @@
[esp32-c3-rust-std]
wifi_ssid = "SET_ME"
wifi_psk = "SET_ME"

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

16
sdkconfig.defaults Normal file
View File

@@ -0,0 +1,16 @@
# 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=7000
#CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
# 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
# Workaround for https://github.com/espressif/esp-idf/issues/7631
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
# NAPT demo (router)
#CONFIG_LWIP_L2_TO_L3_COPY=y
#CONFIG_LWIP_IP_FORWARD=y
#CONFIG_LWIP_IPV4_NAPT=y

3
src/lib.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod modules;
pub mod server;
pub mod settings;

202
src/main.rs Normal file
View File

@@ -0,0 +1,202 @@
use esp_idf_hal::{
delay::FreeRtos, gpio::{Gpio8, Gpio9, Gpio5, Gpio6}, i2c::{self, I2C0, I2c, I2cConfig, I2cDriver}, peripheral::{Peripheral, PeripheralRef}, prelude::Peripherals,
};
use esp_idf_svc::{
eventloop::EspSystemEventLoop,
nvs::EspDefaultNvsPartition,
espnow::{self, EspNow, PeerInfo},
};
use esp_idf_sys::{self as _, soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_DEFAULT};
use log::*;
use anyhow::{Error, Result};
use esp32_c3_rust_std::modules::{display::{DisplayDriver, init_logger}, wifi::WifiDriver};
use esp32_c3_rust_std::server::http::HttpServer;
const OLED_I2C_ADDRESS: u8 = 0x3C;
const BROADCAST_ADDR: [u8; 6] = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
fn setup_i2c(i2c_peripheral: I2C0, sda: Gpio5, scl: Gpio6) -> Result<I2cDriver<'static>, Error> {
use esp_idf_hal::units::Hertz;
let baudrate: Hertz = Hertz(400_000);
let i2c_config: I2cConfig = I2cConfig::new()
.baudrate(baudrate);
let i2c_driver: I2cDriver = I2cDriver::new(
i2c_peripheral,
sda,
scl,
&i2c_config,
)?;
Ok(i2c_driver)
}
use std::env;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
enum AppEvent {
EspNowRecv { mac: [u8; 6], data: Vec<u8> },
SendDiscovery
}
fn main() -> Result<(), Error> {
esp_idf_sys::link_patches(); //Needed for esp32-rs
unsafe { env::set_var("RUST_BACKTRACE", "1");};
info!("Starting ESP32 C3 setup");
let peripherals = Peripherals::take()?;
let sys_loop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;
//setup OLED
let i2c0 = peripherals.i2c0;
let scl_pin = peripherals.pins.gpio6;
let sda_pin = peripherals.pins.gpio5;
init_logger();
let i2c_driver = setup_i2c(i2c0, sda_pin, scl_pin)?;
let mut display_logger = DisplayDriver::new(i2c_driver, OLED_I2C_ADDRESS);
// wifi module as client
let mut wifi = WifiDriver::new(peripherals.modem, sys_loop, nvs);
wifi.configure();
wifi.start();
// server example for accepting HTTP requests
let mut server = HttpServer::new();
server.set_handlers();
info!("HTTP Server accepting connections");
//ESPNOW
//channel for event
let (tx, rx) = mpsc::channel::<AppEvent>();
//espnow
let esp_now = EspNow::take()?;
//esp_now.set_channel(CHANNEL);
//thread receiver
let tx_callback = tx.clone();
esp_now.register_recv_cb(move |mac, data|
{
let event = AppEvent::EspNowRecv {
mac: *mac.src_addr,
data: data.to_vec()
};
let _ = tx_callback.send(event);
})?;
if !esp_now.peer_exists(BROADCAST_ADDR)? {
let broadcast_peer = PeerInfo {
peer_addr: BROADCAST_ADDR,
encrypt: false,
..Default::default()
};
esp_now.add_peer(broadcast_peer)?;
info!("Added broadcast peer");
}
//thread discover
let tx_timer = tx.clone();
thread::spawn(move || {
loop {
thread::sleep(Duration::from_secs(5));
let _ = tx_timer.send(AppEvent::SendDiscovery);
}
});
let mut wifi_static = wifi.driver.ap_netif().get_ip_info().unwrap();
// if we reached 10 times, we will print the ip address again
let mut count = 0;
loop {
match rx.recv_timeout(Duration::from_millis(1000)) {
Ok(AppEvent::EspNowRecv { mac, data }) => {
let msg = String::from_utf8_lossy(&data);
info!("RX from {:02X?}", mac);
if !esp_now.peer_exists(mac)? {
info!("New peer connected: {:02X?}", mac);
let new_peer = PeerInfo {
peer_addr: mac,
encrypt: false,
..Default::default()
};
if let Ok(_) = esp_now.add_peer(new_peer) {
info!("Added peer: {:02X?}", mac);
let _ = esp_now.send(mac, b"Hello Peer!");
}
}
}
Ok(AppEvent::SendDiscovery) => {
info!("Sending broadcast discovery message");
let _ = esp_now.send(BROADCAST_ADDR, b"DISCOVER-ALPHA");
}
Err(mpsc::RecvTimeoutError::Timeout) => {
let wifi_info: esp_idf_svc::ipv4::IpInfo = wifi.driver.sta_netif().get_ip_info().unwrap();
if wifi_static != wifi_info || count > 21{
wifi_static = wifi_info;
info!(
"{:?}",
wifi_info.ip);
info!(
"{:?}",
wifi_info.subnet.gateway);
count = 0;
}
count += 1;
if let Ok(ref mut display_logger) = &mut display_logger{
display_logger.draw_logs();
} else {
info!("Failed to draw_logs")
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
info!("Disconnected");
break;
}
}
// let wifi_info: esp_idf_svc::ipv4::IpInfo = wifi.driver.sta_netif().get_ip_info().unwrap();
// if wifi_static != wifi_info || count > 10{
// wifi_static = wifi_info;
// info!(
// "{:?}",
// wifi_info.ip
// //wifi.driver.sta_netif().get_ip_info().unwrap()
// );
// info!(
// "{:?}",
// wifi_info.subnet.gateway
// //wifi.driver.sta_netif().get_ip_info().unwrap()
// );
// count = 0;
// }
// count += 1;
// // info!(
// // "{:?}",
// // wifi_info.ip
// // //wifi.driver.sta_netif().get_ip_info().unwrap()
// // );
// // info!(
// // "{:?}",
// // wifi_info.subnet.gateway
// // //wifi.driver.sta_netif().get_ip_info().unwrap()
// // );
// // if let Some(dns) = wifi_info.dns {
// // info!("{:?}", dns);
// // }
// //display::draw_logs(&mut display);
// if let Ok(ref mut display_logger) = &mut display_logger{
// display_logger.draw_logs();
// } else {
// info!("Failed to draw_logs")
// }
// //display_logger.draw_logs();
// FreeRtos::delay_ms(1000);
// //sleep(Duration::new(2, 0));
}
Ok(())
}

204
src/modules/display.rs Normal file
View File

@@ -0,0 +1,204 @@
use log::{LevelFilter, Metadata, Record, Level};
use core::fmt::{Debug, Write};
use spin::Mutex;
use heapless::{String, Vec};
use embedded_hal::i2c::{ErrorType, I2c};
use ssd1306::{
prelude::*,
I2CDisplayInterface,
Ssd1306,
mode::BufferedGraphicsMode,
rotation::DisplayRotation,
};
use embedded_graphics::{
mono_font::{MonoTextStyle, ascii::{FONT_4X6, FONT_5X7}},//, iso_8859_2::{self, FONT_4X6}},
pixelcolor::BinaryColor,
prelude::*,
text::{Alignment, Text},
};
const MAX_LOG_LINES: usize = 4;
//const MAX_CHARS_PER_LINE: usize = 128/6;
//const SCROLL_SPEED: u32 = 100;
//const SCROLL_POSITIONS: Mutex<[u32; MAX_LOG_LINES]> = Mutex::new([0u32; MAX_LOG_LINES]);
//const LAST_SCROLL_TIME: Mutex<u32> = Mutex::new(0);
const MAX_LINE_LENGTH: usize = 19;
const START_Y_OFFSET: i32 = 8;
static LOG_BUFFER: Mutex<Vec<String<MAX_LINE_LENGTH>, MAX_LOG_LINES>> = Mutex::new(Vec::new());
pub type OledDisplay<I> = Ssd1306<
I2CInterface<I>,
DisplaySize128x64,
BufferedGraphicsMode<DisplaySize128x64>>;
pub type OledInterface<I> = Ssd1306<
I2CInterface<I>,
//DisplaySize128x64,
//DisplaySize64x32,
DisplaySize128x32,
BufferedGraphicsMode<DisplaySize128x32>>;
pub struct DisplayDriver<I>
where
I: I2c + ErrorType,
<I as ErrorType>::Error: core::fmt::Debug,
{
display: OledInterface<I>,
}
impl<I> DisplayDriver<I>
where
I: I2c + ErrorType,
<I as ErrorType>::Error: core::fmt::Debug,
{
pub fn new(i2c_driver: I, address: u8) -> Result<Self, <I as ErrorType>::Error> {
let interface = I2CDisplayInterface::new_custom_address(i2c_driver, address);
log::info!("Driver address: {:?} ", address);
//let interface = I2CDisplayInterface::new(i2c_driver);
let mut display = Ssd1306::new(
interface,
//DisplaySize128x64,
//DisplaySize64x32,
DisplaySize128x32,
DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
//display.init().expect("display init failed");
if let Err(e) = display.init() {
log::error!("Display init failed: {:?}", e);
//return Err(e);
}
display.clear(BinaryColor::Off).expect("display clear failed");
display.flush().expect("display flush failed");
Ok(Self { display })
}
pub fn draw_logs(&mut self) {
if let Err(e) = self.display.clear(BinaryColor::Off) {
log::error!("draw error: failed to clear display: {:?}", e);
return;
}
let buffer = LOG_BUFFER.lock();
let text_style = MonoTextStyle::new(&FONT_5X7, BinaryColor::On);
//let max_chars = MAX_CHARS_PER_LINE;
for (i, line) in buffer.iter().enumerate() {
let y = START_Y_OFFSET + (i as i32) * FONT_5X7.character_size.height as i32;
// let substrings = Self::create_scroll_substring(line, max_chars);
// for (j, substr) in substrings.iter().enumerate() {
// let x = 127 - (j * 6) as i32; // Position each substring horizontally
// if let Err(e) = Text::with_alignment(substr, Point::new(x, y), text_style, Alignment::Right)
// .draw(&mut self.display) {
// log::error!("draw error: failed to draw text: {:?}", e);
// break;
// }
// }
if let Err(e) = Text::with_alignment(line, Point::new(99, y), text_style, Alignment::Right)
.draw(&mut self.display) {
log::error!("draw error: failed to draw text: {:?}",e);
break;
}
if let Err(e) = self.display.flush() {
log::error!("draw error: failed to flush display: {:?}",e);
}
}
}
fn wrap_text_to_lines(text: &str, max_len: usize) -> Vec<String<MAX_LINE_LENGTH>, MAX_LOG_LINES> {
let mut lines: Vec<String<MAX_LINE_LENGTH>, MAX_LOG_LINES> = Vec::<String<MAX_LINE_LENGTH>, MAX_LOG_LINES>::new();
let mut current_pos = 0;
while current_pos < text.len() {
let end_pos = (current_pos + max_len).min(text.len());
let slice = &text[current_pos..end_pos];
if let Ok(line) = String::try_from(slice) {
if lines.push(line).is_err() {
break;
}
}
current_pos = end_pos;
}
lines
}
fn create_scroll_substring(line: &String<MAX_LINE_LENGTH>, max_chars: usize) -> Vec<String<MAX_LINE_LENGTH>, 10> {
let mut substrings: Vec<String<MAX_LINE_LENGTH>, 10> = Vec::<String<MAX_LINE_LENGTH>, 10>::new();
if line.len() <= max_chars {
let mut substr = String::new();
let _ = write!(&mut substr, "{}", line);
let _ = substrings.push(substr);
return substrings;
}
//let line_chars: Vec<char, 21> = line.chars().collect();
let mut extended_line: String<MAX_LINE_LENGTH> = String::new();
let _ = write!(&mut extended_line, "{} ", line);
let total_chars = extended_line.len();
for i in 0..(total_chars + max_chars) {
let mut substr = String::new();
for j in 0..max_chars {
let char_index = (i*j) % total_chars;
if char_index < extended_line.len() {
let ch = extended_line.chars().nth(char_index).unwrap();
let _ = write!(&mut substr, "{}", ch);
}
}
let _ = substrings.push(substr);
}
substrings
//let mut line_chars: Vec<char,21> = Vec::new();
// for ch in line.chars() {
// if line_chars.push(ch).is_err() {
// break;
// }
// }
// let total_chars = line_chars.len();
// for i in 0..(total_chars + max_chars) {
// let mut substr = String::new();
// for j in 0..max_chars {
// let char_index = (i+ j) % total_chars;
// let _ = write!(&mut substr, "{}", line_chars[char_index]);
// }
// let _ = substrings.push(substr);
// }
// substrings
}
}
pub struct DisplayLogger;
impl log::Log for DisplayLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Info
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("[{}] {}: {}", record.level(), record.target(), record.args());
let mut line: String<MAX_LINE_LENGTH> = String::new();
//let _ = write!(&mut line, "[{}] {}", record.level(), record.args());
let _ = write!(&mut line, "{:?}", record.args());
let mut buffer = LOG_BUFFER.lock();
if buffer.is_full() {
buffer.remove(0);
}
let _ = buffer.push(line);
}
}
fn flush(&self) {}
}
pub fn init_logger() {
static LOGGER: DisplayLogger = DisplayLogger;
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info)).unwrap();
}

2
src/modules/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod wifi;
pub mod display;

65
src/modules/wifi.rs Normal file
View File

@@ -0,0 +1,65 @@
use embedded_svc::wifi::{ClientConfiguration, Configuration};
use esp_idf_hal::{modem};//, peripherals::Peripherals};
use esp_idf_svc::{
eventloop::{EspEventLoop, System},
nvs::{EspNvsPartition, NvsDefault},
wifi::{EspWifi, NonBlocking},
espnow::{EspNow, PeerInfo, SendStatus, ReceiveInfo, },
};
//use heapless::String as HString;
//use std::str::FromStr;
//use crate::{modules::wifi, settings::wifi::WifiConfig};
use heapless;
use crate::settings::wifi::WIFI_CONFIG;
pub struct WifiDriver<'a> {
pub driver: EspWifi<'a>,
}
//use esp_idf_hal::modem;
impl WifiDriver<'_> {
pub fn new(
//peripherals: Peripherals,
//esp_idf_hal::peripherals::{Modem},
mymodem: modem::Modem,
sys_loop: EspEventLoop<System>,
nvs: EspNvsPartition<NvsDefault>,
) -> WifiDriver<'static> {
WifiDriver {
driver: EspWifi::new(mymodem, sys_loop, Some(nvs)).unwrap(),
}
}
pub fn configure(&mut self) {
//let wifi_config = WifiConfig::load_or_default("wifi_config.toml").unwrap();
//let wifi_config = WifiConfig::Config;
let heapless_ssid_str = heapless::String::<32>::try_from(WIFI_CONFIG.wifi_ssid).expect("SSID too long");
let heapless_psk_str = heapless::String::<64>::try_from(WIFI_CONFIG.wifi_psk).expect("PSK too long");
//let wifi_config = WifiConfig::new(
// heapless_ssid_str,
// heapless_psk_str,
//);
self.driver
.set_configuration(&Configuration::Client(ClientConfiguration {
ssid: heapless_ssid_str,
password: heapless_psk_str,
..Default::default()
}))
.unwrap();
}
pub fn start(&mut self) {
self.driver.start().unwrap();
self.driver.connect().unwrap();
while !self.driver.is_connected().unwrap() {
let config = self.driver.get_configuration().unwrap();
println!(
"Waiting for station SSID: {} on Channel #{:?}, with AuthMethod: {}, on BSSID: {:?}",
config.as_client_conf_ref().unwrap().ssid,
config.as_client_conf_ref().unwrap().channel,
config.as_client_conf_ref().unwrap().auth_method,
config.as_client_conf_ref().unwrap().bssid
)
}
println!("Should be connected now");
}
}

47
src/server/http.rs Normal file
View File

@@ -0,0 +1,47 @@
use embedded_svc::{
http::{Headers, Method},
io::Write,
};
use esp_idf_svc::http::server::{Configuration, EspHttpServer};
use log::info;
use super::template::render_html;
pub struct HttpServer<'a> {
server: EspHttpServer<'a>,
}
impl<'a> HttpServer<'a> {
// Set the HTTP server
pub fn new() -> HttpServer<'a> {
info!("Server creating");
HttpServer {
server: EspHttpServer::new(&Configuration::default()).unwrap(),
}
}
pub fn set_handlers(&mut self) {
info!("Server handlers setup");
self.server
.fn_handler("/", Method::Get, move |request| -> Result<(), Box<dyn std::fmt::Debug>> {
let html = render_html("World Of Hurt");
let mut response = request.into_ok_response().map_err(|e| Box::new(e) as Box<dyn std::fmt::Debug>)?;
response.write_all(html.as_bytes()).map_err(|e| Box::new(e) as Box<dyn std::fmt::Debug>)?;
info!("request for /");
Ok(())
})
.unwrap();
self.server
.fn_handler("/host", Method::Get, move |request| -> Result<(), Box<dyn std::fmt::Debug>> {
let html = render_html(request.host().unwrap_or_default());
let mut response = request.into_ok_response().map_err(|e| Box::new(e) as Box<dyn std::fmt::Debug>)?;
response.write_all(html.as_bytes()).map_err(|e| Box::new(e) as Box<dyn std::fmt::Debug>)?;
info!("request for /host");
Ok(())
})
.unwrap();
}
}

2
src/server/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod http;
mod template;

21
src/server/template.rs Normal file
View File

@@ -0,0 +1,21 @@
fn templated(content: impl AsRef<str>) -> String {
format!(
r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>esp-rs web server</title>
</head>
<body>
{}
</body>
</html>
"#,
content.as_ref()
)
}
pub fn render_html(content: impl AsRef<str>) -> String {
templated(format!("Hello {}!", content.as_ref()))
}

1
src/settings/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod wifi;

19
src/settings/wifi.rs Normal file
View File

@@ -0,0 +1,19 @@
#[toml_cfg::toml_config]
#[derive(Default)]
pub struct WifiConfig {
#[default("")]
pub wifi_ssid: &'static str,
#[default("")]
pub wifi_psk: &'static str,
}
// impl WifiConfig {
// pub fn new(
// wifi_ssid: impl Into<heapless::String<32>>,
// wifi_psk: impl Into<heapless::String<64>>,
// ) -> Self {
// Self {
// wifi_ssid: wifi_ssid.into(),
// wifi_psk: wifi_psk.into(),
// }
// }
// }