Compare commits

...

3 Commits

Author SHA1 Message Date
Psenfft
2078a3bab0 Update README with images and remove setup details
Removed setup instructions and added images.
2025-10-04 16:17:44 +02:00
Philipp_EndevourOS
7e59d836a1 added get date method for rtc 2025-10-04 15:46:30 +02:00
Philipp_EndevourOS
09f21403ec changed Date to u8 array 2025-10-04 15:45:56 +02:00
6 changed files with 75 additions and 41 deletions

View File

@@ -1,32 +1,6 @@
# fw-anwesenheit
# Setup
![PXL_20251004_141110955 MP (1)](https://github.com/user-attachments/assets/afffd664-507d-439a-a428-2477b2fe4de2)
In order to use the LED we need to enable the SPI interface on the Rpi.
You can enable it by running `sudo raspi-config`, or by manually adding `dtparam=spi=on` to `/boot/firmware/config.txt`.
Enable PWM -> add dtoverlay=pwm to /boot/config.txt
I²C fpr RTC `sudo raspi-config` -> interface -> enable I²C
![PXL_20251001_103957322 MP~2](https://github.com/user-attachments/assets/8bc182df-a93d-4923-b8d8-88b56d1aa441)
# Config
Flags:
`--error` or `-e`: Enters error state. The LED turns red and the hotspot is activated. This state gets called from systemd if the service is in a failure state.
Environment variables:
- `PM3_BIN`: Path to the pm3 binary. Seach in path if not set. Can also be set to the `pm3_mock.sh` for testing.
- `LOG_LEVEL`: Can be set to either "debug","warn","error","trace" or "info". Defaults to "warn" in production.
- `HTTP_PORT`: What port to listen on. Defaults to 80.
- `HOTSPOT_IDS`: A semicolon seperated list of ids to activate the hotspot with e.g. `578B5DF2;c1532b57`.
- `HOTSPOT_SSID`: Set the hotspot ssid. Defaults to "fwa".
- `HOTSPOT_PW`: Set the hotspot password. Default to "a9LG2kUVrsRRVUo1". Recommended to change.
Systemd:
The service is run as a systemd service. There are two service `fwa.service` and `fwa-fail.service`. They read their config
from a env file located at `/etc/fwa.env`. See example [env file](service/fwa.env).
# Building
Run `make package` to create `.deb` file. [Cross](https://github.com/cross-rs/cross) is used for building the rust code.

View File

@@ -8,13 +8,17 @@ use esp_hal::{
};
use log::{debug, error, info};
use crate::{FEEDBACK_STATE, drivers, feedback};
use crate::{FEEDBACK_STATE, drivers, feedback, store::Date};
use chrono::{TimeZone, Utc};
include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
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 {
dev: DS3231<I2c<'static, Async>>,
}
@@ -41,8 +45,61 @@ impl RTCClock {
}
}
}
pub async fn get_date(&mut self) -> Date {
let (year, month, day) = unix_to_ymd_string(self.get_time().await);
let mut buffer: Date = [0; 10] ;
// Write YYYY
buffer[0] = b'0' + ((year / 1000) % 10) as u8;
buffer[1] = b'0' + ((year / 100) % 10) as u8;
buffer[2] = b'0' + ((year / 10) % 10) as u8;
buffer[3] = b'0' + (year % 10) as u8;
buffer[4] = b'.';
// Write MM
buffer[5] = b'0' + (month / 10) as u8;
buffer[6] = b'0' + (month % 10) as u8;
buffer[7] = b'.';
// Write DD
buffer[8] = b'0' + (day / 10) as u8;
buffer[9] = b'0' + (day % 10) as u8;
buffer
}
}
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>> {
let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);
let naive_dt = Utc
@@ -62,8 +119,9 @@ pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>>
match rtc.configure(&rtc_config).await {
Ok(_) => info!("DS3231 configured successfully"),
Err(e) => {
info!("Failed to configure DS3231: {:?}", e);
panic!("DS3231 configuration failed");
error!("Failed to configure DS3231: {:?}", e);
error!("DS3231 configuration failed");
FEEDBACK_STATE.signal(feedback::FeedbackState::Error);
}
}

View File

@@ -83,10 +83,10 @@ impl Persistence for SDCardPersistence {
let mut root_dir = vol_0.open_root_dir().unwrap();
let mut days_dir = root_dir.open_dir("days").unwrap();
let mut days = Vec::new();
let mut days: Vec<[u8; 10]> = Vec::new();
days_dir
.iterate_dir(|e| {
days.push(1);
days.push([0; 10]);
})
.unwrap();

View File

@@ -20,7 +20,7 @@ use esp_hal::{gpio::InputConfig, peripherals};
use log::{debug, info};
use static_cell::make_static;
use crate::store::{IDStore, TallyID};
use crate::store::{Date, IDStore, TallyID};
extern crate alloc;
@@ -80,7 +80,8 @@ async fn main(mut spawner: Spawner) {
Message(msg) => {
debug!("Got message: {msg:?}");
let added = store.add_id(msg).await;
let day: Date = rtc.get_date().await;
let added = store.add_id(msg, day).await;
if added {
FEEDBACK_STATE.signal(feedback::FeedbackState::Ack);

View File

@@ -1,3 +1,5 @@
use crate::drivers::rtc;
use crate::drivers::rtc::RTCClock;
use crate::store::persistence::Persistence;
use super::Date;
@@ -46,7 +48,7 @@ impl<T: Persistence> IDStore<T> {
// None => IDMapping::new(),
// };
let current_date: Date = 1;
let current_date: Date = [0; 10];
let day = persistence_layer
.load_day(current_date)
@@ -72,8 +74,7 @@ impl<T: Persistence> IDStore<T> {
/// Add a new id for the current day
/// Returns false if ID is already present at the current day.
pub async fn add_id(&mut self, id: TallyID) -> bool {
let current_date: Date = 1;
pub async fn add_id(&mut self, id: TallyID, current_date: Date) -> bool {
if self.current_day.date == current_date {
let changed = self.current_day.add_id(id);

View File

@@ -5,5 +5,5 @@ mod id_store;
pub use id_mapping::{IDMapping, Name};
pub use id_store::{IDStore,AttendanceDay};
pub type TallyID = [u8; 8];
pub type Date = u64;
pub type TallyID = [u8; 12];
pub type Date = [u8; 10];