mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2026-04-30 18:49:09 +00:00
Compare commits
2 Commits
dc8fd22f0f
...
4781570f8e
| Author | SHA1 | Date | |
|---|---|---|---|
| 4781570f8e | |||
| efd096a149 |
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -41,6 +41,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@@ -419,6 +425,7 @@ dependencies = [
|
|||||||
name = "fw-anwesenheit"
|
name = "fw-anwesenheit"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"gpio",
|
"gpio",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ rppal = { version = "0.22.1", features = ["hal"] }
|
|||||||
smart-leds = "0.3"
|
smart-leds = "0.3"
|
||||||
ws2812-spi = "0.3"
|
ws2812-spi = "0.3"
|
||||||
rgb = "0.8.50"
|
rgb = "0.8.50"
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
use anyhow::Result;
|
||||||
use log::error;
|
use log::error;
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use smart_leds::colors::{GREEN, RED};
|
use smart_leds::colors::{GREEN, RED};
|
||||||
use std::{error::Error, time::Duration};
|
use std::time::Duration;
|
||||||
use tokio::{join, time::sleep};
|
use tokio::{join, time::sleep};
|
||||||
|
|
||||||
use crate::{
|
use crate::hardware::{Buzzer, StatusLed};
|
||||||
buzzer::{Buzzer, GPIOBuzzer},
|
|
||||||
led::{SpiLed, StatusLed},
|
#[cfg(not(feature = "mock_pi"))]
|
||||||
};
|
use crate::{gpio_buzzer::GPIOBuzzer, spi_led::SpiLed};
|
||||||
|
|
||||||
#[cfg(feature = "mock_pi")]
|
#[cfg(feature = "mock_pi")]
|
||||||
use crate::mock::{MockBuzzer, MockLed};
|
use crate::mock::{MockBuzzer, MockLed};
|
||||||
@@ -41,18 +42,14 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn blink_led_for_duration(
|
async fn blink_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> {
|
||||||
led: &mut L,
|
|
||||||
color: RGB8,
|
|
||||||
duration: Duration,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
led.turn_on(color)?;
|
led.turn_on(color)?;
|
||||||
sleep(duration).await;
|
sleep(duration).await;
|
||||||
led.turn_off()?;
|
led.turn_off()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn beep_ack(buzzer: &mut B) -> Result<(), Box<dyn Error>> {
|
async fn beep_ack(buzzer: &mut B) -> Result<()> {
|
||||||
buzzer
|
buzzer
|
||||||
.modulated_tone(1200.0, Duration::from_millis(100))
|
.modulated_tone(1200.0, Duration::from_millis(100))
|
||||||
.await?;
|
.await?;
|
||||||
@@ -63,7 +60,7 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn beep_nak(buzzer: &mut B) -> Result<(), Box<dyn Error>> {
|
async fn beep_nak(buzzer: &mut B) -> Result<()> {
|
||||||
buzzer
|
buzzer
|
||||||
.modulated_tone(600.0, Duration::from_millis(150))
|
.modulated_tone(600.0, Duration::from_millis(150))
|
||||||
.await?;
|
.await?;
|
||||||
@@ -81,7 +78,7 @@ pub type FeedbackImpl = Feedback<MockBuzzer, MockLed>;
|
|||||||
pub type FeedbackImpl = Feedback<GPIOBuzzer, SpiLed>;
|
pub type FeedbackImpl = Feedback<GPIOBuzzer, SpiLed>;
|
||||||
|
|
||||||
impl FeedbackImpl {
|
impl FeedbackImpl {
|
||||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
pub fn new() -> Result<Self> {
|
||||||
#[cfg(feature = "mock_pi")]
|
#[cfg(feature = "mock_pi")]
|
||||||
{
|
{
|
||||||
Ok(Feedback {
|
Ok(Feedback {
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
use rppal::pwm::{Channel, Error, Polarity, Pwm};
|
use anyhow::Result;
|
||||||
use std::{future::Future, time::Duration};
|
use rppal::pwm::{Channel, Polarity, Pwm};
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
const DEFAULT_PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12
|
use crate::hardware::Buzzer;
|
||||||
|
|
||||||
pub trait Buzzer {
|
const DEFAULT_PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12
|
||||||
fn modulated_tone(
|
|
||||||
&mut self,
|
|
||||||
frequency_hz: f64,
|
|
||||||
duration: Duration,
|
|
||||||
) -> impl Future<Output = Result<(), Error>> + std::marker::Send;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GPIOBuzzer {
|
pub struct GPIOBuzzer {
|
||||||
pwm: Pwm,
|
pwm: Pwm,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GPIOBuzzer {
|
impl GPIOBuzzer {
|
||||||
pub fn new_from_channel(channel: Channel) -> Result<Self, Error> {
|
pub fn new_from_channel(channel: Channel) -> Result<Self, rppal::pwm::Error> {
|
||||||
// Enable with dummy values; we'll set frequency/duty in the tone method
|
// Enable with dummy values; we'll set frequency/duty in the tone method
|
||||||
let duty_cycle: f64 = 0.5;
|
let duty_cycle: f64 = 0.5;
|
||||||
let pwm = Pwm::with_frequency(channel, 1000.0, duty_cycle, Polarity::Normal, true)?;
|
let pwm = Pwm::with_frequency(channel, 1000.0, duty_cycle, Polarity::Normal, true)?;
|
||||||
@@ -26,13 +21,13 @@ impl GPIOBuzzer {
|
|||||||
Ok(GPIOBuzzer { pwm })
|
Ok(GPIOBuzzer { pwm })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_default() -> Result<Self, Error> {
|
pub fn new_default() -> Result<Self, rppal::pwm::Error> {
|
||||||
Self::new_from_channel(DEFAULT_PWM_CHANNEL_BUZZER)
|
Self::new_from_channel(DEFAULT_PWM_CHANNEL_BUZZER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buzzer for GPIOBuzzer {
|
impl Buzzer for GPIOBuzzer {
|
||||||
async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<(), Error> {
|
async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<()> {
|
||||||
self.pwm.set_frequency(frequency_hz, 0.5)?; // 50% duty cycle (square wave)
|
self.pwm.set_frequency(frequency_hz, 0.5)?; // 50% duty cycle (square wave)
|
||||||
self.pwm.enable()?;
|
self.pwm.enable()?;
|
||||||
sleep(duration).await;
|
sleep(duration).await;
|
||||||
42
src/hardware.rs
Normal file
42
src/hardware.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[cfg(feature = "mock_pi")]
|
||||||
|
use crate::mock::MockHotspot;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "mock_pi"))]
|
||||||
|
use crate::hotspot::NMHotspot;
|
||||||
|
|
||||||
|
pub trait StatusLed {
|
||||||
|
fn turn_off(&mut self) -> Result<()>;
|
||||||
|
|
||||||
|
fn turn_on(&mut self, color: rgb::RGB8) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Buzzer {
|
||||||
|
fn modulated_tone(
|
||||||
|
&mut self,
|
||||||
|
frequency_hz: f64,
|
||||||
|
duration: Duration,
|
||||||
|
) -> impl Future<Output = Result<()>> + std::marker::Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Hotspot {
|
||||||
|
fn enable_hotspot(&self) -> impl std::future::Future<Output = Result<()>> + std::marker::Send;
|
||||||
|
|
||||||
|
fn disable_hotspot(&self) -> impl std::future::Future<Output = Result<()>> + std::marker::Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a struct to manage the hotspot
|
||||||
|
/// Respects the `mock_pi` flag.
|
||||||
|
pub fn create_hotspot() -> Result<impl Hotspot> {
|
||||||
|
#[cfg(feature = "mock_pi")]
|
||||||
|
{
|
||||||
|
Ok(MockHotspot {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "mock_pi"))]
|
||||||
|
{
|
||||||
|
NMHotspot::new_from_env()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,15 @@
|
|||||||
use log::{error, trace, warn};
|
use anyhow::{Result, anyhow};
|
||||||
use std::{
|
use log::{trace, warn};
|
||||||
env,
|
use std::env;
|
||||||
fmt::{self},
|
|
||||||
process::Output,
|
|
||||||
};
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::hardware::Hotspot;
|
||||||
|
|
||||||
const SSID: &str = "fwa";
|
const SSID: &str = "fwa";
|
||||||
const CON_NAME: &str = "fwa-hotspot";
|
const CON_NAME: &str = "fwa-hotspot";
|
||||||
const PASSWORD: &str = "a9LG2kUVrsRRVUo1";
|
const PASSWORD: &str = "a9LG2kUVrsRRVUo1";
|
||||||
const IPV4_ADDRES: &str = "192.168.4.1/24";
|
const IPV4_ADDRES: &str = "192.168.4.1/24";
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum HotspotError {
|
|
||||||
IoError(std::io::Error),
|
|
||||||
NonZeroExit(Output),
|
|
||||||
PasswordToShort,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for HotspotError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
HotspotError::IoError(err) => {
|
|
||||||
write!(f, "Failed to run hotspot command. I/O error: {err}")
|
|
||||||
}
|
|
||||||
HotspotError::NonZeroExit(output) => {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Failed to run hotspot command.\nStdout: {stdout}\nStderr: {stderr}",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
HotspotError::PasswordToShort => {
|
|
||||||
write!(f, "The password must be at leat 8 characters long")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for HotspotError {}
|
|
||||||
|
|
||||||
pub trait Hotspot {
|
|
||||||
fn enable_hotspot(
|
|
||||||
&self,
|
|
||||||
) -> impl std::future::Future<Output = Result<(), HotspotError>> + std::marker::Send;
|
|
||||||
|
|
||||||
fn disable_hotspot(
|
|
||||||
&self,
|
|
||||||
) -> impl std::future::Future<Output = Result<(), HotspotError>> + std::marker::Send;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NetworkManager Hotspot
|
/// NetworkManager Hotspot
|
||||||
pub struct NMHotspot {
|
pub struct NMHotspot {
|
||||||
ssid: String,
|
ssid: String,
|
||||||
@@ -60,7 +19,7 @@ pub struct NMHotspot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NMHotspot {
|
impl NMHotspot {
|
||||||
pub fn new_from_env() -> Result<Self, HotspotError> {
|
pub fn new_from_env() -> Result<Self> {
|
||||||
let ssid = env::var("HOTSPOT_SSID").unwrap_or(SSID.to_owned());
|
let ssid = env::var("HOTSPOT_SSID").unwrap_or(SSID.to_owned());
|
||||||
let password = env::var("HOTSPOT_PW").unwrap_or_else(|_| {
|
let password = env::var("HOTSPOT_PW").unwrap_or_else(|_| {
|
||||||
warn!("HOTSPOT_PW not set. Using default password");
|
warn!("HOTSPOT_PW not set. Using default password");
|
||||||
@@ -68,8 +27,7 @@ impl NMHotspot {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if password.len() < 8 {
|
if password.len() < 8 {
|
||||||
error!("Hotspot PW is to short");
|
return Err(anyhow!("Hotspot password to short"));
|
||||||
return Err(HotspotError::PasswordToShort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(NMHotspot {
|
Ok(NMHotspot {
|
||||||
@@ -80,7 +38,7 @@ impl NMHotspot {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_hotspot(&self) -> Result<(), HotspotError> {
|
async fn create_hotspot(&self) -> Result<()> {
|
||||||
let cmd = Command::new("nmcli")
|
let cmd = Command::new("nmcli")
|
||||||
.args(["device", "wifi", "hotspot"])
|
.args(["device", "wifi", "hotspot"])
|
||||||
.arg("con-name")
|
.arg("con-name")
|
||||||
@@ -90,14 +48,13 @@ impl NMHotspot {
|
|||||||
.arg("password")
|
.arg("password")
|
||||||
.arg(&self.password)
|
.arg(&self.password)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await?;
|
||||||
.map_err(HotspotError::IoError)?;
|
|
||||||
|
|
||||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||||
|
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
return Err(HotspotError::NonZeroExit(cmd));
|
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd = Command::new("nmcli")
|
let cmd = Command::new("nmcli")
|
||||||
@@ -109,24 +66,22 @@ impl NMHotspot {
|
|||||||
.arg("ipv4.addresses")
|
.arg("ipv4.addresses")
|
||||||
.arg(&self.ipv4)
|
.arg(&self.ipv4)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await?;
|
||||||
.map_err(HotspotError::IoError)?;
|
|
||||||
|
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
return Err(HotspotError::NonZeroExit(cmd));
|
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the connection already exists
|
/// Checks if the connection already exists
|
||||||
async fn exists(&self) -> Result<bool, HotspotError> {
|
async fn exists(&self) -> Result<bool> {
|
||||||
let cmd = Command::new("nmcli")
|
let cmd = Command::new("nmcli")
|
||||||
.args(["connection", "show"])
|
.args(["connection", "show"])
|
||||||
.arg(&self.con_name)
|
.arg(&self.con_name)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await?;
|
||||||
.map_err(HotspotError::IoError)?;
|
|
||||||
|
|
||||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||||
@@ -136,7 +91,7 @@ impl NMHotspot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Hotspot for NMHotspot {
|
impl Hotspot for NMHotspot {
|
||||||
async fn enable_hotspot(&self) -> Result<(), HotspotError> {
|
async fn enable_hotspot(&self) -> Result<()> {
|
||||||
if !self.exists().await? {
|
if !self.exists().await? {
|
||||||
self.create_hotspot().await?;
|
self.create_hotspot().await?;
|
||||||
}
|
}
|
||||||
@@ -145,32 +100,30 @@ impl Hotspot for NMHotspot {
|
|||||||
.args(["connection", "up"])
|
.args(["connection", "up"])
|
||||||
.arg(&self.con_name)
|
.arg(&self.con_name)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await?;
|
||||||
.map_err(HotspotError::IoError)?;
|
|
||||||
|
|
||||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||||
|
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
return Err(HotspotError::NonZeroExit(cmd));
|
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn disable_hotspot(&self) -> Result<(), HotspotError> {
|
async fn disable_hotspot(&self) -> Result<()> {
|
||||||
let cmd = Command::new("nmcli")
|
let cmd = Command::new("nmcli")
|
||||||
.args(["connection", "down"])
|
.args(["connection", "down"])
|
||||||
.arg(&self.con_name)
|
.arg(&self.con_name)
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await?;
|
||||||
.map_err(HotspotError::IoError)?;
|
|
||||||
|
|
||||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||||
|
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
return Err(HotspotError::NonZeroExit(cmd));
|
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
error::Error,
|
|
||||||
};
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::{id_mapping::IDMapping, tally_id::TallyID};
|
use crate::{id_mapping::IDMapping, tally_id::TallyID};
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
/// Represents a single day that IDs can attend
|
/// Represents a single day that IDs can attend
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
@@ -30,7 +27,7 @@ impl IDStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creats a new `IDStore` from a json file
|
/// Creats a new `IDStore` from a json file
|
||||||
pub async fn new_from_json(filepath: &str) -> Result<Self, Box<dyn Error>> {
|
pub async fn new_from_json(filepath: &str) -> Result<Self> {
|
||||||
let read_string = fs::read_to_string(filepath).await?;
|
let read_string = fs::read_to_string(filepath).await?;
|
||||||
Ok(serde_json::from_str(&read_string)?)
|
Ok(serde_json::from_str(&read_string)?)
|
||||||
}
|
}
|
||||||
@@ -59,14 +56,14 @@ impl IDStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the store to a json file
|
/// Writes the store to a json file
|
||||||
pub async fn export_json(&self, filepath: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
|
pub async fn export_json(&self, filepath: &str) -> Result<()> {
|
||||||
fs::write(filepath, serde_json::to_string(&self)?).await?;
|
fs::write(filepath, serde_json::to_string(&self)?).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export the store to a csv file.
|
/// Export the store to a csv file.
|
||||||
/// With days in the rows and IDs in the collum.
|
/// With days in the rows and IDs in the collum.
|
||||||
pub fn export_csv(&self) -> Result<String, Box<dyn Error>> {
|
pub fn export_csv(&self) -> Result<String> {
|
||||||
let mut csv = String::new();
|
let mut csv = String::new();
|
||||||
let seperator = ";";
|
let seperator = ";";
|
||||||
let mut user_ids: HashSet<TallyID> = HashSet::new();
|
let mut user_ids: HashSet<TallyID> = HashSet::new();
|
||||||
@@ -100,7 +97,7 @@ impl IDStore {
|
|||||||
let was_there: bool = self
|
let was_there: bool = self
|
||||||
.days
|
.days
|
||||||
.get(day)
|
.get(day)
|
||||||
.ok_or("Failed to access day")?
|
.ok_or(anyhow!("Failed to access day"))?
|
||||||
.ids
|
.ids
|
||||||
.contains(user_id);
|
.contains(user_id);
|
||||||
|
|
||||||
|
|||||||
37
src/main.rs
37
src/main.rs
@@ -1,10 +1,12 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use activity_fairing::{ActivityNotifier, spawn_idle_watcher};
|
use activity_fairing::{ActivityNotifier, spawn_idle_watcher};
|
||||||
use feedback::{Feedback, FeedbackImpl};
|
use feedback::{Feedback, FeedbackImpl};
|
||||||
use hotspot::{Hotspot, HotspotError, NMHotspot};
|
use hardware::{Hotspot, create_hotspot};
|
||||||
use id_store::IDStore;
|
use id_store::IDStore;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use pm3::run_pm3;
|
use pm3::run_pm3;
|
||||||
use std::{env, error::Error, sync::Arc, time::Duration};
|
use std::{env, sync::Arc, time::Duration};
|
||||||
use tally_id::TallyID;
|
use tally_id::TallyID;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs,
|
fs,
|
||||||
@@ -15,45 +17,30 @@ use tokio::{
|
|||||||
try_join,
|
try_join,
|
||||||
};
|
};
|
||||||
use webserver::start_webserver;
|
use webserver::start_webserver;
|
||||||
|
use anyhow::Result;
|
||||||
#[cfg(feature = "mock_pi")]
|
|
||||||
use mock::MockHotspot;
|
|
||||||
|
|
||||||
mod activity_fairing;
|
mod activity_fairing;
|
||||||
mod buzzer;
|
|
||||||
mod feedback;
|
mod feedback;
|
||||||
|
mod gpio_buzzer;
|
||||||
|
mod hardware;
|
||||||
mod hotspot;
|
mod hotspot;
|
||||||
mod id_mapping;
|
mod id_mapping;
|
||||||
mod id_store;
|
mod id_store;
|
||||||
mod led;
|
|
||||||
mod logger;
|
mod logger;
|
||||||
mod mock;
|
mod mock;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod pm3;
|
mod pm3;
|
||||||
|
mod spi_led;
|
||||||
mod tally_id;
|
mod tally_id;
|
||||||
mod webserver;
|
mod webserver;
|
||||||
|
|
||||||
const STORE_PATH: &str = "./data.json";
|
const STORE_PATH: &str = "./data.json";
|
||||||
|
|
||||||
/// Create a struct to manage the hotspot
|
|
||||||
/// Respects the `mock_pi` flag.
|
|
||||||
fn create_hotspot() -> Result<impl Hotspot, HotspotError> {
|
|
||||||
#[cfg(feature = "mock_pi")]
|
|
||||||
{
|
|
||||||
Ok(MockHotspot {})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "mock_pi"))]
|
|
||||||
{
|
|
||||||
NMHotspot::new_from_env()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_webserver<H>(
|
async fn run_webserver<H>(
|
||||||
store: Arc<Mutex<IDStore>>,
|
store: Arc<Mutex<IDStore>>,
|
||||||
id_channel: Sender<String>,
|
id_channel: Sender<String>,
|
||||||
hotspot: Arc<Mutex<H>>,
|
hotspot: Arc<Mutex<H>>,
|
||||||
) -> Result<(), Box<dyn Error>>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
H: Hotspot + Send + Sync + 'static,
|
H: Hotspot + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@@ -73,7 +60,7 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_or_create_store() -> Result<IDStore, Box<dyn Error>> {
|
async fn load_or_create_store() -> Result<IDStore> {
|
||||||
if fs::try_exists(STORE_PATH).await? {
|
if fs::try_exists(STORE_PATH).await? {
|
||||||
info!("Loading data from file");
|
info!("Loading data from file");
|
||||||
IDStore::new_from_json(STORE_PATH).await
|
IDStore::new_from_json(STORE_PATH).await
|
||||||
@@ -103,7 +90,7 @@ async fn handle_ids_loop(
|
|||||||
id_store: Arc<Mutex<IDStore>>,
|
id_store: Arc<Mutex<IDStore>>,
|
||||||
hotspot: Arc<Mutex<impl Hotspot>>,
|
hotspot: Arc<Mutex<impl Hotspot>>,
|
||||||
mut user_feedback: FeedbackImpl,
|
mut user_feedback: FeedbackImpl,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<()> {
|
||||||
while let Ok(tally_id_string) = id_channel.recv().await {
|
while let Ok(tally_id_string) = id_channel.recv().await {
|
||||||
let tally_id = TallyID(tally_id_string);
|
let tally_id = TallyID(tally_id_string);
|
||||||
|
|
||||||
@@ -137,7 +124,7 @@ async fn handle_ids_loop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<()> {
|
||||||
logger::setup_logger();
|
logger::setup_logger();
|
||||||
|
|
||||||
info!("Starting application");
|
info!("Starting application");
|
||||||
|
|||||||
20
src/mock.rs
20
src/mock.rs
@@ -1,18 +1,14 @@
|
|||||||
use std::time::Duration;
|
use anyhow::Result;
|
||||||
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use crate::{buzzer::Buzzer, hotspot::Hotspot, led::StatusLed};
|
use crate::hardware::{Buzzer, Hotspot, StatusLed};
|
||||||
|
|
||||||
pub struct MockBuzzer {}
|
pub struct MockBuzzer {}
|
||||||
|
|
||||||
impl Buzzer for MockBuzzer {
|
impl Buzzer for MockBuzzer {
|
||||||
async fn modulated_tone(
|
async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
frequency_hz: f64,
|
|
||||||
duration: Duration,
|
|
||||||
) -> Result<(), rppal::pwm::Error> {
|
|
||||||
debug!("MockBuzzer: modulte tone: {frequency_hz} Hz");
|
debug!("MockBuzzer: modulte tone: {frequency_hz} Hz");
|
||||||
sleep(duration).await;
|
sleep(duration).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -22,12 +18,12 @@ impl Buzzer for MockBuzzer {
|
|||||||
pub struct MockLed {}
|
pub struct MockLed {}
|
||||||
|
|
||||||
impl StatusLed for MockLed {
|
impl StatusLed for MockLed {
|
||||||
fn turn_off(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
fn turn_off(&mut self) -> Result<()> {
|
||||||
debug!("Turn mock LED off");
|
debug!("Turn mock LED off");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box<dyn std::error::Error>> {
|
fn turn_on(&mut self, color: rgb::RGB8) -> Result<()> {
|
||||||
debug!("Turn mock LED on to: {color}");
|
debug!("Turn mock LED on to: {color}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -36,12 +32,12 @@ impl StatusLed for MockLed {
|
|||||||
pub struct MockHotspot {}
|
pub struct MockHotspot {}
|
||||||
|
|
||||||
impl Hotspot for MockHotspot {
|
impl Hotspot for MockHotspot {
|
||||||
async fn enable_hotspot(&self) -> Result<(), crate::hotspot::HotspotError> {
|
async fn enable_hotspot(&self) -> Result<()> {
|
||||||
debug!("Mockhotspot: Enable hotspot");
|
debug!("Mockhotspot: Enable hotspot");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn disable_hotspot(&self) -> Result<(), crate::hotspot::HotspotError> {
|
async fn disable_hotspot(&self) -> Result<()> {
|
||||||
debug!("Mockhotspot: Disable hotspot");
|
debug!("Mockhotspot: Disable hotspot");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/pm3.rs
10
src/pm3.rs
@@ -1,6 +1,6 @@
|
|||||||
|
use anyhow::{Result, anyhow};
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -11,7 +11,7 @@ use tokio::sync::broadcast;
|
|||||||
/// Runs the pm3 binary and monitors it's output
|
/// Runs the pm3 binary and monitors it's output
|
||||||
/// The pm3 binary is ether set in the env var PM3_BIN or found in the path
|
/// The pm3 binary is ether set in the env var PM3_BIN or found in the path
|
||||||
/// The ouput is parsed and send via the `tx` channel
|
/// The ouput is parsed and send via the `tx` channel
|
||||||
pub async fn run_pm3(tx: broadcast::Sender<String>) -> Result<(), Box<dyn Error>> {
|
pub async fn run_pm3(tx: broadcast::Sender<String>) -> Result<()> {
|
||||||
kill_orphans().await;
|
kill_orphans().await;
|
||||||
|
|
||||||
let pm3_path = match env::var("PM3_BIN") {
|
let pm3_path = match env::var("PM3_BIN") {
|
||||||
@@ -32,8 +32,8 @@ pub async fn run_pm3(tx: broadcast::Sender<String>) -> Result<(), Box<dyn Error>
|
|||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
let stdout = cmd.stdout.take().ok_or("Failed to get stdout")?;
|
let stdout = cmd.stdout.take().ok_or(anyhow!("Failed to get stdout"))?;
|
||||||
let mut stdin = cmd.stdin.take().ok_or("Failed to get stdin")?;
|
let mut stdin = cmd.stdin.take().ok_or(anyhow!("Failed to get stdin"))?;
|
||||||
|
|
||||||
let mut reader = BufReader::new(stdout).lines();
|
let mut reader = BufReader::new(stdout).lines();
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ pub async fn run_pm3(tx: broadcast::Sender<String>) -> Result<(), Box<dyn Error>
|
|||||||
if status.success() {
|
if status.success() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("PM3 exited with a non-zero exit code".into())
|
Err(anyhow!("PM3 exited with a non-zero exit code"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
|
use anyhow::Result;
|
||||||
use rppal::spi::{Bus, Mode, SlaveSelect, Spi};
|
use rppal::spi::{Bus, Mode, SlaveSelect, Spi};
|
||||||
use smart_leds::SmartLedsWrite;
|
use smart_leds::SmartLedsWrite;
|
||||||
use std::error::Error;
|
|
||||||
use ws2812_spi::Ws2812;
|
use ws2812_spi::Ws2812;
|
||||||
|
|
||||||
|
use crate::hardware::StatusLed;
|
||||||
|
|
||||||
const SPI_CLOCK_SPEED: u32 = 3_800_000;
|
const SPI_CLOCK_SPEED: u32 = 3_800_000;
|
||||||
|
|
||||||
pub trait StatusLed {
|
|
||||||
fn turn_off(&mut self) -> Result<(), Box<dyn Error>>;
|
|
||||||
|
|
||||||
fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SpiLed {
|
pub struct SpiLed {
|
||||||
controller: Ws2812<Spi>,
|
controller: Ws2812<Spi>,
|
||||||
}
|
}
|
||||||
@@ -24,13 +20,13 @@ impl SpiLed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StatusLed for SpiLed {
|
impl StatusLed for SpiLed {
|
||||||
fn turn_off(&mut self) -> Result<(), Box<dyn Error>> {
|
fn turn_off(&mut self) -> Result<()> {
|
||||||
self.controller
|
self.controller
|
||||||
.write(vec![rgb::RGB8::new(0, 0, 0)].into_iter())?;
|
.write(vec![rgb::RGB8::new(0, 0, 0)].into_iter())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box<dyn Error>> {
|
fn turn_on(&mut self, color: rgb::RGB8) -> Result<()> {
|
||||||
self.controller.write(vec![color].into_iter())?;
|
self.controller.write(vec![color].into_iter())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user