driver_pal/hal/
mod.rs

1use std::string::String;
2use std::time::{Duration, SystemTime};
3
4use clap::Parser;
5
6use serde::Deserialize;
7
8pub use simplelog::{LevelFilter, TermLogger, TerminalMode};
9
10pub mod error;
11pub use error::HalError;
12
13#[cfg(all(feature = "hal-linux", target_os = "linux"))]
14pub mod linux;
15
16#[cfg(feature = "hal-cp2130")]
17pub mod cp2130;
18
19use crate::*;
20
21/// Generic device configuration structure for SPI drivers
22#[derive(Debug, Parser, Deserialize)]
23pub struct DeviceConfig {
24    /// Linux SpiDev SPI device
25    #[clap(long, group = "spi-kind", env = "SPI_DEV")]
26    pub spi_dev: Option<String>,
27
28    /// CP2130 SPI device
29    #[clap(long, group = "spi-kind", env = "CP2130_DEV")]
30    pub cp2130_dev: Option<usize>,
31
32    #[clap(flatten)]
33    #[serde(flatten)]
34    pub spi: SpiConfig,
35
36    #[clap(flatten)]
37    #[serde(flatten)]
38    pub pins: PinConfig,
39}
40
41/// SPI device configuration
42#[derive(Debug, Clone, Parser, Deserialize)]
43pub struct SpiConfig {
44    /// Baud rate setting
45    #[clap(long = "spi-baud", default_value = "1000000", env = "SPI_BAUD")]
46    pub baud: u32,
47
48    /// SPI mode setting
49    #[clap(long = "spi-mode", default_value = "0", env = "SPI_MODE")]
50    pub mode: u32,
51}
52
53/// Pin configuration object
54#[derive(Debug, Clone, Parser, Deserialize)]
55pub struct PinConfig {
56    /// Chip Select (output) pin
57    #[clap(long = "cs-pin", default_value = "16", env = "CS_PIN")]
58    pub chip_select: u64,
59
60    /// Reset (output) pin
61    #[clap(long = "reset-pin", default_value = "17", env = "RESET_PIN")]
62    pub reset: u64,
63
64    /// Busy (input) pin
65    #[clap(long = "busy-pin", env = "BUSY_PIN")]
66    pub busy: Option<u64>,
67
68    /// Ready (input) pin
69    #[clap(long = "ready-pin", env = "READY_PIN")]
70    pub ready: Option<u64>,
71
72    /// LED 0 (output) pin
73    #[clap(long = "led0-pin", env = "LED0_PIN")]
74    pub led0: Option<u64>,
75
76    /// LED 1 (output) pin
77    #[clap(long = "led1-pin", env = "LED1_PIN")]
78    pub led1: Option<u64>,
79}
80
81/// Log configuration object
82#[derive(Debug, Parser)]
83pub struct LogConfig {
84    #[clap(long = "log-level", default_value = "info")]
85    /// Enable verbose logging
86    level: LevelFilter,
87}
88
89impl LogConfig {
90    /// Initialise logging with the provided level
91    pub fn init(&self) {
92        TermLogger::init(
93            self.level,
94            simplelog::Config::default(),
95            TerminalMode::Mixed,
96            simplelog::ColorChoice::Auto,
97        )
98        .unwrap();
99    }
100}
101
102/// HAL instance
103pub struct HalInst {
104    pub base: HalBase,
105    pub spi: HalSpi,
106    pub pins: HalPins,
107}
108impl HalInst {
109    /// Load a hal instance from the provided configuration
110    pub fn load(config: &DeviceConfig) -> Result<HalInst, HalError> {
111        // Process HAL configuration options
112        let hal = match (&config.spi_dev, &config.cp2130_dev) {
113            (Some(_), Some(_)) => {
114                error!("Only one of spi_dev and cp2130_dev may be specified");
115                return Err(HalError::InvalidConfig);
116            }
117            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
118            (Some(s), None) => {
119                debug!("Creating linux hal driver");
120                linux::LinuxDriver::new(s, &config.spi, &config.pins)?
121            }
122            #[cfg(all(feature = "hal-linux", not(target_os = "linux")))]
123            (Some(s), None) => {
124                error!("Linux HAL only supported on linux platforms");
125                return Err(HalError::InvalidConfig);
126            }
127            #[cfg(feature = "hal-cp2130")]
128            (None, Some(i)) => {
129                debug!("Creating cp2130 hal driver");
130                cp2130::Cp2130Driver::new(*i, &config.spi, &config.pins)?
131            }
132            _ => {
133                error!("No SPI configuration provided or no matching implementation found");
134                return Err(HalError::InvalidConfig);
135            }
136        };
137
138        Ok(hal)
139    }
140}
141
142/// Base storage for Hal instances
143pub enum HalBase {
144    #[cfg(feature = "hal-cp2130")]
145    Cp2130(driver_cp2130::Cp2130),
146    None,
147}
148
149/// SPI hal wrapper
150#[non_exhaustive]
151pub enum HalSpi {
152    #[cfg(all(feature = "hal-linux", target_os = "linux"))]
153    Linux(linux::LinuxSpiDevice),
154    #[cfg(feature = "hal-cp2130")]
155    Cp2130(driver_cp2130::Spi),
156}
157
158impl embedded_hal::spi::SpiDevice<u8> for HalSpi {
159    fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> {
160        match self {
161            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
162            HalSpi::Linux(i) => i.transaction(operations)?,
163            #[cfg(feature = "hal-cp2130")]
164            HalSpi::Cp2130(i) => i.transaction(operations)?,
165            #[allow(unreachable_patterns)]
166            _ => return Err(HalError::NoDriver),
167        }
168        Ok(())
169    }
170
171    fn write<'w>(&mut self, data: &[u8]) -> Result<(), Self::Error> {
172        match self {
173            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
174            HalSpi::Linux(i) => i.write(data)?,
175            #[cfg(feature = "hal-cp2130")]
176            HalSpi::Cp2130(i) => i.write(data)?,
177            #[allow(unreachable_patterns)]
178            _ => return Err(HalError::NoDriver),
179        }
180        Ok(())
181    }
182
183    fn transfer<'w>(&mut self, buff: &'w mut [u8], data: &'w [u8]) -> Result<(), Self::Error> {
184        match self {
185            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
186            HalSpi::Linux(i) => i.transfer(buff, data)?,
187            #[cfg(feature = "hal-cp2130")]
188            HalSpi::Cp2130(i) => i.transfer(buff, data)?,
189            #[allow(unreachable_patterns)]
190            _ => return Err(HalError::NoDriver),
191        }
192        Ok(())
193    }
194
195    fn transfer_in_place<'w>(&mut self, data: &'w mut [u8]) -> Result<(), Self::Error> {
196        match self {
197            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
198            HalSpi::Linux(i) => i.transfer_in_place(data)?,
199            #[cfg(feature = "hal-cp2130")]
200            HalSpi::Cp2130(i) => i.transfer_in_place(data)?,
201            #[allow(unreachable_patterns)]
202            _ => return Err(HalError::NoDriver),
203        }
204        Ok(())
205    }
206}
207
208impl embedded_hal::spi::ErrorType for HalSpi {
209    type Error = HalError;
210}
211
212/// Input pin hal wrapper
213#[non_exhaustive]
214pub enum HalInputPin {
215    #[cfg(all(feature = "hal-linux", target_os = "linux"))]
216    Linux(linux_embedded_hal::SysfsPin),
217    #[cfg(feature = "hal-cp2130")]
218    Cp2130(driver_cp2130::InputPin),
219    None,
220}
221
222impl embedded_hal::digital::InputPin for HalInputPin {
223    fn is_high(&mut self) -> Result<bool, Self::Error> {
224        let r = match self {
225            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
226            HalInputPin::Linux(i) => i.is_high()?,
227
228            #[cfg(feature = "hal-cp2130")]
229            HalInputPin::Cp2130(i) => i.is_high()?,
230
231            #[allow(unreachable_patterns)]
232            _ => return Err(HalError::NoPin),
233        };
234
235        Ok(r)
236    }
237
238    fn is_low(&mut self) -> Result<bool, Self::Error> {
239        Ok(!self.is_high()?)
240    }
241}
242
243impl embedded_hal::digital::ErrorType for HalInputPin {
244    type Error = HalError;
245}
246
247/// Output pin hal wrapper
248#[non_exhaustive]
249pub enum HalOutputPin {
250    #[cfg(all(feature = "hal-linux", target_os = "linux"))]
251    Linux(linux_embedded_hal::SysfsPin),
252    #[cfg(feature = "hal-cp2130")]
253    Cp2130(driver_cp2130::OutputPin),
254    None,
255}
256
257impl embedded_hal::digital::OutputPin for HalOutputPin {
258    fn set_high(&mut self) -> Result<(), Self::Error> {
259        match self {
260            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
261            HalOutputPin::Linux(i) => i.set_high()?,
262
263            #[cfg(feature = "hal-cp2130")]
264            HalOutputPin::Cp2130(i) => i.set_high()?,
265
266            #[allow(unreachable_patterns)]
267            _ => return Err(HalError::NoPin),
268        }
269        Ok(())
270    }
271
272    fn set_low(&mut self) -> Result<(), Self::Error> {
273        match self {
274            #[cfg(all(feature = "hal-linux", target_os = "linux"))]
275            HalOutputPin::Linux(i) => i.set_low()?,
276
277            #[cfg(feature = "hal-cp2130")]
278            HalOutputPin::Cp2130(i) => i.set_low()?,
279
280            #[allow(unreachable_patterns)]
281            _ => return Err(HalError::NoPin),
282        }
283        Ok(())
284    }
285}
286
287impl embedded_hal::digital::ErrorType for HalOutputPin {
288    type Error = HalError;
289}
290
291/// Load a configuration file
292pub fn load_config<T>(file: &str) -> T
293where
294    T: serde::de::DeserializeOwned,
295{
296    let d = std::fs::read_to_string(file).expect("error reading file");
297    toml::from_str(&d).expect("error parsing toml file")
298}
299
300/// HalPins object for conveniently returning bound pins
301pub struct HalPins {
302    pub reset: HalOutputPin,
303    pub busy: HalInputPin,
304    pub ready: HalInputPin,
305    pub led0: HalOutputPin,
306    pub led1: HalOutputPin,
307}
308
309/// HalDelay object based on blocking SystemTime::elapsed calls
310pub struct HalDelay;
311
312impl embedded_hal::delay::DelayNs for HalDelay {
313    fn delay_ns(&mut self, ns: u32) {
314        let n = SystemTime::now();
315        let d = Duration::from_nanos(ns as u64);
316        while n.elapsed().unwrap() < d {}
317    }
318}