mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2026-05-01 02:59:09 +00:00
Compare commits
3 Commits
db7e22f45d
...
2078a3bab0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2078a3bab0 | ||
|
|
7e59d836a1 | ||
|
|
09f21403ec |
30
README.md
30
README.md
@@ -1,32 +1,6 @@
|
|||||||
# fw-anwesenheit
|
# fw-anwesenheit
|
||||||
|
|
||||||
# Setup
|

|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|||||||
@@ -8,13 +8,17 @@ use esp_hal::{
|
|||||||
};
|
};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
|
||||||
use crate::{FEEDBACK_STATE, drivers, feedback};
|
use crate::{FEEDBACK_STATE, drivers, feedback, store::Date};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
|
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>>,
|
||||||
}
|
}
|
||||||
@@ -41,6 +45,59 @@ 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>> {
|
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> {
|
||||||
@@ -62,8 +119,9 @@ pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>>
|
|||||||
match rtc.configure(&rtc_config).await {
|
match rtc.configure(&rtc_config).await {
|
||||||
Ok(_) => info!("DS3231 configured successfully"),
|
Ok(_) => info!("DS3231 configured successfully"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("Failed to configure DS3231: {:?}", e);
|
error!("Failed to configure DS3231: {:?}", e);
|
||||||
panic!("DS3231 configuration failed");
|
error!("DS3231 configuration failed");
|
||||||
|
FEEDBACK_STATE.signal(feedback::FeedbackState::Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,10 +83,10 @@ impl Persistence for SDCardPersistence {
|
|||||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||||
let mut days_dir = root_dir.open_dir("days").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
|
days_dir
|
||||||
.iterate_dir(|e| {
|
.iterate_dir(|e| {
|
||||||
days.push(1);
|
days.push([0; 10]);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use esp_hal::{gpio::InputConfig, peripherals};
|
|||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use static_cell::make_static;
|
use static_cell::make_static;
|
||||||
|
|
||||||
use crate::store::{IDStore, TallyID};
|
use crate::store::{Date, IDStore, TallyID};
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
@@ -80,7 +80,8 @@ async fn main(mut spawner: Spawner) {
|
|||||||
Message(msg) => {
|
Message(msg) => {
|
||||||
debug!("Got 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 {
|
if added {
|
||||||
FEEDBACK_STATE.signal(feedback::FeedbackState::Ack);
|
FEEDBACK_STATE.signal(feedback::FeedbackState::Ack);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use crate::drivers::rtc;
|
||||||
|
use crate::drivers::rtc::RTCClock;
|
||||||
use crate::store::persistence::Persistence;
|
use crate::store::persistence::Persistence;
|
||||||
|
|
||||||
use super::Date;
|
use super::Date;
|
||||||
@@ -46,7 +48,7 @@ impl<T: Persistence> IDStore<T> {
|
|||||||
// None => IDMapping::new(),
|
// None => IDMapping::new(),
|
||||||
// };
|
// };
|
||||||
|
|
||||||
let current_date: Date = 1;
|
let current_date: Date = [0; 10];
|
||||||
|
|
||||||
let day = persistence_layer
|
let day = persistence_layer
|
||||||
.load_day(current_date)
|
.load_day(current_date)
|
||||||
@@ -72,8 +74,7 @@ impl<T: Persistence> IDStore<T> {
|
|||||||
|
|
||||||
/// Add a new id for the current day
|
/// Add a new id for the current day
|
||||||
/// Returns false if ID is already present at the current day.
|
/// Returns false if ID is already present at the current day.
|
||||||
pub async fn add_id(&mut self, id: TallyID) -> bool {
|
pub async fn add_id(&mut self, id: TallyID, current_date: Date) -> bool {
|
||||||
let current_date: Date = 1;
|
|
||||||
|
|
||||||
if self.current_day.date == current_date {
|
if self.current_day.date == current_date {
|
||||||
let changed = self.current_day.add_id(id);
|
let changed = self.current_day.add_id(id);
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ mod id_store;
|
|||||||
pub use id_mapping::{IDMapping, Name};
|
pub use id_mapping::{IDMapping, Name};
|
||||||
pub use id_store::{IDStore,AttendanceDay};
|
pub use id_store::{IDStore,AttendanceDay};
|
||||||
|
|
||||||
pub type TallyID = [u8; 8];
|
pub type TallyID = [u8; 12];
|
||||||
pub type Date = u64;
|
pub type Date = [u8; 10];
|
||||||
|
|||||||
Reference in New Issue
Block a user