use cortex_m::{prelude::_embedded_hal_PwmPin, singleton};
use critical_section::CriticalSection;
use defmt::{debug, error, info, warn, Format, Formatter};
use embedded_hal::digital::{OutputPin, PinState};
use rp2040_hal::{
dma::{single_buffer, SingleChannel},
gpio::{
bank0::{Gpio6, Gpio7, Gpio8},
FunctionNull, FunctionSio, Pin, PullDown, SioOutput,
},
};
use crate::{
buffer::DetectionMsg,
interrupt::{READINGS_FIFO, SIGNAL_CONF, SIGNAL_GEN, STATUS_LEDS},
};
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum StatusLedStates {
Normal,
Alert,
Error,
Disabled,
}
impl Format for StatusLedStates {
fn format(&self, fmt: Formatter) {
defmt::write!(
fmt,
"{}",
match self {
StatusLedStates::Normal => "Normal",
StatusLedStates::Alert => "Alert",
StatusLedStates::Error => "Error",
StatusLedStates::Disabled => "Disabled",
}
);
}
}
pub trait StatusLed {
const NO_LED_PANIC_MSG: &'static str =
"Unable to display state due to non-configured LEDs, or not available in mutex";
const RESET_MSG: &'static str = "\nSystem must be power cycled to restore normal operation.";
const DISABLE_MSG: &'static str = "\nToggle the disable switch to resume normal operation.";
fn set_normal(cs: CriticalSection, message: Option<&str>);
fn set_alert(cs: CriticalSection, message: Option<DetectionMsg>);
fn set_error(cs: CriticalSection, message: Option<&str>);
fn set_disabled(cs: CriticalSection, message: Option<&str>);
fn pause_detection(cs: CriticalSection);
fn resume_detection(cs: CriticalSection);
}
pub trait LedControl {
fn init(
gpio6: Pin<Gpio6, FunctionNull, PullDown>,
gpio7: Pin<Gpio7, FunctionNull, PullDown>,
gpio8: Pin<Gpio8, FunctionNull, PullDown>,
) -> Option<&'static mut impl StatusLed>;
fn set_led(
&mut self,
old_state: &StatusLedStates,
new_state: StatusLedStates,
) -> StatusLedStates;
}
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, Format)]
pub struct StatusLedBase<C>
where
C: LedControl,
{
pub state: StatusLedStates,
pub ctrl: C,
}
impl<C: LedControl> StatusLed for StatusLedBase<C> {
fn set_normal(cs: CriticalSection, message: Option<&str>) {
let status = STATUS_LEDS.take(cs).expect(Self::NO_LED_PANIC_MSG);
if let Some(detection_msg) = message {
info!("Resuming normal detection: {}", detection_msg);
} else {
warn!("State changed to normal");
}
match status.state {
StatusLedStates::Error | StatusLedStates::Disabled => Self::resume_detection(cs),
StatusLedStates::Normal | StatusLedStates::Alert => {}
}
status.state = status.ctrl.set_led(&status.state, StatusLedStates::Normal);
STATUS_LEDS.replace(cs, Some(status));
}
fn set_alert(cs: CriticalSection, message: Option<DetectionMsg>) {
let status = STATUS_LEDS.take(cs).expect(Self::NO_LED_PANIC_MSG);
if let Some(detection_msg) = message {
info!("{}", detection_msg);
} else {
warn!("Unknown alert raised!");
}
match status.state {
StatusLedStates::Error | StatusLedStates::Disabled => Self::resume_detection(cs),
StatusLedStates::Normal | StatusLedStates::Alert => {}
};
status.state = status.ctrl.set_led(&status.state, StatusLedStates::Alert);
STATUS_LEDS.replace(cs, Some(status));
}
fn set_error(cs: CriticalSection, message: Option<&str>) {
let status = STATUS_LEDS.take(cs).expect(Self::NO_LED_PANIC_MSG);
if let Some(msg_text) = message {
error!(
"Error encountered during operation:\n{=str}{=str}",
msg_text,
Self::RESET_MSG
);
} else {
error!(
"Unknown error encountered during operation.{=str}",
Self::RESET_MSG
);
}
match status.state {
StatusLedStates::Normal | StatusLedStates::Alert => Self::pause_detection(cs),
StatusLedStates::Error | StatusLedStates::Disabled => {}
};
status.state = status.ctrl.set_led(&status.state, StatusLedStates::Error);
STATUS_LEDS.replace(cs, Some(status));
}
fn set_disabled(cs: CriticalSection, message: Option<&str>) {
let status = STATUS_LEDS.take(cs).expect(Self::NO_LED_PANIC_MSG);
if let Some(msg_text) = message {
info!(
"System has been disabled:\n{=str}{=str}",
msg_text,
Self::DISABLE_MSG
);
} else {
info!("System has been disabled.{=str}", Self::DISABLE_MSG);
}
match status.state {
StatusLedStates::Normal | StatusLedStates::Alert => Self::pause_detection(cs),
StatusLedStates::Error | StatusLedStates::Disabled => {}
};
status.state = status
.ctrl
.set_led(&status.state, StatusLedStates::Disabled);
STATUS_LEDS.replace(cs, Some(status));
}
fn pause_detection(cs: CriticalSection) {
debug!("Disabling signal generation");
let mut signal_pwm = SIGNAL_GEN.take(cs).expect("Unable to access PWM controls");
signal_pwm.disable();
SIGNAL_GEN.replace(cs, Some(signal_pwm));
debug!("Disabling FIFO readings/interrupts");
let fifo_transfer = READINGS_FIFO.take(cs).expect("Unable to access ADC FIFO");
SIGNAL_CONF.replace(cs, Some(fifo_transfer.wait()));
}
fn resume_detection(cs: CriticalSection) {
debug!("Restoring signal generation");
let mut signal_pwm = SIGNAL_GEN.take(cs).expect("Unable to access PWM controls");
signal_pwm.enable();
SIGNAL_GEN.replace(cs, Some(signal_pwm));
debug!("Restoring ADC readings and interrupts");
let config = SIGNAL_CONF.replace(cs, None);
if let Some(mut inner) = config {
inner.0.enable_irq0();
let new_transfer = single_buffer::Config::new(inner.0, inner.1, inner.2);
READINGS_FIFO.replace(cs, Some(new_transfer.start()));
} else {
warn!("Failed to restore FIFO config");
READINGS_FIFO.replace(cs, None);
}
}
}
#[cfg(any(doc, feature = "rgba_status"))]
pub struct Rgba {
red_led: Pin<Gpio6, FunctionSio<SioOutput>, PullDown>,
green_led: Pin<Gpio7, FunctionSio<SioOutput>, PullDown>,
#[allow(dead_code)]
blue_led: Pin<Gpio8, FunctionSio<SioOutput>, PullDown>,
}
#[cfg(any(doc, feature = "rgba_status"))]
impl LedControl for Rgba {
#[allow(refining_impl_trait)]
fn init(
gpio6: Pin<Gpio6, FunctionNull, PullDown>,
gpio7: Pin<Gpio7, FunctionNull, PullDown>,
gpio8: Pin<Gpio8, FunctionNull, PullDown>,
) -> Option<&'static mut StatusLedBase<Rgba>> {
singleton!(: StatusLedBase<Rgba> = StatusLedBase {
state: StatusLedStates::Alert,
ctrl: Rgba {
red_led: gpio6.into_push_pull_output_in_state(PinState::Low),
green_led: gpio7.into_push_pull_output_in_state(PinState::Low),
blue_led: gpio8.into_push_pull_output_in_state(PinState::High),
}
})
}
fn set_led(
&mut self,
old_state: &StatusLedStates,
new_state: StatusLedStates,
) -> StatusLedStates {
match old_state {
StatusLedStates::Normal => self.green_led.set_high().unwrap(),
StatusLedStates::Alert => {
self.red_led.set_high().unwrap();
self.green_led.set_high().unwrap();
}
StatusLedStates::Error => self.red_led.set_high().unwrap(),
StatusLedStates::Disabled => {}
}
match new_state {
StatusLedStates::Normal => self.green_led.set_low().unwrap(),
StatusLedStates::Alert => {
self.red_led.set_low().unwrap();
self.green_led.set_low().unwrap();
}
StatusLedStates::Error => self.green_led.set_low().unwrap(),
StatusLedStates::Disabled => {}
}
new_state
}
}
#[cfg(any(doc, feature = "triple_status"))]
pub struct Triple {
normal_led: Pin<Gpio6, FunctionSio<SioOutput>, PullDown>,
alert_led: Pin<Gpio7, FunctionSio<SioOutput>, PullDown>,
error_led: Pin<Gpio8, FunctionSio<SioOutput>, PullDown>,
}
#[cfg(any(doc, feature = "triple_status"))]
impl LedControl for Triple {
#[allow(refining_impl_trait)]
fn init(
gpio6: Pin<Gpio6, FunctionNull, PullDown>,
gpio7: Pin<Gpio7, FunctionNull, PullDown>,
gpio8: Pin<Gpio8, FunctionNull, PullDown>,
) -> Option<&'static mut StatusLedBase<Self>> {
singleton!(: StatusLedBase<Triple> = StatusLedBase {
state: StatusLedStates::Alert,
ctrl: Triple {
normal_led: gpio6.into_push_pull_output_in_state(PinState::Low),
alert_led: gpio7.into_push_pull_output_in_state(PinState::High),
error_led: gpio8.into_push_pull_output_in_state(PinState::Low),
}
})
}
fn set_led(
&mut self,
old_state: &StatusLedStates,
new_state: StatusLedStates,
) -> StatusLedStates {
match old_state {
StatusLedStates::Normal => self.normal_led.set_low().unwrap(),
StatusLedStates::Alert => self.alert_led.set_low().unwrap(),
StatusLedStates::Error => self.error_led.set_low().unwrap(),
StatusLedStates::Disabled => {}
}
match new_state {
StatusLedStates::Normal => self.normal_led.set_high().unwrap(),
StatusLedStates::Alert => self.alert_led.set_high().unwrap(),
StatusLedStates::Error => self.error_led.set_high().unwrap(),
StatusLedStates::Disabled => {}
}
new_state
}
}