Skip to main content

endbasic_std/gpio/
fakes.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! Fake implementations of GPIO pins that work on all platforms.
17
18use crate::gpio::{Pin, PinMode, Pins};
19use std::any::Any;
20use std::collections::VecDeque;
21use std::io;
22
23/// Stand-in implementation of the EndBASIC GPIO operations that always returns an error.
24#[derive(Default)]
25pub struct NoopPins {}
26
27impl Pins for NoopPins {
28    fn as_any(&self) -> &dyn Any {
29        self
30    }
31
32    fn as_any_mut(&mut self) -> &mut dyn Any {
33        self
34    }
35
36    fn setup(&mut self, _pin: Pin, _mode: PinMode) -> io::Result<()> {
37        Err(io::Error::other("GPIO backend not compiled in"))
38    }
39
40    fn clear(&mut self, _pin: Pin) -> io::Result<()> {
41        Err(io::Error::other("GPIO backend not compiled in"))
42    }
43
44    fn clear_all(&mut self) -> io::Result<()> {
45        Err(io::Error::other("GPIO backend not compiled in"))
46    }
47
48    fn read(&mut self, _pin: Pin) -> io::Result<bool> {
49        Err(io::Error::other("GPIO backend not compiled in"))
50    }
51
52    fn write(&mut self, _pin: Pin, _v: bool) -> io::Result<()> {
53        Err(io::Error::other("GPIO backend not compiled in"))
54    }
55}
56
57/// Per-pin operation identifier in the mock data.
58#[derive(PartialEq)]
59enum MockOp {
60    SetupIn = 1,
61    SetupInPullDown = 2,
62    SetupInPullUp = 3,
63    SetupOut = 4,
64
65    Clear = 5,
66    ClearAll = -1,
67
68    ReadLow = 10,
69    ReadHigh = 11,
70
71    WriteLow = 20,
72    WriteHigh = 21,
73}
74
75impl MockOp {
76    /// Encodes a `pin` and `op` pair as a trace datum.
77    fn encode(pin: Pin, op: Self) -> i32 {
78        assert!(op != Self::ClearAll);
79        (pin.0 as i32) * 100 + (op as i32)
80    }
81}
82
83/// Self-contained mock GPIO implementation that records operations and supplies pre-seeded reads.
84///
85/// Call `inject_read` to pre-seed reads before running code that calls `GPIO_READ`, and call
86/// `trace` afterwards to inspect the ordered record of all GPIO operations.
87#[derive(Default)]
88pub struct MockPins {
89    /// FIFO queue of pre-seeded reads: the next `read` call pops from the front.
90    reads: VecDeque<(Pin, bool)>,
91
92    /// Ordered record of all GPIO operations performed.
93    ///
94    /// Operations are encoded as `pin * 100 + MockOp` with `ClearAll` being encoded as `-1`.
95    trace: Vec<i32>,
96}
97
98impl MockPins {
99    /// Pre-seeds a future `read(pin)` call to return `high`.
100    pub fn inject_read(&mut self, pin: Pin, high: bool) {
101        self.reads.push_back((pin, high));
102    }
103
104    /// Returns the ordered trace of all GPIO operations performed so far.
105    pub fn trace(&self) -> &[i32] {
106        &self.trace
107    }
108}
109
110impl Pins for MockPins {
111    fn as_any(&self) -> &dyn Any {
112        self
113    }
114
115    fn as_any_mut(&mut self) -> &mut dyn Any {
116        self
117    }
118
119    fn setup(&mut self, pin: Pin, mode: PinMode) -> io::Result<()> {
120        let datum = match mode {
121            PinMode::In => MockOp::encode(pin, MockOp::SetupIn),
122            PinMode::InPullDown => MockOp::encode(pin, MockOp::SetupInPullDown),
123            PinMode::InPullUp => MockOp::encode(pin, MockOp::SetupInPullUp),
124            PinMode::Out => MockOp::encode(pin, MockOp::SetupOut),
125        };
126        self.trace.push(datum);
127        Ok(())
128    }
129
130    fn clear(&mut self, pin: Pin) -> io::Result<()> {
131        self.trace.push(MockOp::encode(pin, MockOp::Clear));
132        Ok(())
133    }
134
135    fn clear_all(&mut self) -> io::Result<()> {
136        self.trace.push(MockOp::ClearAll as i32);
137        Ok(())
138    }
139
140    fn read(&mut self, pin: Pin) -> io::Result<bool> {
141        match self.reads.pop_front() {
142            Some((read_pin, high)) if read_pin == pin => {
143                let op = if high { MockOp::ReadHigh } else { MockOp::ReadLow };
144                self.trace.push(MockOp::encode(pin, op));
145                Ok(high)
146            }
147            Some((read_pin, _)) => Err(io::Error::new(
148                io::ErrorKind::InvalidData,
149                format!("Want to read pin {} but next mock read is for pin {}", pin.0, read_pin.0),
150            )),
151            None => Err(io::Error::new(
152                io::ErrorKind::InvalidData,
153                format!("No mock read available for pin {}", pin.0),
154            )),
155        }
156    }
157
158    fn write(&mut self, pin: Pin, v: bool) -> io::Result<()> {
159        let op = if v { MockOp::WriteHigh } else { MockOp::WriteLow };
160        self.trace.push(MockOp::encode(pin, op));
161        Ok(())
162    }
163}