diff --git a/Cargo.lock b/Cargo.lock index d45527d..3482f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,6 +984,7 @@ name = "fw-anwesenheit" version = "0.1.0" dependencies = [ "bleps", + "chrono", "critical-section", "ds3231", "edge-dhcp", diff --git a/Cargo.toml b/Cargo.toml index c5b8974..4d4b8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,9 +60,9 @@ edge-nal-embassy = { version = "0.6.0", features = ["log"] } picoserve = { version = "0.16.0", features = ["embassy", "log"] } embassy-sync = { version = "0.7.0", features = ["log"] } -ds3231 = { version = "0.3.0", features = ["async"] } +ds3231 = { version = "0.3.0", features = ["async", "temperature_f32"] } ws2812-spi = "0.5.1" - +chrono = { version = "0.4.41", default-features = false } [profile.dev] # Rust debug is too slow. diff --git a/build.rs b/build.rs index 512e10e..10704fb 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,31 @@ +use std::env; +use std::path::Path; +use std::fs::File; +use std::io::Write; + fn main() { linker_be_nice(); // make sure linkall.x is the last linker script (otherwise might cause problems with flip-link) println!("cargo:rustc-link-arg=-Tlinkall.x"); + save_build_time(); +} + +fn save_build_time() { + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("build_time.rs"); + let system_time = std::time::SystemTime::now(); + let unix_time = system_time + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + println!("cargo:rustc-env=BUILD_TIME={}", unix_time); + let content = format!( + "/// compile time as UNIX-Timestamp (seconds since 1970-01-01) + pub const BUILD_UNIX_TIME: u64 = {};", + unix_time + ); + let mut f = File::create(dest_path).unwrap(); + f.write_all(content.as_bytes()).unwrap(); } fn linker_be_nice() { @@ -14,7 +38,9 @@ fn linker_be_nice() { "undefined-symbol" => match what.as_str() { "_defmt_timestamp" => { eprintln!(); - eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`"); + eprintln!( + "💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`" + ); eprintln!(); } "_stack_start" => { diff --git a/src/drivers.rs b/src/drivers.rs index 7fa4aa5..fefdb1e 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -1,2 +1,3 @@ pub mod nfc_reader; -pub mod rtc; \ No newline at end of file +pub mod rtc; +pub mod buzzer; diff --git a/src/drivers/buzzer.rs b/src/drivers/buzzer.rs new file mode 100644 index 0000000..3514967 --- /dev/null +++ b/src/drivers/buzzer.rs @@ -0,0 +1,20 @@ +use embassy_time::{Duration, Timer}; +use esp_hal::peripherals; +use log::{debug, error, info}; + +use crate::init; + + +#[embassy_executor::task] +pub async fn feedback_task(buzzer: peripherals::GPIO19<'static>) { + info!("Starting feedback task"); + let mut buzzer = init::hardware::setup_buzzer(buzzer).await; + loop { + debug!("Buzzer feedback task running"); + buzzer.set_high(); + Timer::after(Duration::from_millis(100)).await; + buzzer.set_low(); + Timer::after(Duration::from_millis(100)).await; + return ; + } +} diff --git a/src/drivers/rtc.rs b/src/drivers/rtc.rs index 525e551..c0dc6f5 100644 --- a/src/drivers/rtc.rs +++ b/src/drivers/rtc.rs @@ -1,12 +1,58 @@ +use ds3231::{Alarm1Config, DS3231Error, Seconds, DS3231}; +use embassy_time::{Timer, Duration}; +use esp_hal::{i2c::{self, master::I2c}, peripherals, Async}; +use log::{debug, error, info}; + +use crate::{drivers, init, UTC_TIME}; + +const RTC_ADDRESS: u8 = 0x57; + +#[embassy_executor::task] +async fn rtc_task( + i2c: i2c::master::I2c<'static, Async>, + sqw_pin: peripherals::GPIO21<'static>, +) { + debug!("init rtc interrupt"); + let mut rtc_interrupt = init::hardware::setup_rtc_iterrupt(sqw_pin).await; + debug!("configuring rtc"); + let mut rtc = drivers::rtc::rtc_config(i2c).await; + + let mut utc_time = UTC_TIME.lock().await; + let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await; + *utc_time = timestamp_result.unwrap_or(0); + + loop { + debug!("Waiting for RTC interrupt..."); + rtc_interrupt.wait_for_falling_edge().await; + debug!("RTC interrupt triggered"); + utc_time = UTC_TIME.lock().await; + let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await; + *utc_time = timestamp_result.unwrap_or(0); + Timer::after(Duration::from_secs(1)).await; // Debounce delay + } +} pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231> { let mut rtc: DS3231> = DS3231::new(i2c, RTC_ADDRESS); let daily_alarm = Alarm1Config::AtTime { hours: 0, // set alarm every day 00:00:00 to sync time minutes: 0, - seconds: 0, + seconds: 10, is_pm: None, // 24-hour mode }; + // Replace 'main::UTC_TIME' with the correct path to UTC_TIME, for example 'crate::UTC_TIME' + let mut utc_time; + { + utc_time = crate::UTC_TIME.lock().await; + } + + let naive_dt = chrono::NaiveDateTime::from_timestamp_opt(*utc_time as i64, 0) + .expect("Invalid timestamp for NaiveDateTime"); + rtc.set_datetime(&naive_dt).await.unwrap_or_else(|e| { + error!("Failed to set RTC datetime: {:?}", e); + panic!(); + }); + if let Err(e) = rtc.set_alarm1(&daily_alarm).await { error!("Failed to configure RTC: {:?}", e); panic!(); @@ -25,4 +71,19 @@ pub async fn read_rtc_time<'a>(rtc: &'a mut DS3231>) -> Resu Err(e) } } -} \ No newline at end of file +} + + +// TODO Update time when device is connected other device over Wifi +/* pub async fn update_rtc_time<'a>(rtc: &'a mut DS3231>, datetime: u64) -> Result<(), DS3231Error> { + + match rtc.set_datetime(datetime).await { + info!("RTC datetime updated to: {}", datetime); + Ok(_) => Ok(()), + Err(e) => { + error!("Failed to update RTC datetime: {:?}", e); + Err(e) + } + } +} + */ \ No newline at end of file diff --git a/src/feedback.rs b/src/feedback.rs index 4e85256..ee4fba9 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -1,180 +1,98 @@ -use anyhow::Result; -use log::error; -use rgb::RGB8; -use smart_leds::colors::{GREEN, RED}; -use std::time::Duration; -use tokio::{join, time::sleep}; -use crate::hardware::{Buzzer, StatusLed}; -#[cfg(not(feature = "mock_pi"))] -use crate::{hardware::GPIOBuzzer, hardware::SpiLed}; +/* pub async fn failure(&mut self) { + let buzzer_handle = Self::beep_nak(&mut self.buzzer); + let led_handle = Self::flash_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION); -#[cfg(feature = "mock_pi")] -use crate::hardware::{MockBuzzer, MockLed}; + let (buzzer_result, _) = join!(buzzer_handle, led_handle); -const LED_BLINK_DURATION: Duration = Duration::from_secs(1); + buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}"); + }); -pub enum DeviceStatus { - NotReady, - Ready, - HotspotEnabled, + let _ = self.led_to_status(); } -impl DeviceStatus { - pub fn color(&self) -> RGB8 { - match self { - Self::NotReady => RGB8::new(0, 0, 0), - Self::Ready => RGB8::new(0, 50, 0), - Self::HotspotEnabled => RGB8::new(0, 0, 50), - } - } -} -pub struct Feedback { - device_status: DeviceStatus, - buzzer: B, - led: L, +pub async fn activate_error_state(&mut self) -> Result<()> { + self.led.turn_on(RED)?; + Self::beep_nak(&mut self.buzzer).await?; + Ok(()) } -impl Feedback { - pub async fn success(&mut self) { - let buzzer_handle = Self::beep_ack(&mut self.buzzer); - let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, LED_BLINK_DURATION); - let (buzzer_result, _) = join!(buzzer_handle, led_handle); +pub async fn startup(&mut self){ + self.device_status = DeviceStatus::Ready; - buzzer_result.unwrap_or_else(|err| { - error!("Failed to buzz: {err}"); - }); + let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, Duration::from_secs(1)); + let buzzer_handle = Self::beep_startup(&mut self.buzzer); - let _ = self.led_to_status(); - } + let (buzzer_result, led_result) = join!(buzzer_handle, led_handle); - pub async fn failure(&mut self) { - let buzzer_handle = Self::beep_nak(&mut self.buzzer); - let led_handle = Self::flash_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION); + buzzer_result.unwrap_or_else(|err| { + error!("Failed to buzz: {err}"); + }); - let (buzzer_result, _) = join!(buzzer_handle, led_handle); + led_result.unwrap_or_else(|err| { + error!("Failed to blink led: {err}"); + }); - buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}"); - }); - - let _ = self.led_to_status(); - } - - pub async fn activate_error_state(&mut self) -> Result<()> { - self.led.turn_on(RED)?; - Self::beep_nak(&mut self.buzzer).await?; - Ok(()) - } - - pub async fn startup(&mut self){ - self.device_status = DeviceStatus::Ready; - - let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, Duration::from_secs(1)); - let buzzer_handle = Self::beep_startup(&mut self.buzzer); - - let (buzzer_result, led_result) = join!(buzzer_handle, led_handle); - - buzzer_result.unwrap_or_else(|err| { - error!("Failed to buzz: {err}"); - }); - - led_result.unwrap_or_else(|err| { - error!("Failed to blink led: {err}"); - }); - - let _ = self.led_to_status(); - } - - pub fn set_device_status(&mut self, status: DeviceStatus){ - self.device_status = status; - let _ = self.led_to_status(); - } - - fn led_to_status(&mut self) -> Result<()> { - self.led.turn_on(self.device_status.color()) - } - - async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> { - led.turn_on(color)?; - - sleep(duration).await; - - led.turn_off()?; - - Ok(()) - } - - async fn beep_ack(buzzer: &mut B) -> Result<()> { - buzzer - .modulated_tone(1200.0, Duration::from_millis(100)) - .await?; - sleep(Duration::from_millis(10)).await; - buzzer - .modulated_tone(2000.0, Duration::from_millis(50)) - .await?; - Ok(()) - } - - async fn beep_nak(buzzer: &mut B) -> Result<()> { - buzzer - .modulated_tone(600.0, Duration::from_millis(150)) - .await?; - sleep(Duration::from_millis(100)).await; - buzzer - .modulated_tone(600.0, Duration::from_millis(150)) - .await?; - Ok(()) - } - - async fn beep_startup(buzzer: &mut B) -> Result<()> { - buzzer - .modulated_tone(523.0, Duration::from_millis(150)) - .await?; - buzzer - .modulated_tone(659.0, Duration::from_millis(150)) - .await?; - buzzer - .modulated_tone(784.0, Duration::from_millis(150)) - .await?; - buzzer - .modulated_tone(1046.0, Duration::from_millis(200)) - .await?; - - sleep(Duration::from_millis(100)).await; - - buzzer - .modulated_tone(784.0, Duration::from_millis(100)) - .await?; - buzzer - .modulated_tone(880.0, Duration::from_millis(200)) - .await?; - Ok(()) - } + let _ = self.led_to_status(); } -#[cfg(feature = "mock_pi")] -pub type FeedbackImpl = Feedback; -#[cfg(not(feature = "mock_pi"))] -pub type FeedbackImpl = Feedback; -impl FeedbackImpl { - pub fn new() -> Result { - #[cfg(feature = "mock_pi")] - { - Ok(Feedback { - device_status: DeviceStatus::NotReady, - buzzer: MockBuzzer {}, - led: MockLed {}, - }) - } - #[cfg(not(feature = "mock_pi"))] - { - Ok(Feedback { - device_status: DeviceStatus::NotReady, - buzzer: GPIOBuzzer::new_default()?, - led: SpiLed::new()?, - }) - } - } +async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> { + led.turn_on(color)?; + + sleep(duration).await; + + led.turn_off()?; + + Ok(()) } + +async fn beep_ack(buzzer: &mut B) -> Result<()> { + buzzer + .modulated_tone(1200.0, Duration::from_millis(100)) + .await?; + sleep(Duration::from_millis(10)).await; + buzzer + .modulated_tone(2000.0, Duration::from_millis(50)) + .await?; + Ok(()) +} + +async fn beep_nak(buzzer: &mut B) -> Result<()> { + buzzer + .modulated_tone(600.0, Duration::from_millis(150)) + .await?; + sleep(Duration::from_millis(100)).await; + buzzer + .modulated_tone(600.0, Duration::from_millis(150)) + .await?; + Ok(()) +} + +async fn beep_startup(buzzer: &mut B) -> Result<()> { + buzzer + .modulated_tone(523.0, Duration::from_millis(150)) + .await?; + buzzer + .modulated_tone(659.0, Duration::from_millis(150)) + .await?; + buzzer + .modulated_tone(784.0, Duration::from_millis(150)) + .await?; + buzzer + .modulated_tone(1046.0, Duration::from_millis(200)) + .await?; + + sleep(Duration::from_millis(100)).await; + + buzzer + .modulated_tone(784.0, Duration::from_millis(100)) + .await?; + buzzer + .modulated_tone(880.0, Duration::from_millis(200)) + .await?; + Ok(()) +} + + */ \ No newline at end of file diff --git a/src/init/hardware.rs b/src/init/hardware.rs index ee59444..2bcb800 100644 --- a/src/init/hardware.rs +++ b/src/init/hardware.rs @@ -1,13 +1,10 @@ -use core::slice::RChunks; - -use ds3231::{Alarm1Config, DS3231Error, DS3231}; use embassy_executor::Spawner; use embassy_net::{driver, Stack}; use embassy_sync::mutex::Mutex; use esp_hal::config; use esp_hal::gpio::{Input, Pull}; use esp_hal::i2c::master::Config; -use esp_hal::peripherals::{self, GPIO0, GPIO1, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO21, GPIO22, GPIO23, I2C0, UART1}; +use esp_hal::peripherals::{self, GPIO0, GPIO1, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO19, GPIO21, GPIO22, GPIO23, I2C0, UART1}; use esp_hal::time::Rate; use esp_hal::{ Async, @@ -18,7 +15,7 @@ use esp_hal::{ gpio::{Output, OutputConfig} }; use esp_println::logger::init_logger; -use log::error; +use log::{debug, error}; use crate::init::wifi; use crate::init::network; @@ -26,17 +23,15 @@ use crate::init::network; /************************************************* * GPIO Pinout Xiao Esp32c6 * - * D0 -> Level Shifter OE - * D1 -> Level Shifter A0 -> LED - * D3 -> SQW Interrupt RTC - * D4 -> SDA - * D5 -> SCL - * D7 -> Level Shifter A1 -> NFC Reader - * D8 -> Buzzer + * D0 -> GPIO0 -> Level Shifter OE + * D1 -> GPIO1 -> Level Shifter A0 -> LED + * D3 -> GPIO21 -> SQW Interrupt RTC + * D4 -> GPIO22 -> SDA + * D5 -> GPIO23 -> SCL + * D7 -> GPIO17 -> Level Shifter A1 -> NFC Reader + * D8 -> GPIO19 -> Buzzer * - */ - -const RTC_ADDRESS: u8 = 0x57; + *************************************************/ #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { @@ -45,7 +40,7 @@ fn panic(_: &core::panic::PanicInfo) -> ! { esp_bootloader_esp_idf::esp_app_desc!(); -pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stack<'static>, I2c<'static, Async>, GPIO21<'static>) { +pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stack<'static>, I2c<'static, Async>, GPIO21<'static>, GPIO19<'static>) { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); @@ -73,9 +68,11 @@ pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stac //RTC Interrupt pin let sqw_pin = peripherals.GPIO21; - //TODO change to get I2C device back / maybe init for each protocol + let buzzer_gpio = peripherals.GPIO19; - (uart_device, stack, i2c_device, sqw_pin) + debug!("hardware init done"); + + (uart_device, stack, i2c_device, sqw_pin, buzzer_gpio) } // Initialize the level shifter for the NFC reader and LED (output-enable (OE) input is low, all outputs are placed in the high-impedance (Hi-Z) state) @@ -105,7 +102,7 @@ fn setup_i2c( sda: GPIO22<'static>, scl: GPIO23<'static>, ) -> I2c<'static, Async> { - + debug!("init I2C"); let config = Config::default().with_frequency(Rate::from_khz(400)); let i2c = match I2c::new(i2c0, config) { Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(), @@ -117,12 +114,20 @@ fn setup_i2c( i2c } -pub async fn rtc_init_iterrupt(sqw_pin: GPIO21<'static>) -> Input<'static> { +pub async fn setup_rtc_iterrupt(sqw_pin: GPIO21<'static>) -> Input<'static> { + debug!("init rtc interrupt"); let config = esp_hal::gpio::InputConfig::default().with_pull(Pull::Up); //Active low interrupt in rtc let sqw_interrupt = Input::new(sqw_pin, config); sqw_interrupt } +pub async fn setup_buzzer(buzzer_gpio: GPIO19<'static>) -> Output<'static> { + let config = esp_hal::gpio::OutputConfig::default().with_drive_strength(esp_hal::gpio::DriveStrength::_40mA); + let buzzer = Output::new(buzzer_gpio, esp_hal::gpio::Level::Low, config); + + buzzer +} + fn setup_spi_led() { diff --git a/src/main.rs b/src/main.rs index 57db22a..d854788 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,17 +13,20 @@ use embassy_sync::{ }; use embassy_time::{Duration, Timer}; use esp_alloc::psram_allocator; -use esp_hal::{i2c, peripherals, Async}; +use esp_hal::{gpio::Output, i2c, peripherals, Async}; use esp_hal::uart::Uart; use log::{debug, info}; use static_cell::make_static; use crate::{store::TallyID, webserver::start_webserver}; +include!(concat!(env!("OUT_DIR"), "/build_time.rs")); + mod init; mod drivers; mod store; mod webserver; +mod feedback; type TallyChannel = PubSubChannel; type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>; @@ -32,7 +35,13 @@ static UTC_TIME: Mutex = Mutex::new(0); #[esp_hal_embassy::main] async fn main(mut spawner: Spawner) { - let (uart_device, stack, i2c, sqw_pin) = init::hardware::hardware_init(&mut spawner).await; + { + let mut utc_time = UTC_TIME.lock().await; + *utc_time = BUILD_UNIX_TIME; + info!("UTC Time initialized to: {}", *utc_time); + } + + let (uart_device, stack, _i2c, sqw_pin, buzzer_gpio) = init::hardware::hardware_init(&mut spawner).await; wait_for_stack_up(stack).await; @@ -42,8 +51,12 @@ async fn main(mut spawner: Spawner) { let publisher = chan.publisher().unwrap(); + debug!("spawing NFC reader task"); spawner.must_spawn(drivers::nfc_reader::rfid_reader_task(uart_device, publisher)); - spawner.must_spawn(rtc_task(i2c, sqw_pin)); + //debug!("spawing rtc task"); + //spawner.must_spawn(rtc_task(_i2c, sqw_pin)); + debug!("spawing feedback task"); + spawner.must_spawn(drivers::buzzer::feedback_task(buzzer_gpio)); let mut sub = chan.subscriber().unwrap(); loop { @@ -66,25 +79,4 @@ async fn wait_for_stack_up(stack: Stack<'static>) { } Timer::after(Duration::from_millis(500)).await; } -} - -#[embassy_executor::task] -async fn rtc_task( - i2c: i2c::master::I2c<'static, Async>, - sqw_pin: peripherals::GPIO21<'static>, -) { - let mut rtc_interrupt = init::hardware::rtc_init_iterrupt(sqw_pin).await; - let mut rtc = drivers::rtc::rtc_config(i2c).await; - - let mut utc_time = UTC_TIME.lock().await; - let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await; - *utc_time = timestamp_result.unwrap_or(0); - - loop { - rtc_interrupt.wait_for_falling_edge().await; - debug!("RTC interrupt triggered"); - utc_time = UTC_TIME.lock().await; - let timestamp_result = drivers::rtc::read_rtc_time(&mut rtc).await; - *utc_time = timestamp_result.unwrap_or(0); - } } \ No newline at end of file