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
86impl 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}