Compare commits

..

3 Commits

Author SHA1 Message Date
Philipp_EndevourOS
cab2533fab deleted some imports 2025-10-27 15:19:51 +01:00
Philipp_EndevourOS
5f65cc7a73 Merge remote-tracking branch 'origin/main' into feature/newlib 2025-10-27 15:11:04 +01:00
Philipp_EndevourOS
03e6a9036f implemented LED for new lib !esp-hhal-smartled is not released! 2025-10-27 15:08:22 +01:00
14 changed files with 271 additions and 204 deletions

3
Cargo.lock generated
View File

@@ -814,7 +814,7 @@ dependencies = [
[[package]] [[package]]
name = "esp-hal-smartled" name = "esp-hal-smartled"
version = "0.17.0" version = "0.17.0"
source = "git+https://github.com/esp-rs/esp-hal-community.git?branch=main#ab4316534d90e3a12785907f043f6899faee0f20" source = "git+https://github.com/esp-rs/esp-hal-community.git?rev=ab4316534d90e3a12785907f043f6899faee0f20#ab4316534d90e3a12785907f043f6899faee0f20"
dependencies = [ dependencies = [
"document-features", "document-features",
"esp-hal", "esp-hal",
@@ -1156,7 +1156,6 @@ dependencies = [
"smart-leds", "smart-leds",
"smoltcp", "smoltcp",
"static_cell", "static_cell",
"thiserror",
] ]
[[package]] [[package]]

View File

@@ -45,12 +45,11 @@ serde = { version = "1.0.219", default-features = false, features = ["derive", "
serde_json = { version = "1.0.143", default-features = false, features = ["alloc"]} serde_json = { version = "1.0.143", default-features = false, features = ["alloc"]}
ds3231 = { version = "0.3.0", features = ["async", "temperature_f32"] } ds3231 = { version = "0.3.0", features = ["async", "temperature_f32"] }
esp-hal-smartled = { git = "https://github.com/esp-rs/esp-hal-community.git", package = "esp-hal-smartled", branch = "main", features = ["esp32c6"]} esp-hal-smartled = { git = "https://github.com/esp-rs/esp-hal-community.git", rev = "ab4316534d90e3a12785907f043f6899faee0f20", package = "esp-hal-smartled", features = ["esp32c6"]}
smart-leds = "0.4.0" smart-leds = "0.4.0"
embedded-sdmmc = "0.8.0" embedded-sdmmc = "0.8.0"
embedded-hal-bus = "0.3.0" embedded-hal-bus = "0.3.0"
thiserror = { version = "2.0.17", default-features = false }
[profile.dev] [profile.dev]
# Rust debug is too slow. # Rust debug is too slow.

View File

@@ -1,2 +1,3 @@
pub mod nfc_reader; pub mod nfc_reader;
pub mod rtc; pub mod rtc;
pub mod buzzer;

0
src/drivers/buzzer.rs Normal file
View File

44
src/drivers/mock.rs Normal file
View File

@@ -0,0 +1,44 @@
use anyhow::Result;
use log::debug;
use std::time::Duration;
use tokio::time::sleep;
use crate::hardware::{Buzzer, Hotspot, StatusLed};
pub struct MockBuzzer {}
impl Buzzer for MockBuzzer {
async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<()> {
debug!("MockBuzzer: modulte tone: {frequency_hz} Hz");
sleep(duration).await;
Ok(())
}
}
pub struct MockLed {}
impl StatusLed for MockLed {
fn turn_off(&mut self) -> Result<()> {
debug!("Turn mock LED off");
Ok(())
}
fn turn_on(&mut self, color: rgb::RGB8) -> Result<()> {
debug!("Turn mock LED on to: {color}");
Ok(())
}
}
pub struct MockHotspot {}
impl Hotspot for MockHotspot {
async fn enable_hotspot(&self) -> Result<()> {
debug!("Mockhotspot: Enable hotspot");
Ok(())
}
async fn disable_hotspot(&self) -> Result<()> {
debug!("Mockhotspot: Disable hotspot");
Ok(())
}
}

View File

@@ -1,6 +1,7 @@
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use ds3231::{ use ds3231::{
Config, DS3231, InterruptControl, Oscillator, SquareWaveFrequency, TimeRepresentation, Config, DS3231, DS3231Error, InterruptControl, Oscillator, SquareWaveFrequency,
TimeRepresentation,
}; };
use esp_hal::{ use esp_hal::{
Async, Async,
@@ -14,6 +15,10 @@ include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
const RTC_ADDRESS: u8 = 0x68; const RTC_ADDRESS: u8 = 0x68;
const SECS_PER_DAY: u64 = 86_400;
const UNIX_OFFSET_DAYS: u64 = 719_163; // Days from 0000-03-01 to 1970-01-01
const UTC_PLUS_ONE: u64 = 3600;
pub struct RTCClock { pub struct RTCClock {
dev: DS3231<I2c<'static, Async>>, dev: DS3231<I2c<'static, Async>>,
} }
@@ -29,7 +34,10 @@ impl RTCClock {
pub async fn get_time(&mut self) -> u64 { pub async fn get_time(&mut self) -> u64 {
match self.dev.datetime().await { match self.dev.datetime().await {
Ok(datetime) => datetime.and_utc().timestamp() as u64, Ok(datetime) => {
let utc_time = datetime.and_utc().timestamp() as u64;
utc_time
}
Err(e) => { Err(e) => {
FEEDBACK_STATE.signal(feedback::FeedbackState::Error); FEEDBACK_STATE.signal(feedback::FeedbackState::Error);
error!("Failed to read RTC datetime: {:?}", e); error!("Failed to read RTC datetime: {:?}", e);
@@ -39,6 +47,33 @@ impl RTCClock {
} }
} }
fn unix_to_ymd_string(timestamp: u64) -> (u16, u8, u8) {
// Apply UTC+1 offset
let ts = timestamp + UTC_PLUS_ONE;
// Convert to total days since UNIX epoch
let days_since_epoch = ts / SECS_PER_DAY;
// Convert to proleptic Gregorian date
civil_from_days(days_since_epoch as i64 + UNIX_OFFSET_DAYS as i64)
}
// This function returns (year, month, day).
// Based on the algorithm by Howard Hinnant.
fn civil_from_days(z: i64) -> (u16, u8, u8) {
let mut z = z;
z -= 60; // shift epoch for algorithm
let era = (z >= 0).then_some(z).unwrap_or(z - 146096) / 146097;
let doe = z - era * 146097; // [0, 146096]
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
let mp = (5 * doy + 2) / 153; // [0, 11]
let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
let m = mp + (if mp < 10 { 3 } else { -9 }); // [1, 12]
((y + (m <= 2) as i64) as u16, m as u8, d as u8)
}
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> { pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> {
let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS); let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);
let naive_dt = Utc let naive_dt = Utc
@@ -64,13 +99,11 @@ pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>>
} }
} }
if rtc.datetime().await.unwrap() < naive_dt {
rtc.set_datetime(&naive_dt).await.unwrap_or_else(|e| { rtc.set_datetime(&naive_dt).await.unwrap_or_else(|e| {
FEEDBACK_STATE.signal(feedback::FeedbackState::Error); FEEDBACK_STATE.signal(feedback::FeedbackState::Error);
error!("Failed to set RTC datetime: {:?}", e); error!("Failed to set RTC datetime: {:?}", e);
}); });
info!("RTC datetime set to: {}", naive_dt); info!("RTC datetime set to: {}", naive_dt);
}
match rtc.status().await { match rtc.status().await {
Ok(mut status) => { Ok(mut status) => {
@@ -83,5 +116,13 @@ pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>>
} }
Err(e) => info!("Failed to read status: {:?}", e), Err(e) => info!("Failed to read status: {:?}", e),
} }
rtc rtc
} }
pub async fn read_rtc_time<'a>(
rtc: &'a mut DS3231<I2c<'static, Async>>,
) -> Result<u64, DS3231Error<esp_hal::i2c::master::Error>> {
let timestamp_result = rtc.datetime().await?;
Ok(timestamp_result.and_utc().timestamp() as u64)
}

View File

@@ -1,6 +1,7 @@
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use esp_hal::gpio::Output; use esp_hal::rmt::Rmt;
use esp_hal_smartled::SmartLedsAdapterAsync; use esp_hal::peripherals;
use esp_hal_smartled::{SmartLedsAdapterAsync, buffer_size_async};
use log::debug; use log::debug;
use smart_leds::SmartLedsWriteAsync; use smart_leds::SmartLedsWriteAsync;
use smart_leds::colors::{BLACK, GREEN, RED, YELLOW}; use smart_leds::colors::{BLACK, GREEN, RED, YELLOW};
@@ -25,10 +26,18 @@ const LED_LEVEL: u8 = 255;
#[embassy_executor::task] #[embassy_executor::task]
pub async fn feedback_task( pub async fn feedback_task(
mut led: SmartLedsAdapterAsync<'static, { hardware::LED_BUFFER_SIZE }>, rmt: Rmt<'static, esp_hal::Async>,
mut buzzer: Output<'static>, led_gpio: peripherals::GPIO1<'static>,
buzzer_gpio: peripherals::GPIO21<'static>,
) { ) {
debug!("Starting feedback task"); debug!("Starting feedback task");
let rmt_channel = rmt.channel0;
let rmt_buffer = [esp_hal::rmt::PulseCode::default(); buffer_size_async(hardware::NUM_LEDS)];
let mut led = SmartLedsAdapterAsync::new(rmt_channel, led_gpio, rmt_buffer);
let mut buzzer = init::hardware::setup_buzzer(buzzer_gpio);
loop { loop {
let feedback_state = FEEDBACK_STATE.wait().await; let feedback_state = FEEDBACK_STATE.wait().await;
match feedback_state { match feedback_state {
@@ -104,8 +113,6 @@ pub async fn feedback_task(
buzzer.set_high(); buzzer.set_high();
Timer::after(Duration::from_millis(100)).await; Timer::after(Duration::from_millis(100)).await;
buzzer.set_low(); buzzer.set_low();
Timer::after(Duration::from_secs(2)).await;
led.write(brightness( led.write(brightness(
[BLACK; init::hardware::NUM_LEDS].into_iter(), [BLACK; init::hardware::NUM_LEDS].into_iter(),
LED_LEVEL, LED_LEVEL,

View File

@@ -1,12 +1,15 @@
use core::cell::RefCell;
use critical_section::Mutex;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::Stack; use embassy_net::Stack;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use esp_hal::Blocking; use esp_hal::Blocking;
use esp_hal::delay::Delay; use esp_hal::delay::Delay;
use esp_hal::gpio::AnyPin; use esp_hal::gpio::Input;
use esp_hal::i2c::master::Config; use esp_hal::i2c::master::Config;
use esp_hal::peripherals::{ use esp_hal::peripherals::{
GPIO1, GPIO16, GPIO17, GPIO18, GPIO19, GPIO20, GPIO21, GPIO22, GPIO23, I2C0, RMT, SPI2, UART1, GPIO0, GPIO1, GPIO16, GPIO17, GPIO18, GPIO19, GPIO20, GPIO21, GPIO22, GPIO23, I2C0, RMT, SPI2,
UART1,
}; };
use esp_hal::rmt::Rmt; use esp_hal::rmt::Rmt;
use esp_hal::spi::master::{Config as Spi_config, Spi}; use esp_hal::spi::master::{Config as Spi_config, Spi};
@@ -18,15 +21,12 @@ use esp_hal::{
clock::CpuClock, clock::CpuClock,
gpio::{Output, OutputConfig}, gpio::{Output, OutputConfig},
i2c::master::I2c, i2c::master::I2c,
timer::systimer::SystemTimer,
uart::Uart, uart::Uart,
}; };
use esp_hal_smartled::{SmartLedsAdapterAsync, buffer_size_async}; use esp_hal_smartled::{SmartLedsAdapterAsync, buffer_size_async};
use esp_println::logger::init_logger; use esp_println::logger::init_logger;
use log::{debug, error}; use log::{debug, error};
use smart_leds::SmartLedsWriteAsync;
use smart_leds::brightness;
use smart_leds::colors::RED;
use thiserror::Error;
use crate::init::network; use crate::init::network;
use crate::init::sd_card::{SDCardPersistence, setup_sdcard}; use crate::init::sd_card::{SDCardPersistence, setup_sdcard};
@@ -50,7 +50,8 @@ use crate::init::wifi;
*************************************************/ *************************************************/
pub const NUM_LEDS: usize = 1; pub const NUM_LEDS: usize = 1;
pub const LED_BUFFER_SIZE: usize = buffer_size_async(NUM_LEDS);
static SD_DET: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
#[panic_handler] #[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! { fn panic(info: &core::panic::PanicInfo) -> ! {
@@ -62,36 +63,19 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
esp_bootloader_esp_idf::esp_app_desc!(); esp_bootloader_esp_idf::esp_app_desc!();
#[derive(Error, Debug)] pub async fn hardware_init(
pub enum HardwareInitError { spawner: Spawner,
#[error("Failed to etup UART")] ) -> (
Uart(#[from] esp_hal::uart::ConfigError), Uart<'static, Async>,
Stack<'static>,
#[error("Failed to setup I2C")] I2c<'static, Async>,
I2C(#[from] esp_hal::i2c::master::ConfigError), Rmt<'static, esp_hal::Async>,
GPIO1<'static>,
#[error("Failed to setup SPI")] GPIO21<'static>,
Spi(#[from] esp_hal::spi::master::ConfigError), GPIO0<'static>,
SmartLedsAdapterAsync<'static, LED_BUFFER_SIZE>,
#[error("Failed to setuo LED")] SDCardPersistence,
Led(#[from] esp_hal::rmt::Error), ) {
#[error("Failed to setup wifi")]
Wifi(#[from] wifi::WifiError),
}
pub struct AppHardware {
pub uart: Uart<'static, Async>,
pub network_stack: Stack<'static>,
pub i2c: I2c<'static, Async>,
pub buzzer: Output<'static>,
pub sd_present: AnyPin<'static>,
pub led: SmartLedsAdapterAsync<'static, LED_BUFFER_SIZE>,
pub sdcard: SDCardPersistence,
}
impl AppHardware {
pub async fn init(spawner: Spawner) -> Result<Self, HardwareInitError> {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config); let peripherals = esp_hal::init(config);
@@ -104,34 +88,31 @@ impl AppHardware {
init_logger(log::LevelFilter::Debug); init_logger(log::LevelFilter::Debug);
let mut led = setup_led(peripherals.RMT, peripherals.GPIO1)?;
let _ = led.write(brightness(
[RED; NUM_LEDS].into_iter(),
255,
))
.await;
let rng = esp_hal::rng::Rng::new(); let rng = esp_hal::rng::Rng::new();
let network_seed = (rng.random() as u64) << 32 | rng.random() as u64; let network_seed = (rng.random() as u64) << 32 | rng.random() as u64;
wifi::set_antenna_mode(peripherals.GPIO3, peripherals.GPIO14).await; wifi::set_antenna_mode(peripherals.GPIO3, peripherals.GPIO14).await;
let interfaces = wifi::setup_wifi(peripherals.WIFI, spawner)?; let interfaces = wifi::setup_wifi(peripherals.WIFI, spawner);
let network_stack = network::setup_network(network_seed, interfaces.ap, spawner); let stack = network::setup_network(network_seed, interfaces.ap, spawner);
Timer::after(Duration::from_millis(1)).await; Timer::after(Duration::from_millis(1)).await;
let uart_device = setup_uart(peripherals.UART1, peripherals.GPIO16, peripherals.GPIO17)?; let uart_device = setup_uart(peripherals.UART1, peripherals.GPIO16, peripherals.GPIO17);
let i2c_device = setup_i2c(peripherals.I2C0, peripherals.GPIO22, peripherals.GPIO23)?; let i2c_device = setup_i2c(peripherals.I2C0, peripherals.GPIO22, peripherals.GPIO23);
let sd_det_gpio = peripherals.GPIO0; let sd_det_gpio = peripherals.GPIO0;
let rmt: Rmt<'_, esp_hal::Async> = {let frequency: Rate = Rate::from_mhz(80);
Rmt::new(peripherals.RMT, frequency)} .expect("Failed to initialize RMT")
.into_async();
let spi_bus = setup_spi( let spi_bus = setup_spi(
peripherals.SPI2, peripherals.SPI2,
peripherals.GPIO19, peripherals.GPIO19,
peripherals.GPIO20, peripherals.GPIO20,
peripherals.GPIO18, peripherals.GPIO18,
)?; );
let sd_cs_pin = Output::new( let sd_cs_pin = Output::new(
peripherals.GPIO2, peripherals.GPIO2,
@@ -141,45 +122,59 @@ impl AppHardware {
let vol_mgr = setup_sdcard(spi_bus, sd_cs_pin); let vol_mgr = setup_sdcard(spi_bus, sd_cs_pin);
let led_gpio = peripherals.GPIO1;
let buzzer_gpio = peripherals.GPIO21; let buzzer_gpio = peripherals.GPIO21;
let buzzer = setup_buzzer(buzzer_gpio);
let led = setup_led(peripherals.RMT, peripherals.GPIO1);
Timer::after(Duration::from_millis(500)).await; Timer::after(Duration::from_millis(500)).await;
debug!("hardware init done"); debug!("hardware init done");
Ok(Self { (
uart: uart_device, uart_device,
network_stack, stack,
i2c: i2c_device, i2c_device,
buzzer, rmt,
sd_present: sd_det_gpio.into(), led_gpio,
buzzer_gpio,
sd_det_gpio,
led, led,
sdcard: vol_mgr, vol_mgr,
}) )
}
} }
fn setup_uart( fn setup_uart(
uart1: UART1<'static>, uart1: UART1<'static>,
uart_tx: GPIO16<'static>, uart_tx: GPIO16<'static>,
uart_rx: GPIO17<'static>, uart_rx: GPIO17<'static>,
) -> Result<Uart<'static, Async>, esp_hal::uart::ConfigError> { ) -> Uart<'static, Async> {
let uart_device = Uart::new(uart1, esp_hal::uart::Config::default().with_baudrate(9600))?; let uard_device = Uart::new(uart1, esp_hal::uart::Config::default().with_baudrate(9600));
Ok(uart_device.with_rx(uart_rx).with_tx(uart_tx).into_async())
match uard_device {
Ok(block) => block.with_rx(uart_rx).with_tx(uart_tx).into_async(),
Err(e) => {
error!("Failed to initialize UART: {e}");
panic!(); //TODO panic!
}
}
} }
fn setup_i2c( fn setup_i2c(
i2c0: I2C0<'static>, i2c0: I2C0<'static>,
sda: GPIO22<'static>, sda: GPIO22<'static>,
scl: GPIO23<'static>, scl: GPIO23<'static>,
) -> Result<I2c<'static, Async>, esp_hal::i2c::master::ConfigError> { ) -> I2c<'static, Async> {
debug!("init I2C"); debug!("init I2C");
let config = Config::default().with_frequency(Rate::from_khz(400)); let config = Config::default().with_frequency(Rate::from_khz(400));
let i2c = match I2c::new(i2c0, config) {
let i2c = I2c::new(i2c0, config)?; Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(),
Err(e) => {
Ok(i2c.with_sda(sda).with_scl(scl).into_async()) error!("Failed to initialize I2C: {:?}", e);
panic!(); //TODO panic!
}
};
i2c
} }
fn setup_spi( fn setup_spi(
@@ -187,34 +182,35 @@ fn setup_spi(
sck: GPIO19<'static>, sck: GPIO19<'static>,
miso: GPIO20<'static>, miso: GPIO20<'static>,
mosi: GPIO18<'static>, mosi: GPIO18<'static>,
) -> Result<Spi<'static, Blocking>, esp_hal::spi::master::ConfigError> { ) -> Spi<'static, Blocking> {
let spi = Spi::new(spi2, Spi_config::default())?; let spi = match Spi::new(spi2, Spi_config::default()) {
Ok(spi.with_sck(sck).with_miso(miso).with_mosi(mosi)) Ok(spi) => spi.with_sck(sck).with_miso(miso).with_mosi(mosi),
Err(e) => panic!("Failed to initialize SPI: {:?}", e),
};
spi
} }
pub fn setup_buzzer(buzzer_gpio: GPIO21<'static>) -> Output<'static> { pub fn setup_buzzer(buzzer_gpio: GPIO21<'static>) -> Output<'static> {
let config = esp_hal::gpio::OutputConfig::default() let config = esp_hal::gpio::OutputConfig::default()
.with_drive_strength(esp_hal::gpio::DriveStrength::_40mA); .with_drive_strength(esp_hal::gpio::DriveStrength::_40mA);
let buzzer = Output::new(buzzer_gpio, esp_hal::gpio::Level::Low, config);
Output::new(buzzer_gpio, esp_hal::gpio::Level::Low, config) buzzer
} }
fn setup_led<'a>( fn setup_led<'a>(
rmt: RMT<'a>, rmt: RMT<'a>,
led_gpio: GPIO1<'a>, led_gpio: GPIO1<'a>,
) -> Result<esp_hal_smartled::SmartLedsAdapterAsync<'a, LED_BUFFER_SIZE>, esp_hal::rmt::Error> { ) -> esp_hal_smartled::SmartLedsAdapterAsync<'a, LED_BUFFER_SIZE> {
let rmt: Rmt<'_, esp_hal::Async> = { let rmt: Rmt<'_, esp_hal::Async> = {
let frequency: Rate = Rate::from_mhz(80); let frequency: Rate = Rate::from_mhz(80);
Rmt::new(rmt, frequency) Rmt::new(rmt, frequency)
}? }
.expect("Failed to initialize RMT")
.into_async(); .into_async();
let rmt_channel = rmt.channel0; let rmt_channel = rmt.channel0;
let rmt_buffer = [esp_hal::rmt::PulseCode::default(); LED_BUFFER_SIZE]; let rmt_buffer = [esp_hal::rmt::PulseCode::default(); LED_BUFFER_SIZE];
Ok(SmartLedsAdapterAsync::new( SmartLedsAdapterAsync::new(rmt_channel, led_gpio, rmt_buffer)
rmt_channel,
led_gpio,
rmt_buffer,
))
} }

View File

@@ -1,41 +1,48 @@
use core::{net::Ipv4Addr, str::FromStr};
use embassy_executor::Spawner;
use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_time::{Duration, Timer};
use esp_radio::wifi::WifiDevice;
use static_cell::make_static;
use crate::webserver::WEB_TAKS_SIZE;
pub const NETWORK_STACK_SIZE: usize = WEB_TAKS_SIZE + 2; // + 2 for other network taks. Breaks
// without
pub fn setup_network<'a>(seed: u64, wifi: WifiDevice<'static>, spawner: Spawner) -> Stack<'a> {
let gw_ip_addr_str = "192.168.2.1";
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
address: Ipv4Cidr::new(gw_ip_addr, 24),
gateway: Some(gw_ip_addr),
dns_servers: Default::default(),
});
let nw_stack: &'static mut StackResources<NETWORK_STACK_SIZE> =
make_static!(StackResources::<NETWORK_STACK_SIZE>::new());
let (stack, runner) = embassy_net::new(wifi, config, nw_stack, seed);
spawner.must_spawn(net_task(runner));
spawner.must_spawn(run_dhcp(stack, gw_ip_addr_str));
stack
}
#[embassy_executor::task]
async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
use core::net::{Ipv4Addr, SocketAddrV4}; use core::net::{Ipv4Addr, SocketAddrV4};
use edge_dhcp::{ use edge_dhcp::{
io::{self, DEFAULT_SERVER_PORT}, io::{self, DEFAULT_SERVER_PORT},
server::{Server, ServerOptions}, server::{Server, ServerOptions},
}; };
use edge_nal::UdpBind; use edge_nal::UdpBind;
use edge_nal_embassy::{Udp, UdpBuffers}; use edge_nal_embassy::{Udp, UdpBuffers};
use embassy_executor::Spawner;
use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_time::{Duration, Timer};
use esp_radio::wifi::WifiDevice;
use static_cell::StaticCell;
use crate::webserver::WEB_TAKS_SIZE; let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip");
pub const NETWORK_STACK_SIZE: usize = WEB_TAKS_SIZE + 2; // + 2 for other network taks. Breaks without
pub const GW_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 2, 1);
pub fn setup_network<'a>(seed: u64, wifi: WifiDevice<'static>, spawner: Spawner) -> Stack<'a> {
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
address: Ipv4Cidr::new(GW_IP, 24),
gateway: Some(GW_IP),
dns_servers: Default::default(),
});
static NETWORK_STACK: StaticCell<StackResources<NETWORK_STACK_SIZE>> = StaticCell::new();
let nw_stack = NETWORK_STACK.init(StackResources::new());
let (stack, runner) = embassy_net::new(wifi, config, nw_stack, seed);
spawner.must_spawn(net_task(runner));
spawner.must_spawn(run_dhcp(stack));
stack
}
#[embassy_executor::task]
async fn run_dhcp(stack: Stack<'static>) {
let mut buf = [0u8; 1500]; let mut buf = [0u8; 1500];
let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
@@ -48,12 +55,12 @@ async fn run_dhcp(stack: Stack<'static>) {
DEFAULT_SERVER_PORT, DEFAULT_SERVER_PORT,
))) )))
.await .await
.expect("Failed to bind socket for DHCP server"); .unwrap();
loop { loop {
_ = io::server::run( _ = io::server::run(
&mut Server::<_, 64>::new_with_et(GW_IP), &mut Server::<_, 64>::new_with_et(ip),
&ServerOptions::new(GW_IP, Some(&mut gw_buf)), &ServerOptions::new(ip, Some(&mut gw_buf)),
&mut bound_socket, &mut bound_socket,
&mut buf, &mut buf,
) )

View File

@@ -8,7 +8,8 @@ use esp_radio::wifi::{
}; };
use log::debug; use log::debug;
use static_cell::StaticCell; use static_cell::StaticCell;
use thiserror::Error;
static ESP_WIFI_CTRL: StaticCell<Controller<'static>> = StaticCell::new();
pub async fn set_antenna_mode(gpio3: GPIO3<'static>, gpio14: GPIO14<'static>) { pub async fn set_antenna_mode(gpio3: GPIO3<'static>, gpio14: GPIO14<'static>) {
let mut rf_switch = Output::new(gpio3, esp_hal::gpio::Level::Low, OutputConfig::default()); let mut rf_switch = Output::new(gpio3, esp_hal::gpio::Level::Low, OutputConfig::default());
@@ -22,45 +23,29 @@ pub async fn set_antenna_mode(gpio3: GPIO3<'static>, gpio14: GPIO14<'static>) {
antenna_mode.set_low(); antenna_mode.set_low();
} }
#[derive(Error, Debug)] pub fn setup_wifi<'d: 'static>(wifi: WIFI<'static>, spawner: Spawner) -> Interfaces<'d> {
pub enum WifiError { let esp_wifi_ctrl = ESP_WIFI_CTRL.init(esp_radio::init().unwrap());
#[error("Failed to init radio")]
Init(#[from] esp_radio::InitializationError),
#[error("Failed to init wifi")]
Wifi(#[from] esp_radio::wifi::WifiError),
#[error("Failed to spawn wifi task")]
Spawn(#[from] embassy_executor::SpawnError),
}
pub fn setup_wifi<'d: 'static>(
wifi: WIFI<'static>,
spawner: Spawner,
) -> Result<Interfaces<'d>, WifiError> {
static ESP_WIFI_CTRL: StaticCell<Controller<'static>> = StaticCell::new();
let esp_wifi_ctrl = ESP_WIFI_CTRL.init(esp_radio::init()?);
let config = esp_radio::wifi::Config::default(); let config = esp_radio::wifi::Config::default();
let (controller, interfaces) = esp_radio::wifi::new(esp_wifi_ctrl, wifi, config)?; let (controller, interfaces) = esp_radio::wifi::new(esp_wifi_ctrl, wifi, config).unwrap();
spawner.spawn(connection(controller))?; spawner.must_spawn(connection(controller));
Ok(interfaces) interfaces
} }
#[embassy_executor::task] #[embassy_executor::task]
async fn connection(mut controller: WifiController<'static>) { async fn connection(mut controller: WifiController<'static>) {
debug!("start connection task"); debug!("start connection task");
debug!("Device capabilities: {:?}", controller.capabilities()); debug!("Device capabilities: {:?}", controller.capabilities());
loop { loop {
if esp_radio::wifi::ap_state() == WifiApState::Started { match esp_radio::wifi::ap_state() {
WifiApState::Started => {
// wait until we're no longer connected // wait until we're no longer connected
controller.wait_for_event(WifiEvent::ApStop).await; controller.wait_for_event(WifiEvent::ApStop).await;
Timer::after(Duration::from_millis(5000)).await Timer::after(Duration::from_millis(5000)).await
} }
_ => {}
}
if !matches!(controller.is_started(), Ok(true)) { if !matches!(controller.is_started(), Ok(true)) {
let client_config = ModeConfig::AccessPoint( let client_config = ModeConfig::AccessPoint(
AccessPointConfig::default() AccessPointConfig::default()

View File

@@ -17,15 +17,15 @@ use embassy_sync::{
signal::Signal, signal::Signal,
}; };
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use esp_hal::gpio::InputConfig; use esp_hal::gpio::Input;
use esp_hal::gpio::{AnyPin, Input}; use esp_hal::{gpio::InputConfig, peripherals};
use log::{debug, info}; use log::{debug, info};
use static_cell::StaticCell; use static_cell::StaticCell;
extern crate alloc; extern crate alloc;
use crate::{ use crate::{
init::{hardware::AppHardware, sd_card::SDCardPersistence}, init::sd_card::SDCardPersistence,
store::{IDStore, day::Day, tally_id::TallyID}, store::{IDStore, day::Day, tally_id::TallyID},
webserver::start_webserver, webserver::start_webserver,
}; };
@@ -47,44 +47,36 @@ static CHAN: StaticCell<TallyChannel> = StaticCell::new();
#[esp_rtos::main] #[esp_rtos::main]
async fn main(spawner: Spawner) -> ! { async fn main(spawner: Spawner) -> ! {
let app_hardware = AppHardware::init(spawner).await.unwrap(); let (uart_device, stack, i2c, rmt, led_gpio, buzzer_gpio, sd_det_gpio, persistence_layer) =
init::hardware::hardware_init(spawner).await;
info!("Starting up..."); info!("Starting up...");
let mut rtc = drivers::rtc::RTCClock::new(app_hardware.i2c).await; let mut rtc = drivers::rtc::RTCClock::new(i2c).await;
let current_day: Day = rtc.get_time().await.into(); let store: UsedStore = IDStore::new_from_storage(persistence_layer).await;
let store: UsedStore = IDStore::new_from_storage(app_hardware.sdcard, current_day).await;
let shared_store = Rc::new(Mutex::new(store)); let shared_store = Rc::new(Mutex::new(store));
let chan: &'static mut TallyChannel = CHAN.init(PubSubChannel::new()); let chan: &'static mut TallyChannel = CHAN.init(PubSubChannel::new());
let publisher: TallyPublisher = chan.publisher().unwrap(); let publisher: TallyPublisher = chan.publisher().unwrap();
let mut sub: TallySubscriber = chan.subscriber().unwrap(); let mut sub: TallySubscriber = chan.subscriber().unwrap();
wait_for_stack_up(app_hardware.network_stack).await; wait_for_stack_up(stack).await;
start_webserver( start_webserver(spawner, stack, shared_store.clone(), chan);
spawner,
app_hardware.network_stack,
shared_store.clone(),
chan,
);
/****************************** Spawning tasks ***********************************/ /****************************** Spawning tasks ***********************************/
debug!("spawing NFC reader task..."); debug!("spawing NFC reader task...");
spawner.must_spawn(drivers::nfc_reader::rfid_reader_task( spawner.must_spawn(drivers::nfc_reader::rfid_reader_task(
app_hardware.uart, uart_device,
publisher, publisher,
)); ));
debug!("spawing feedback task.."); debug!("spawing feedback task..");
spawner.must_spawn(feedback::feedback_task( spawner.must_spawn(feedback::feedback_task(rmt, led_gpio, buzzer_gpio));
app_hardware.led,
app_hardware.buzzer,
));
debug!("spawn sd detect task"); debug!("spawn sd detect task");
spawner.must_spawn(sd_detect_task(app_hardware.sd_present)); spawner.must_spawn(sd_detect_task(sd_det_gpio));
/******************************************************************************/ /******************************************************************************/
debug!("everything spawned"); debug!("everything spawned");
@@ -109,7 +101,7 @@ async fn main(spawner: Spawner) -> ! {
} }
#[embassy_executor::task] #[embassy_executor::task]
async fn sd_detect_task(sd_det_gpio: AnyPin<'static>) { async fn sd_detect_task(sd_det_gpio: peripherals::GPIO0<'static>) {
let mut sd_det = Input::new(sd_det_gpio, InputConfig::default()); let mut sd_det = Input::new(sd_det_gpio, InputConfig::default());
sd_det.wait_for(esp_hal::gpio::Event::AnyEdge).await; sd_det.wait_for(esp_hal::gpio::Event::AnyEdge).await;

View File

@@ -40,12 +40,14 @@ pub struct IDStore<T: Persistence> {
} }
impl<T: Persistence> IDStore<T> { impl<T: Persistence> IDStore<T> {
pub async fn new_from_storage(mut persistence_layer: T, current_date: Day) -> Self { pub async fn new_from_storage(mut persistence_layer: T) -> Self {
let mapping = match persistence_layer.load_mapping().await { let mapping = match persistence_layer.load_mapping().await {
Some(map) => map, Some(map) => map,
None => IDMapping::new(), None => IDMapping::new(),
}; };
let current_date: Day = Day::new(1);
let day = persistence_layer let day = persistence_layer
.load_day(current_date) .load_day(current_date)
.await .await

14
web/package-lock.json generated
View File

@@ -19,7 +19,7 @@
"svelte": "^5.28.1", "svelte": "^5.28.1",
"svelte-check": "^4.1.6", "svelte-check": "^4.1.6",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"vite": "^6.4.1" "vite": "^6.3.5"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -771,7 +771,6 @@
"integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
"debug": "^4.4.0", "debug": "^4.4.0",
@@ -1101,7 +1100,6 @@
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2213,7 +2211,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -2580,7 +2577,6 @@
"integrity": "sha512-QIYtKnJGkubWXtNkrUBKVCvyo9gjcccdbnvXfwsGNhvbeNNdQjRDTa/BiQcJ2kWXbXPQbWKyT7CUu53KIj1rfw==", "integrity": "sha512-QIYtKnJGkubWXtNkrUBKVCvyo9gjcccdbnvXfwsGNhvbeNNdQjRDTa/BiQcJ2kWXbXPQbWKyT7CUu53KIj1rfw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.3.0", "@ampproject/remapping": "^2.3.0",
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -2704,7 +2700,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -2734,11 +2729,10 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.4.1", "version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.4",

View File

@@ -15,7 +15,7 @@
"svelte": "^5.28.1", "svelte": "^5.28.1",
"svelte-check": "^4.1.6", "svelte-check": "^4.1.6",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"vite": "^6.4.1", "vite": "^6.3.5",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",
"express": "^5.1.0" "express": "^5.1.0"
}, },