mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2026-05-01 02:59:09 +00:00
Compare commits
3 Commits
5a2beb1fb3
...
fe6540ca3d
| Author | SHA1 | Date | |
|---|---|---|---|
| fe6540ca3d | |||
|
|
161ebf9bd2 | ||
|
|
c1b54920ff |
31
Cargo.lock
generated
31
Cargo.lock
generated
@@ -266,6 +266,19 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dir-embed"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "322ab0f130706b5a06c6f0fe92e7d6453ed0829779d7545a054e291684971712"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "document-features"
|
name = "document-features"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@@ -984,7 +997,9 @@ name = "fw-anwesenheit"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bleps",
|
"bleps",
|
||||||
|
"chrono",
|
||||||
"critical-section",
|
"critical-section",
|
||||||
|
"dir-embed",
|
||||||
"ds3231",
|
"ds3231",
|
||||||
"edge-dhcp",
|
"edge-dhcp",
|
||||||
"edge-nal",
|
"edge-nal",
|
||||||
@@ -1234,6 +1249,22 @@ version = "2.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ edge-nal = "0.5.0"
|
|||||||
edge-nal-embassy = { version = "0.6.0", features = ["log"] }
|
edge-nal-embassy = { version = "0.6.0", features = ["log"] }
|
||||||
picoserve = { version = "0.16.0", features = ["embassy", "log"] }
|
picoserve = { version = "0.16.0", features = ["embassy", "log"] }
|
||||||
embassy-sync = { version = "0.7.0", features = ["log"] }
|
embassy-sync = { version = "0.7.0", features = ["log"] }
|
||||||
|
ds3231 = { version = "0.3.0", features = ["async", "temperature_f32"] }
|
||||||
ds3231 = { version = "0.3.0", features = ["async"] }
|
|
||||||
ws2812-spi = "0.5.1"
|
ws2812-spi = "0.5.1"
|
||||||
|
chrono = { version = "0.4.41", default-features = false }
|
||||||
|
dir-embed = "0.3.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Rust debug is too slow.
|
# Rust debug is too slow.
|
||||||
|
|||||||
28
build.rs
28
build.rs
@@ -1,7 +1,31 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
linker_be_nice();
|
linker_be_nice();
|
||||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
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() {
|
fn linker_be_nice() {
|
||||||
@@ -14,7 +38,9 @@ fn linker_be_nice() {
|
|||||||
"undefined-symbol" => match what.as_str() {
|
"undefined-symbol" => match what.as_str() {
|
||||||
"_defmt_timestamp" => {
|
"_defmt_timestamp" => {
|
||||||
eprintln!();
|
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!();
|
eprintln!();
|
||||||
}
|
}
|
||||||
"_stack_start" => {
|
"_stack_start" => {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod nfc_reader;
|
pub mod nfc_reader;
|
||||||
pub mod rtc;
|
pub mod rtc;
|
||||||
|
pub mod buzzer;
|
||||||
|
|||||||
20
src/drivers/buzzer.rs
Normal file
20
src/drivers/buzzer.rs
Normal file
@@ -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 ;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,89 @@
|
|||||||
|
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<I2c<'static, Async>> {
|
||||||
|
let mut rtc: DS3231<I2c<'static, Async>> = 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: 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!();
|
||||||
|
}
|
||||||
|
rtc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_rtc_time<'a>(rtc: &'a mut DS3231<I2c<'static, Async>>) -> Result<u64, DS3231Error<esp_hal::i2c::master::Error>> {
|
||||||
|
match rtc.datetime().await {
|
||||||
|
Ok(datetime) => {
|
||||||
|
let utc_time = datetime.and_utc().timestamp() as u64;
|
||||||
|
Ok(utc_time)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to read RTC datetime: {:?}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO Update time when device is connected other device over Wifi
|
||||||
|
/* pub async fn update_rtc_time<'a>(rtc: &'a mut DS3231<I2c<'static, Async>>, datetime: u64) -> Result<(), DS3231Error<esp_hal::i2c::master::Error>> {
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
242
src/feedback.rs
242
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"))]
|
/* pub async fn failure(&mut self) {
|
||||||
use crate::{hardware::GPIOBuzzer, hardware::SpiLed};
|
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")]
|
let (buzzer_result, _) = join!(buzzer_handle, led_handle);
|
||||||
use crate::hardware::{MockBuzzer, MockLed};
|
|
||||||
|
|
||||||
const LED_BLINK_DURATION: Duration = Duration::from_secs(1);
|
buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}");
|
||||||
|
});
|
||||||
|
|
||||||
pub enum DeviceStatus {
|
let _ = self.led_to_status();
|
||||||
NotReady,
|
|
||||||
Ready,
|
|
||||||
HotspotEnabled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceStatus {
|
pub async fn activate_error_state(&mut self) -> Result<()> {
|
||||||
pub fn color(&self) -> RGB8 {
|
self.led.turn_on(RED)?;
|
||||||
match self {
|
Self::beep_nak(&mut self.buzzer).await?;
|
||||||
Self::NotReady => RGB8::new(0, 0, 0),
|
Ok(())
|
||||||
Self::Ready => RGB8::new(0, 50, 0),
|
|
||||||
Self::HotspotEnabled => RGB8::new(0, 0, 50),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct Feedback<B: Buzzer, L: StatusLed> {
|
|
||||||
device_status: DeviceStatus,
|
|
||||||
buzzer: B,
|
|
||||||
led: L,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
pub async fn startup(&mut self){
|
||||||
pub async fn success(&mut self) {
|
self.device_status = DeviceStatus::Ready;
|
||||||
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);
|
|
||||||
|
|
||||||
buzzer_result.unwrap_or_else(|err| {
|
let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, Duration::from_secs(1));
|
||||||
error!("Failed to buzz: {err}");
|
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) {
|
buzzer_result.unwrap_or_else(|err| {
|
||||||
let buzzer_handle = Self::beep_nak(&mut self.buzzer);
|
error!("Failed to buzz: {err}");
|
||||||
let led_handle = Self::flash_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION);
|
});
|
||||||
|
|
||||||
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();
|
||||||
});
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mock_pi")]
|
|
||||||
pub type FeedbackImpl = Feedback<MockBuzzer, MockLed>;
|
|
||||||
#[cfg(not(feature = "mock_pi"))]
|
|
||||||
pub type FeedbackImpl = Feedback<GPIOBuzzer, SpiLed>;
|
|
||||||
|
|
||||||
impl FeedbackImpl {
|
async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> {
|
||||||
pub fn new() -> Result<Self> {
|
led.turn_on(color)?;
|
||||||
#[cfg(feature = "mock_pi")]
|
|
||||||
{
|
sleep(duration).await;
|
||||||
Ok(Feedback {
|
|
||||||
device_status: DeviceStatus::NotReady,
|
led.turn_off()?;
|
||||||
buzzer: MockBuzzer {},
|
|
||||||
led: MockLed {},
|
Ok(())
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "mock_pi"))]
|
|
||||||
{
|
|
||||||
Ok(Feedback {
|
|
||||||
device_status: DeviceStatus::NotReady,
|
|
||||||
buzzer: GPIOBuzzer::new_default()?,
|
|
||||||
led: SpiLed::new()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
use core::slice::RChunks;
|
|
||||||
|
|
||||||
use ds3231::{Alarm1Config, DS3231Error, DS3231};
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_net::{driver, Stack};
|
use embassy_net::{driver, Stack};
|
||||||
use embassy_sync::mutex::Mutex;
|
use embassy_sync::mutex::Mutex;
|
||||||
use esp_hal::config;
|
use esp_hal::config;
|
||||||
use esp_hal::gpio::{Input, Pull};
|
use esp_hal::gpio::{Input, Pull};
|
||||||
use esp_hal::i2c::master::Config;
|
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::time::Rate;
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
Async,
|
Async,
|
||||||
@@ -18,12 +15,23 @@ use esp_hal::{
|
|||||||
gpio::{Output, OutputConfig}
|
gpio::{Output, OutputConfig}
|
||||||
};
|
};
|
||||||
use esp_println::logger::init_logger;
|
use esp_println::logger::init_logger;
|
||||||
use log::error;
|
use log::{debug, error};
|
||||||
|
|
||||||
use crate::init::wifi;
|
use crate::init::wifi;
|
||||||
use crate::init::network;
|
use crate::init::network;
|
||||||
|
|
||||||
const RTC_ADDRESS: u8 = 0x57;
|
/*************************************************
|
||||||
|
* GPIO Pinout Xiao Esp32c6
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_: &core::panic::PanicInfo) -> ! {
|
fn panic(_: &core::panic::PanicInfo) -> ! {
|
||||||
@@ -32,7 +40,7 @@ fn panic(_: &core::panic::PanicInfo) -> ! {
|
|||||||
|
|
||||||
esp_bootloader_esp_idf::esp_app_desc!();
|
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 config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||||
let peripherals = esp_hal::init(config);
|
let peripherals = esp_hal::init(config);
|
||||||
|
|
||||||
@@ -60,9 +68,11 @@ pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stac
|
|||||||
//RTC Interrupt pin
|
//RTC Interrupt pin
|
||||||
let sqw_pin = peripherals.GPIO21;
|
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)
|
// 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)
|
||||||
@@ -92,7 +102,7 @@ fn setup_i2c(
|
|||||||
sda: GPIO22<'static>,
|
sda: GPIO22<'static>,
|
||||||
scl: GPIO23<'static>,
|
scl: GPIO23<'static>,
|
||||||
) -> I2c<'static, Async> {
|
) -> I2c<'static, Async> {
|
||||||
|
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 = match I2c::new(i2c0, config) {
|
||||||
Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(),
|
Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(),
|
||||||
@@ -104,38 +114,18 @@ fn setup_i2c(
|
|||||||
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 config = esp_hal::gpio::InputConfig::default().with_pull(Pull::Up); //Active low interrupt in rtc
|
||||||
let sqw_interrupt = Input::new(sqw_pin, config);
|
let sqw_interrupt = Input::new(sqw_pin, config);
|
||||||
sqw_interrupt
|
sqw_interrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> {
|
pub async fn setup_buzzer(buzzer_gpio: GPIO19<'static>) -> Output<'static> {
|
||||||
let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);
|
let config = esp_hal::gpio::OutputConfig::default().with_drive_strength(esp_hal::gpio::DriveStrength::_40mA);
|
||||||
let daily_alarm = Alarm1Config::AtTime {
|
let buzzer = Output::new(buzzer_gpio, esp_hal::gpio::Level::Low, config);
|
||||||
hours: 0, // set alarm every day 00:00:00 to sync time
|
|
||||||
minutes: 0,
|
|
||||||
seconds: 0,
|
|
||||||
is_pm: None, // 24-hour mode
|
|
||||||
};
|
|
||||||
if let Err(e) = rtc.set_alarm1(&daily_alarm).await {
|
|
||||||
error!("Failed to configure RTC: {:?}", e);
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
rtc
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_rtc_time<'a>(rtc: &'a mut DS3231<I2c<'static, Async>>) -> Result<u64, DS3231Error<esp_hal::i2c::master::Error>> {
|
buzzer
|
||||||
match rtc.datetime().await {
|
|
||||||
Ok(datetime) => {
|
|
||||||
let utc_time = datetime.and_utc().timestamp() as u64;
|
|
||||||
Ok(utc_time)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to read RTC datetime: {:?}", e);
|
|
||||||
Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
40
src/main.rs
40
src/main.rs
@@ -13,17 +13,20 @@ use embassy_sync::{
|
|||||||
};
|
};
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use esp_alloc::psram_allocator;
|
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 esp_hal::uart::Uart;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use static_cell::make_static;
|
use static_cell::make_static;
|
||||||
|
|
||||||
use crate::{store::TallyID, webserver::start_webserver};
|
use crate::{store::TallyID, webserver::start_webserver};
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
|
||||||
|
|
||||||
mod init;
|
mod init;
|
||||||
mod drivers;
|
mod drivers;
|
||||||
mod store;
|
mod store;
|
||||||
mod webserver;
|
mod webserver;
|
||||||
|
mod feedback;
|
||||||
|
|
||||||
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
|
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
|
||||||
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
|
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
|
||||||
@@ -32,7 +35,13 @@ static UTC_TIME: Mutex<CriticalSectionRawMutex, u64> = Mutex::new(0);
|
|||||||
|
|
||||||
#[esp_hal_embassy::main]
|
#[esp_hal_embassy::main]
|
||||||
async fn main(mut spawner: Spawner) {
|
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;
|
wait_for_stack_up(stack).await;
|
||||||
|
|
||||||
@@ -42,8 +51,12 @@ async fn main(mut spawner: Spawner) {
|
|||||||
|
|
||||||
let publisher = chan.publisher().unwrap();
|
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(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();
|
let mut sub = chan.subscriber().unwrap();
|
||||||
loop {
|
loop {
|
||||||
@@ -67,24 +80,3 @@ async fn wait_for_stack_up(stack: Stack<'static>) {
|
|||||||
Timer::after(Duration::from_millis(500)).await;
|
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 = init::hardware::rtc_config(i2c).await;
|
|
||||||
|
|
||||||
let mut utc_time = UTC_TIME.lock().await;
|
|
||||||
let timestamp_result = init::hardware::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 = init::hardware::read_rtc_time(&mut rtc).await;
|
|
||||||
*utc_time = timestamp_result.unwrap_or(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
77
src/webserver/assets.rs
Normal file
77
src/webserver/assets.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
use dir_embed::Embed;
|
||||||
|
use picoserve::response::Content;
|
||||||
|
|
||||||
|
#[derive(Embed)]
|
||||||
|
#[dir = "../../web/dist"]
|
||||||
|
#[mode = "mime"]
|
||||||
|
pub struct Assets;
|
||||||
|
|
||||||
|
impl<State, CurrentPathParameters>
|
||||||
|
picoserve::routing::PathRouterService<State, CurrentPathParameters> for Assets
|
||||||
|
{
|
||||||
|
async fn call_request_handler_service<
|
||||||
|
R: picoserve::io::embedded_io_async::Read,
|
||||||
|
W: picoserve::response::ResponseWriter<Error = R::Error>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
state: &State,
|
||||||
|
current_path_parameters: CurrentPathParameters,
|
||||||
|
path: picoserve::request::Path<'_>,
|
||||||
|
request: picoserve::request::Request<'_, R>,
|
||||||
|
response_writer: W,
|
||||||
|
) -> Result<picoserve::ResponseSent, W::Error> {
|
||||||
|
let requested_path = path.encoded();
|
||||||
|
|
||||||
|
let requested_file = if requested_path == "/" {
|
||||||
|
Self::get("index.html")
|
||||||
|
} else if let Some(striped_path) = requested_path.strip_prefix("/") {
|
||||||
|
Self::get(striped_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match requested_file {
|
||||||
|
Some(content) => {
|
||||||
|
let response = picoserve::response::Response::new(
|
||||||
|
picoserve::response::StatusCode::OK,
|
||||||
|
StaticAsset(content.0, content.1),
|
||||||
|
);
|
||||||
|
|
||||||
|
response_writer
|
||||||
|
.write_response(request.body_connection.finalize().await.unwrap(), response)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
use picoserve::routing::PathRouter;
|
||||||
|
picoserve::routing::NotFound
|
||||||
|
.call_path_router(
|
||||||
|
state,
|
||||||
|
current_path_parameters,
|
||||||
|
path,
|
||||||
|
request,
|
||||||
|
response_writer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StaticAsset(pub &'static [u8], pub &'static str);
|
||||||
|
|
||||||
|
impl Content for StaticAsset {
|
||||||
|
fn content_type(&self) -> &'static str {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn content_length(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_content<W: embedded_io_async::Write>(
|
||||||
|
self,
|
||||||
|
mut writer: W,
|
||||||
|
) -> Result<(), W::Error> {
|
||||||
|
writer.write_all(self.0).await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ use embassy_time::Duration;
|
|||||||
use picoserve::{AppBuilder, AppRouter, routing::get};
|
use picoserve::{AppBuilder, AppRouter, routing::get};
|
||||||
use static_cell::make_static;
|
use static_cell::make_static;
|
||||||
|
|
||||||
|
mod assets;
|
||||||
|
|
||||||
pub fn start_webserver(spawner: &mut Spawner, stack: Stack<'static>) {
|
pub fn start_webserver(spawner: &mut Spawner, stack: Stack<'static>) {
|
||||||
let app = make_static!(AppProps.build_app());
|
let app = make_static!(AppProps.build_app());
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ impl AppBuilder for AppProps {
|
|||||||
type PathRouter = impl picoserve::routing::PathRouter;
|
type PathRouter = impl picoserve::routing::PathRouter;
|
||||||
|
|
||||||
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
|
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
|
||||||
picoserve::Router::new().route("/", get(|| async move { "Hello World" }))
|
picoserve::Router::from_service(assets::Assets).route("/api/a", get(async move || "Hello"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user