use std::io;
use std::time::Duration;
use pigdef::config::{HardwareConfig, InputPull, LevelChange};
use pigdef::description::{BCMPinNumber, PinLevel};
use pigdef::pin_function::PinFunction;
use crate::pin_descriptions::*;
use pigdef::description::{HardwareDescription, HardwareDetails, PinDescriptionSet};
use crate::fake_pi::Pin::Output;
use rand_core::{OsRng, RngCore};
use std::time::{SystemTime, UNIX_EPOCH};
enum Pin {
Input(PinLevel, std::sync::mpsc::Sender<PinLevel>),
#[allow(dead_code)]
Output(PinLevel),
}
pub struct HW {
configured_pins: std::collections::HashMap<BCMPinNumber, Pin>,
hardware_description: HardwareDescription,
}
impl HW {
pub fn new() -> Self {
HW {
configured_pins: Default::default(),
hardware_description: HardwareDescription {
details: Self::get_details(),
pins: PinDescriptionSet::new(&GPIO_PIN_DESCRIPTIONS),
},
}
}
pub fn description(&self) -> &HardwareDescription {
&self.hardware_description
}
pub async fn apply_config<C>(&mut self, config: &HardwareConfig, callback: C) -> io::Result<()>
where
C: FnMut(BCMPinNumber, LevelChange) + Send + Sync + Clone + 'static,
{
for (bcm_pin_number, pin_function) in &config.pin_functions {
self.apply_pin_config(*bcm_pin_number, &Some(*pin_function), callback.clone())
.await?;
}
Ok(())
}
pub fn set_output_level(
&mut self,
bcm_pin_number: BCMPinNumber,
level: PinLevel,
) -> io::Result<()> {
match self.configured_pins.get_mut(&bcm_pin_number) {
Some(pin) => {
*pin = Output(level);
}
_ => return Err(io::Error::other("Could not find a configured output pin")),
}
Ok(())
}
fn get_details() -> HardwareDetails {
let mut details = HardwareDetails {
hardware: "fake gpio".to_string(),
revision: "unknown".to_string(),
serial: "unknown".to_string(),
model: "Fake local GPIO".to_string(),
wifi: true,
app_name: env!("CARGO_PKG_NAME").to_string(),
app_version: env!("CARGO_PKG_VERSION").to_string(),
};
{
let random_serial: u32 = OsRng.next_u32();
details.serial = format!("{:01$x}", random_serial, 18);
}
details
}
pub fn get_time_since_boot(&self) -> Duration {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
}
pub async fn apply_pin_config<C>(
&mut self,
bcm_pin_number: BCMPinNumber,
pin_function: &Option<PinFunction>,
mut callback: C,
) -> io::Result<()>
where
C: FnMut(BCMPinNumber, LevelChange) + Send + Sync + Clone + 'static,
{
if bcm_pin_number > self.hardware_description.pins.pins().len() as u8 {
return Err(io::Error::other("Invalid pin number"));
}
if let Some(Pin::Input(level, sender)) = self.configured_pins.get_mut(&bcm_pin_number) {
let _ = sender.send(*level);
self.configured_pins.remove(&bcm_pin_number);
}
match pin_function {
None => {
self.configured_pins.remove(&bcm_pin_number);
}
Some(PinFunction::Input(pullup)) => {
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
loop {
let level: bool = OsRng.next_u32() > (u32::MAX / 2);
#[allow(clippy::unwrap_used)]
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
callback(bcm_pin_number, LevelChange::new(level, now));
if receiver.recv_timeout(Duration::from_millis(666)).is_ok() {
return;
}
}
});
match pullup {
None | Some(InputPull::None) => self
.configured_pins
.insert(bcm_pin_number, Pin::Input(false, sender)),
Some(InputPull::PullDown) => self
.configured_pins
.insert(bcm_pin_number, Pin::Input(false, sender)),
Some(InputPull::PullUp) => self
.configured_pins
.insert(bcm_pin_number, Pin::Input(true, sender)),
};
}
Some(PinFunction::Output(opt_level)) => {
match opt_level {
None => self.configured_pins.insert(bcm_pin_number, Output(false)),
Some(level) => self.configured_pins.insert(bcm_pin_number, Output(*level)),
};
}
}
Ok(())
}
pub fn get_input_level(&self, _bcm_pin_number: BCMPinNumber) -> io::Result<bool> {
Ok(true)
}
}
impl Default for HW {
fn default() -> Self {
Self::new()
}
}