use crate::gpio::{Pin, PinMode, Pins};
use endbasic_core::ast::{ExprType, Value, VarRef};
use endbasic_core::syms::{Array, Symbol, Symbols};
use std::io;
#[derive(Default)]
pub(crate) struct NoopPins {}
impl Pins for NoopPins {
fn setup(&mut self, _pin: Pin, _mode: PinMode) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "GPIO backend not compiled in"))
}
fn clear(&mut self, _pin: Pin) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "GPIO backend not compiled in"))
}
fn clear_all(&mut self) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "GPIO backend not compiled in"))
}
fn read(&mut self, _pin: Pin) -> io::Result<bool> {
Err(io::Error::new(io::ErrorKind::Other, "GPIO backend not compiled in"))
}
fn write(&mut self, _pin: Pin, _v: bool) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "GPIO backend not compiled in"))
}
}
pub(crate) struct MockPins<'a> {
symbols: &'a mut Symbols,
}
#[derive(PartialEq)]
enum MockOp {
SetupIn = 1,
SetupInPullDown = 2,
SetupInPullUp = 3,
SetupOut = 4,
Clear = 5,
ClearAll = -1,
ReadLow = 10,
ReadHigh = 11,
WriteLow = 20,
WriteHigh = 21,
}
impl MockOp {
fn encode(pin: Pin, op: Self) -> i32 {
assert!(op != Self::ClearAll);
(pin.0 as i32) * 100 + (op as i32)
}
fn decode_read(pos: i32, datum: i32) -> io::Result<(Pin, bool)> {
if datum < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Negative read value at __GPIO_MOCK_DATA({})", pos),
));
}
let pin = datum / 100;
if pin > u8::MAX as i32 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Pin number too large at __GPIO_MOCK_DATA({})", pos),
));
}
let pin = Pin(pin as u8);
match datum % 100 {
i if i == (MockOp::ReadLow as i32) => Ok((pin, false)),
i if i == (MockOp::ReadHigh as i32) => Ok((pin, true)),
i => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unknown read operation {} at __GPIO_MOCK_DATA({})", i, pos),
)),
}
}
}
impl<'a> MockPins<'a> {
pub(crate) fn try_new(symbols: &'a mut Symbols) -> Option<MockPins<'a>> {
if MockPins::get_last(symbols).is_none() || MockPins::get_mut_data(symbols).is_none() {
None
} else {
Some(Self { symbols })
}
}
fn get_last(symbols: &Symbols) -> Option<i32> {
match symbols.get(&VarRef::new("__GPIO_MOCK_LAST", Some(ExprType::Integer))) {
Ok(Some(Symbol::Variable(Value::Integer(i)))) => Some(*i),
_ => None,
}
}
fn get_mut_data(symbols: &mut Symbols) -> Option<&mut Array> {
match symbols.get_mut(&VarRef::new("__GPIO_MOCK_DATA", Some(ExprType::Integer))) {
Ok(Some(Symbol::Array(data))) if data.dimensions().len() == 1 => Some(data),
_ => None,
}
}
fn raw_get(last: i32, data: &Array) -> io::Result<i32> {
match data.index(&[last]) {
Ok(Value::Integer(v)) => Ok(*v),
Ok(_) => panic!("We know it's an integer"),
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
}
}
fn increment_last(&mut self) -> io::Result<()> {
let last = MockPins::get_last(self.symbols).expect("Validated at construction time");
let new_last = Value::Integer(last + 1);
match self
.symbols
.set_var(&VarRef::new("__GPIO_MOCK_LAST", Some(ExprType::Integer)), new_last)
{
Ok(()) => Ok(()),
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
}
}
fn read_and_advance(&mut self) -> io::Result<(i32, i32)> {
let last = MockPins::get_last(self.symbols).expect("Validated at construction time");
let data = MockPins::get_mut_data(self.symbols).expect("Validated at construction time");
let v = MockPins::raw_get(last, data)?;
self.increment_last()?;
Ok((last, v))
}
fn append(&mut self, datum: i32) -> io::Result<()> {
let last = MockPins::get_last(self.symbols).expect("Validated at construction time");
let data = MockPins::get_mut_data(self.symbols).expect("Validated at construction time");
let old_datum = MockPins::raw_get(last, data)?;
if old_datum != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Position already occupied at __GPIO_MOCK_READ({})", last),
));
}
let data = MockPins::get_mut_data(self.symbols).expect("Validated at construction time");
match data.assign(&[last], Value::Integer(datum)) {
Ok(()) => (),
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
};
self.increment_last()
}
}
impl<'a> Pins for MockPins<'a> {
fn setup(&mut self, pin: Pin, mode: PinMode) -> io::Result<()> {
let datum = match mode {
PinMode::In => MockOp::encode(pin, MockOp::SetupIn),
PinMode::InPullDown => MockOp::encode(pin, MockOp::SetupInPullDown),
PinMode::InPullUp => MockOp::encode(pin, MockOp::SetupInPullUp),
PinMode::Out => MockOp::encode(pin, MockOp::SetupOut),
};
self.append(datum)
}
fn clear(&mut self, pin: Pin) -> io::Result<()> {
let datum = MockOp::encode(pin, MockOp::Clear);
self.append(datum)
}
fn clear_all(&mut self) -> io::Result<()> {
let datum = MockOp::ClearAll as i32;
self.append(datum)
}
fn read(&mut self, pin: Pin) -> io::Result<bool> {
let (pos, datum) = self.read_and_advance()?;
let (datum_pin, value) = MockOp::decode_read(pos, datum)?;
if datum_pin != pin {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Want to read pin {} but __GPIO_MOCK_DATA({}) is for pin {}",
pin.0, pos, datum_pin.0
),
));
}
Ok(value)
}
fn write(&mut self, pin: Pin, v: bool) -> io::Result<()> {
if v {
self.append(MockOp::encode(pin, MockOp::WriteHigh))
} else {
self.append(MockOp::encode(pin, MockOp::WriteLow))
}
}
}