use crate::api::*;
use anyhow::Result;
use rust_patlite_beacon::{Beacon, BuzzerCount, BuzzerPattern as BeaconBuzzerPattern, BuzzerVolume, LedColor, LedPattern};
use std::sync::{Arc, Mutex};
use std::time::Instant;
pub struct BeaconController {
beacon: Option<Arc<Mutex<Beacon>>>,
current_light: Option<CurrentLightState>,
light_timer: Option<Instant>,
sequence: Option<LightSequence>,
sequence_status: Option<SequenceStatus>,
sequence_handle: Option<Arc<Mutex<bool>>>,
buzzer_state: BuzzerState,
buzzer_timer: Option<Instant>,
}
impl BeaconController {
pub fn new() -> Result<Self> {
let beacon = match Beacon::open() {
Ok(b) => Some(Arc::new(Mutex::new(b))),
Err(_) => None,
};
Ok(Self {
beacon,
current_light: None,
light_timer: None,
sequence: None,
sequence_status: None,
sequence_handle: None,
buzzer_state: BuzzerState::default(),
buzzer_timer: None,
})
}
pub fn get_beacon_clone(&self) -> Option<Arc<Mutex<Beacon>>> {
self.beacon.clone()
}
#[cfg(test)]
pub fn mock() -> Self {
Self {
beacon: None,
current_light: None,
light_timer: None,
sequence: None,
sequence_status: None,
sequence_handle: None,
buzzer_state: BuzzerState::default(),
buzzer_timer: None,
}
}
pub fn get_status(&self) -> BeaconStatus {
let light = if let Some(ref current) = self.current_light {
let mut light_with_timer = current.clone();
if let Some(timer) = self.light_timer {
let elapsed = timer.elapsed();
if let Some(remaining) = current.remaining_ms {
let remaining_ms = remaining.saturating_sub(elapsed.as_millis() as u64);
light_with_timer.remaining_ms = Some(remaining_ms);
}
}
Some(light_with_timer)
} else {
None
};
let mut buzzer = self.buzzer_state.clone();
if let Some(timer) = self.buzzer_timer {
let elapsed = timer.elapsed();
if let Some(remaining) = buzzer.remaining_ms {
let remaining_ms = remaining.saturating_sub(elapsed.as_millis() as u64);
buzzer.remaining_ms = Some(remaining_ms);
}
}
BeaconStatus {
light,
sequence: self.sequence_status.clone(),
buzzer,
connected: self.beacon.is_some(),
last_updated: chrono::Utc::now().to_rfc3339(),
}
}
pub fn set_light(&mut self, color: LightColor, mode: LightMode, duration_ms: Option<u64>) -> Result<()> {
self.stop_sequence();
if let Some(ref beacon) = self.beacon {
let beacon_color = match color {
LightColor::Red => LedColor::Red,
LightColor::Yellow => LedColor::Yellow,
LightColor::Green => LedColor::Green,
LightColor::Blue => LedColor::Blue,
LightColor::White => LedColor::White,
};
let beacon_pattern = match mode {
LightMode::Off => LedPattern::Off,
LightMode::On | LightMode::Solid => LedPattern::On,
LightMode::Pattern1 | LightMode::Blink => LedPattern::Pattern1,
LightMode::Pattern2 | LightMode::Flash => LedPattern::Pattern2,
LightMode::Pattern3 => LedPattern::Pattern3,
LightMode::Pattern4 => LedPattern::Pattern4,
LightMode::Pattern5 => LedPattern::Pattern5,
LightMode::Pattern6 => LedPattern::Pattern6,
};
beacon.lock().unwrap().set_light(beacon_color, beacon_pattern)?;
}
self.current_light = Some(CurrentLightState {
color,
mode,
active: mode != LightMode::Off,
remaining_ms: duration_ms,
});
self.light_timer = if duration_ms.is_some() {
Some(Instant::now())
} else {
None
};
Ok(())
}
pub fn check_timeouts(&mut self) -> Result<()> {
if let Some(timer) = self.light_timer {
if let Some(ref light) = self.current_light {
if let Some(duration) = light.remaining_ms {
if timer.elapsed().as_millis() >= duration as u128 {
if let Some(ref beacon) = self.beacon {
beacon.lock().unwrap().set_light(LedColor::Off, LedPattern::Off)?;
}
self.current_light = None;
self.light_timer = None;
}
}
}
}
if let Some(timer) = self.buzzer_timer {
if let Some(duration) = self.buzzer_state.remaining_ms {
if timer.elapsed().as_millis() >= duration as u128 {
if let Some(ref beacon) = self.beacon {
beacon.lock().unwrap().set_buzzer(BeaconBuzzerPattern::Off, BuzzerCount::CONTINUOUS)?;
}
self.buzzer_state = BuzzerState::default();
self.buzzer_timer = None;
}
}
}
Ok(())
}
pub fn clear_light(&mut self) -> Result<()> {
self.stop_sequence();
if let Some(ref beacon) = self.beacon {
beacon.lock().unwrap().set_light(LedColor::Off, LedPattern::Off)?;
}
self.current_light = None;
self.light_timer = None;
Ok(())
}
pub fn stop_sequence(&mut self) {
if let Some(handle) = &self.sequence_handle {
if let Ok(mut stop) = handle.lock() {
*stop = true;
}
}
self.sequence_handle = None;
self.sequence_status = None;
}
pub fn start_sequence(&mut self, sequence: LightSequence) -> Result<()> {
self.stop_sequence();
self.clear_light()?;
self.sequence_status = Some(SequenceStatus {
commands: sequence.commands.clone(),
current_index: 0,
loop_sequence: sequence.loop_sequence,
active: true,
});
self.sequence = Some(sequence.clone());
let stop_flag = Arc::new(Mutex::new(false));
self.sequence_handle = Some(stop_flag.clone());
if let Some(status) = &self.sequence_status {
if status.commands.len() > 0 {
let first_command = &status.commands[0];
self.set_light_internal(first_command.color, first_command.mode, Some(first_command.duration_ms))?;
}
}
Ok(())
}
fn set_light_internal(&mut self, color: LightColor, mode: LightMode, duration_ms: Option<u64>) -> Result<()> {
if let Some(ref beacon) = self.beacon {
let beacon_color = match color {
LightColor::Red => LedColor::Red,
LightColor::Yellow => LedColor::Yellow,
LightColor::Green => LedColor::Green,
LightColor::Blue => LedColor::Blue,
LightColor::White => LedColor::White,
};
let beacon_pattern = match mode {
LightMode::Off => LedPattern::Off,
LightMode::On | LightMode::Solid => LedPattern::On,
LightMode::Pattern1 | LightMode::Blink => LedPattern::Pattern1,
LightMode::Pattern2 | LightMode::Flash => LedPattern::Pattern2,
LightMode::Pattern3 => LedPattern::Pattern3,
LightMode::Pattern4 => LedPattern::Pattern4,
LightMode::Pattern5 => LedPattern::Pattern5,
LightMode::Pattern6 => LedPattern::Pattern6,
};
beacon.lock().unwrap().set_light(beacon_color, beacon_pattern)?;
}
self.current_light = Some(CurrentLightState {
color,
mode,
active: mode != LightMode::Off,
remaining_ms: duration_ms,
});
self.light_timer = if duration_ms.is_some() {
Some(Instant::now())
} else {
None
};
Ok(())
}
pub fn update_sequence(&mut self) -> Result<()> {
if self.sequence_status.is_none() {
self.check_timeouts()?;
return Ok(());
}
let should_stop = if let Some(handle) = &self.sequence_handle {
if let Ok(stop) = handle.lock() {
*stop
} else {
false
}
} else {
false
};
if should_stop {
self.stop_sequence();
self.clear_light()?;
return Ok(());
}
let (should_advance, should_stop_sequence) = if let Some(status) = &self.sequence_status {
if !status.active || status.commands.is_empty() {
(false, false)
} else if let Some(timer) = self.light_timer {
if let Some(ref current) = self.current_light {
if let Some(duration) = current.remaining_ms {
let elapsed = timer.elapsed().as_millis() >= duration as u128;
if elapsed {
let next_index = (status.current_index + 1) % status.commands.len();
let should_stop = next_index == 0 && !status.loop_sequence;
(elapsed, should_stop)
} else {
(false, false)
}
} else {
(false, false)
}
} else {
(false, false)
}
} else {
(false, false)
}
} else {
(false, false)
};
if should_stop_sequence {
self.stop_sequence();
self.clear_light()?;
return Ok(());
}
if should_advance {
if let Some(status) = &mut self.sequence_status {
status.current_index = (status.current_index + 1) % status.commands.len();
let next_command = status.commands[status.current_index];
self.set_light_internal(next_command.color, next_command.mode, Some(next_command.duration_ms))?;
}
}
Ok(())
}
pub fn set_buzzer(&mut self, pattern: BuzzerPattern, volume: Option<u8>, duration_ms: Option<u64>) -> Result<()> {
if let Some(ref beacon) = self.beacon {
let beacon = beacon.lock().unwrap();
let buzzer_pattern = match pattern {
BuzzerPattern::Off => BeaconBuzzerPattern::Off,
BuzzerPattern::On | BuzzerPattern::Continuous => BeaconBuzzerPattern::On,
BuzzerPattern::Sweep => BeaconBuzzerPattern::Sweep,
BuzzerPattern::Intermittent => BeaconBuzzerPattern::Intermittent,
BuzzerPattern::WeakAttention => BeaconBuzzerPattern::WeakAttention,
BuzzerPattern::StrongAttention => BeaconBuzzerPattern::StrongAttention,
BuzzerPattern::ShiningStar => BeaconBuzzerPattern::ShiningStar,
BuzzerPattern::LondonBridge => BeaconBuzzerPattern::LondonBridge,
};
let buzzer_volume = if let Some(v) = volume {
BuzzerVolume::level(v / 10).unwrap_or(BuzzerVolume::level(5).unwrap())
} else {
BuzzerVolume::level(5).unwrap()
};
let buzzer_count = if duration_ms.is_some() {
BuzzerCount::times(5).unwrap_or(BuzzerCount::CONTINUOUS)
} else {
BuzzerCount::CONTINUOUS
};
beacon.set_buzzer_ex(buzzer_pattern, buzzer_count, buzzer_volume)?;
}
self.buzzer_state = BuzzerState {
active: true,
pattern: Some(pattern),
volume: volume.unwrap_or(50),
remaining_ms: duration_ms,
};
if duration_ms.is_some() {
self.buzzer_timer = Some(Instant::now());
} else {
self.buzzer_timer = None;
}
Ok(())
}
pub fn stop_buzzer(&mut self) -> Result<()> {
if let Some(ref beacon) = self.beacon {
beacon.lock().unwrap().set_buzzer(BeaconBuzzerPattern::Off, BuzzerCount::CONTINUOUS)?;
}
self.buzzer_state = BuzzerState::default();
self.buzzer_timer = None;
Ok(())
}
pub fn set_buzzer_pattern(&mut self, pattern: Vec<BuzzerNote>, repetitions: u8, volume: Option<u8>, duration_ms: Option<u64>) -> Result<()> {
if let Some(ref beacon) = self.beacon {
let beacon = beacon.lock().unwrap();
let buzzer_volume = if let Some(v) = volume {
BuzzerVolume::level(v / 10).unwrap_or(BuzzerVolume::level(5).unwrap())
} else {
BuzzerVolume::level(5).unwrap()
};
let pattern_duration: u64 = pattern.iter()
.map(|note| note.duration_ms + note.pause_ms)
.sum();
let _total_duration = pattern_duration * repetitions as u64;
for note in &pattern {
beacon.set_buzzer_ex(
BeaconBuzzerPattern::On,
BuzzerCount::times(1).unwrap_or(BuzzerCount::CONTINUOUS),
buzzer_volume
)?;
std::thread::sleep(std::time::Duration::from_millis(note.duration_ms));
beacon.set_buzzer(BeaconBuzzerPattern::Off, BuzzerCount::CONTINUOUS)?;
std::thread::sleep(std::time::Duration::from_millis(note.pause_ms));
}
}
self.buzzer_state = BuzzerState {
active: true,
pattern: None,
volume: volume.unwrap_or(50),
remaining_ms: duration_ms,
};
if duration_ms.is_some() {
self.buzzer_timer = Some(Instant::now());
} else {
self.buzzer_timer = None;
}
Ok(())
}
pub fn clear_all(&mut self) -> Result<()> {
self.stop_sequence();
self.clear_light()?;
self.stop_buzzer()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_beacon_controller_mock() {
let controller = BeaconController::mock();
assert!(controller.beacon.is_none());
assert!(controller.current_light.is_none());
}
#[test]
fn test_get_status() {
let controller = BeaconController::mock();
let status = controller.get_status();
assert!(!status.connected);
assert!(status.light.is_none());
assert!(!status.buzzer.active);
}
#[test]
fn test_set_light() {
let mut controller = BeaconController::mock();
controller.set_light(LightColor::Red, LightMode::On, Some(1000)).unwrap();
let light = controller.current_light.as_ref().unwrap();
assert_eq!(light.color, LightColor::Red);
assert_eq!(light.mode, LightMode::On);
assert!(light.active);
assert_eq!(light.remaining_ms, Some(1000));
}
#[test]
fn test_clear_light() {
let mut controller = BeaconController::mock();
controller.set_light(LightColor::Green, LightMode::Flash, None).unwrap();
controller.clear_light().unwrap();
assert!(controller.current_light.is_none());
}
#[test]
fn test_start_sequence() {
let mut controller = BeaconController::mock();
let sequence = LightSequence {
commands: vec![
LightCommand {
color: LightColor::Red,
mode: LightMode::On,
duration_ms: 1000,
},
LightCommand {
color: LightColor::Blue,
mode: LightMode::Flash,
duration_ms: 1000,
},
],
loop_sequence: true,
};
controller.start_sequence(sequence).unwrap();
assert!(controller.sequence_status.is_some());
let status = controller.sequence_status.as_ref().unwrap();
assert_eq!(status.commands.len(), 2);
assert!(status.active);
}
#[test]
fn test_set_buzzer() {
let mut controller = BeaconController::mock();
controller.set_buzzer(BuzzerPattern::StrongAttention, Some(75), Some(2000)).unwrap();
assert!(controller.buzzer_state.active);
assert_eq!(controller.buzzer_state.pattern, Some(BuzzerPattern::StrongAttention));
assert_eq!(controller.buzzer_state.volume, 75);
assert_eq!(controller.buzzer_state.remaining_ms, Some(2000));
}
#[test]
fn test_stop_buzzer() {
let mut controller = BeaconController::mock();
controller.set_buzzer(BuzzerPattern::Continuous, Some(60), None).unwrap();
controller.stop_buzzer().unwrap();
assert!(!controller.buzzer_state.active);
assert_eq!(controller.buzzer_state.pattern, None);
}
#[test]
fn test_clear_all() {
let mut controller = BeaconController::mock();
controller.set_light(LightColor::Red, LightMode::On, None).unwrap();
controller.set_buzzer(BuzzerPattern::WeakAttention, Some(50), None).unwrap();
controller.clear_all().unwrap();
assert!(controller.current_light.is_none());
assert!(!controller.buzzer_state.active);
}
}