use std::collections::HashMap;
use std::io;
use std::time::Duration;
use crate::pin_descriptions::*;
use pigdef::config::InputPull;
use pigdef::config::{HardwareConfig, LevelChange};
use pigdef::description::{BCMPinNumber, PinLevel};
use pigdef::description::{HardwareDescription, HardwareDetails, PinDescriptionSet};
use pigdef::pin_function::PinFunction;
use rppal::gpio::{Gpio, InputPin, Level, OutputPin, Trigger};
enum Pin {
Input(InputPin),
Output(OutputPin),
}
pub struct HW {
app_name: &'static str,
configured_pins: std::collections::HashMap<BCMPinNumber, Pin>,
}
impl HW {
pub fn new(app_name: &'static str) -> Self {
HW {
app_name,
configured_pins: HashMap::default(),
}
}
pub fn description(&self) -> HardwareDescription {
HardwareDescription {
details: Self::get_details(self.app_name),
pins: PinDescriptionSet::new(&GPIO_PIN_DESCRIPTIONS),
}
}
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::Output(output_pin)) => match level {
true => output_pin.write(Level::High),
false => output_pin.write(Level::Low),
},
_ => {
return Err(io::Error::new(
io::ErrorKind::Other,
"Could not find a configured output pin",
))
}
}
Ok(())
}
fn get_details(app_name: &str) -> HardwareDetails {
let mut details = HardwareDetails {
hardware: "fake".to_string(),
revision: "unknown".to_string(),
serial: "unknown".to_string(),
model: "Fake Pi".to_string(),
wifi: true,
app_name: app_name.to_string(),
app_version: env!("CARGO_PKG_VERSION").to_string(),
};
if let Ok(cpu_info) = std::fs::read_to_string("/proc/cpuinfo") {
for line in cpu_info.lines() {
match line
.split_once(':')
.map(|(key, value)| (key.trim(), value.trim()))
{
Some(("Hardware", hw)) => details.hardware = hw.to_string(),
Some(("Revision", revision)) => details.revision = revision.to_string(),
Some(("Serial", serial)) => details.serial = serial.to_string(),
Some(("Model", model)) => details.model = model.to_string(),
_ => {}
}
}
}
details
}
pub fn get_time_since_boot(&self) -> Duration {
let mut time = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut time) };
Duration::new(time.tv_sec as u64, time.tv_nsec as u32)
}
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,
{
self.configured_pins.remove(&bcm_pin_number);
match pin_function {
None => {
self.configured_pins.remove(&bcm_pin_number);
}
Some(PinFunction::Input(pull)) => {
let pin = Gpio::new()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
.get(bcm_pin_number)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let mut input = match pull {
None | Some(InputPull::None) => pin.into_input(),
Some(InputPull::PullUp) => pin.into_input_pullup(),
Some(InputPull::PullDown) => pin.into_input_pulldown(),
};
input
.set_async_interrupt(
Trigger::Both,
Some(Duration::from_millis(1)),
move |event| {
callback(
bcm_pin_number,
LevelChange::new(
event.trigger == Trigger::RisingEdge,
event.timestamp,
),
);
},
)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
self.configured_pins
.insert(bcm_pin_number, Pin::Input(input));
}
Some(PinFunction::Output(value)) => {
let pin = Gpio::new()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
.get(bcm_pin_number)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let output_pin = match value {
Some(true) => pin.into_output_high(),
Some(false) => pin.into_output_low(),
None => pin.into_output(),
};
self.configured_pins
.insert(bcm_pin_number, Pin::Output(output_pin));
}
}
Ok(())
}
pub fn get_input_level(&self, bcm_pin_number: BCMPinNumber) -> io::Result<bool> {
match self.configured_pins.get(&bcm_pin_number) {
Some(Pin::Input(input_pin)) => Ok(input_pin.read() == Level::High),
_ => Err(io::Error::new(
io::ErrorKind::Other,
"Could not find a configured input pin",
)),
}
}
}