Skip to main content

cu_rp_encoder/
lib.rs

1#[cfg(mock)]
2mod mock;
3
4use bincode::{Decode, Encode};
5use cu29::CuResult;
6use cu29::prelude::*;
7#[cfg(hardware)]
8use cu29::resource::Owned;
9use cu29::resource::{ResourceBindingMap, ResourceBindings, ResourceManager};
10use std::sync::{Arc, Mutex};
11
12#[allow(unused_imports)]
13use cu29_traits::CuError;
14
15#[cfg(hardware)]
16use cu_linux_resources::LinuxInputPin;
17#[cfg(mock)]
18use mock::{InputPin, get_pin};
19#[cfg(hardware)]
20use rppal::gpio::{Level, Trigger};
21use serde::Deserialize;
22
23#[cfg(hardware)]
24type InputPin = LinuxInputPin;
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq)]
27pub enum Binding {
28    ClkPin,
29    DatPin,
30}
31
32pub struct EncoderResources {
33    #[cfg(hardware)]
34    pub clk_pin: Owned<InputPin>,
35    #[cfg(hardware)]
36    pub dat_pin: Owned<InputPin>,
37}
38
39impl<'r> ResourceBindings<'r> for EncoderResources {
40    type Binding = Binding;
41
42    fn from_bindings(
43        manager: &'r mut ResourceManager,
44        mapping: Option<&ResourceBindingMap<Self::Binding>>,
45    ) -> CuResult<Self> {
46        #[cfg(hardware)]
47        {
48            let mapping = mapping.ok_or_else(|| {
49                CuError::from("Encoder requires `clk_pin` and `dat_pin` resource mappings")
50            })?;
51            let clk_pin = mapping.get(Binding::ClkPin).ok_or_else(|| {
52                CuError::from("Encoder resources must include `clk_pin: <bundle.resource>`")
53            })?;
54            let dat_pin = mapping.get(Binding::DatPin).ok_or_else(|| {
55                CuError::from("Encoder resources must include `dat_pin: <bundle.resource>`")
56            })?;
57            let clk_pin = manager
58                .take::<InputPin>(clk_pin.typed())
59                .map_err(|e| e.add_cause("Failed to fetch encoder clk pin resource"))?;
60            let dat_pin = manager
61                .take::<InputPin>(dat_pin.typed())
62                .map_err(|e| e.add_cause("Failed to fetch encoder dat pin resource"))?;
63            Ok(Self { clk_pin, dat_pin })
64        }
65        #[cfg(mock)]
66        {
67            let _ = manager;
68            let _ = mapping;
69            Ok(Self {})
70        }
71    }
72}
73
74#[allow(dead_code)]
75struct InterruptData {
76    dat_pin: InputPin,
77    ticks: i32,
78    tov: CuTime,
79}
80
81#[derive(Default, Clone, Debug, Encode, Decode, Serialize, Deserialize, Reflect)]
82pub struct EncoderPayload {
83    pub ticks: i32,
84}
85
86/// That allows the interfacing with the GenericPID
87impl From<&EncoderPayload> for f32 {
88    fn from(payload: &EncoderPayload) -> f32 {
89        payload.ticks as f32
90    }
91}
92
93#[allow(dead_code)]
94#[derive(Reflect)]
95#[reflect(from_reflect = false)]
96pub struct Encoder {
97    #[reflect(ignore)]
98    clk_pin: InputPin,
99    #[reflect(ignore)]
100    data_from_interrupts: Arc<Mutex<InterruptData>>,
101}
102
103impl Freezable for Encoder {
104    fn freeze<E: bincode::enc::Encoder>(
105        &self,
106        encoder: &mut E,
107    ) -> Result<(), bincode::error::EncodeError> {
108        let data = self.data_from_interrupts.lock().map_err(|_| {
109            bincode::error::EncodeError::OtherString(
110                "encoder interrupt data mutex poisoned".to_string(),
111            )
112        })?;
113        Encode::encode(&data.ticks, encoder)?;
114        Encode::encode(&data.tov, encoder)?;
115        Ok(())
116    }
117
118    fn thaw<D: bincode::de::Decoder>(
119        &mut self,
120        decoder: &mut D,
121    ) -> Result<(), bincode::error::DecodeError> {
122        let mut data = self.data_from_interrupts.lock().map_err(|_| {
123            bincode::error::DecodeError::OtherString(
124                "encoder interrupt data mutex poisoned".to_string(),
125            )
126        })?;
127        data.ticks = Decode::decode(decoder)?;
128        data.tov = Decode::decode(decoder)?;
129        Ok(())
130    }
131}
132
133impl CuSrcTask for Encoder {
134    type Resources<'r> = EncoderResources;
135    type Output<'m> = output_msg!(EncoderPayload);
136
137    fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
138    where
139        Self: Sized,
140    {
141        #[cfg(hardware)]
142        let clk_pin: InputPin = _resources.clk_pin.0;
143
144        #[cfg(mock)]
145        let clk_pin: InputPin = {
146            let clk_pin = pin_from_config(_config, "clk_pin")?;
147            get_pin(clk_pin)?
148        };
149
150        #[cfg(hardware)]
151        let dat_pin: InputPin = _resources.dat_pin.0;
152
153        #[cfg(mock)]
154        let dat_pin: InputPin = {
155            let dat_pin = pin_from_config(_config, "dat_pin")?;
156            get_pin(dat_pin)?
157        };
158
159        Ok(Self {
160            clk_pin,
161            data_from_interrupts: Arc::new(Mutex::new(InterruptData {
162                dat_pin,
163                ticks: 0,
164                tov: CuTime::default(),
165            })),
166        })
167    }
168
169    #[allow(unused_variables)]
170    fn start(&mut self, ctx: &CuContext) -> CuResult<()> {
171        let clock = ctx.clock.clone();
172        let idata = Arc::clone(&self.data_from_interrupts);
173        #[cfg(hardware)]
174        self.clk_pin
175            .get_mut()
176            .set_async_interrupt(Trigger::FallingEdge, None, move |_| {
177                let mut idata = idata.lock().unwrap();
178                if idata.dat_pin.get_mut().read() == Level::Low {
179                    idata.ticks -= 1;
180                } else {
181                    idata.ticks += 1;
182                }
183                idata.tov = clock.now();
184            })
185            .map_err(|e| CuError::new_with_cause("Failed to set async interrupt", e))?;
186        Ok(())
187    }
188
189    fn process(&mut self, ctx: &CuContext, new_msg: &mut Self::Output<'_>) -> CuResult<()> {
190        let idata = self.data_from_interrupts.lock().unwrap();
191        new_msg.tov = Some(ctx.now()).into();
192        new_msg.metadata.set_status(idata.ticks);
193        new_msg.set_payload(EncoderPayload { ticks: idata.ticks });
194        Ok(())
195    }
196    fn stop(&mut self, _ctx: &CuContext) -> CuResult<()> {
197        #[cfg(hardware)]
198        self.clk_pin
199            .get_mut()
200            .clear_async_interrupt()
201            .map_err(|e| CuError::new_with_cause("Failed to reset async interrupt", e))?;
202        Ok(())
203    }
204}
205
206#[cfg(mock)]
207fn pin_from_config(config: Option<&ComponentConfig>, key: &str) -> CuResult<u8> {
208    let config = config.ok_or("Encoder needs a config with clk_pin and dat_pin.")?;
209    config
210        .get::<u8>(key)?
211        .ok_or_else(|| CuError::from(format!("Encoder needs a {key}")))
212}