1use bincode::de::Decoder;
2use bincode::enc::Encoder;
3use bincode::error::{DecodeError, EncodeError};
4use bincode::{Decode, Encode};
5use cu29::prelude::*;
6use serde::Serialize;
7use std::marker::PhantomData;
8
9#[derive(Debug, Default, Clone, Encode, Decode, Serialize)]
11pub struct PIDControlOutputPayload {
12 pub p: f32,
14 pub i: f32,
16 pub d: f32,
18 pub output: f32,
20}
21
22pub struct PIDController {
24 kp: f32,
26 ki: f32,
27 kd: f32,
28 setpoint: f32,
29 p_limit: f32,
30 i_limit: f32,
31 d_limit: f32,
32 output_limit: f32,
33 sampling: CuDuration,
34 integral: f32,
36 last_error: f32,
37 elapsed: CuDuration,
38 last_output: PIDControlOutputPayload,
39}
40
41impl PIDController {
42 #[allow(clippy::too_many_arguments)]
43 pub fn new(
44 kp: f32,
45 ki: f32,
46 kd: f32,
47 setpoint: f32,
48 p_limit: f32,
49 i_limit: f32,
50 d_limit: f32,
51 output_limit: f32,
52 sampling: CuDuration, ) -> Self {
54 PIDController {
55 kp,
56 ki,
57 kd,
58 setpoint,
59 integral: 0.0,
60 last_error: 0.0,
61 p_limit,
62 i_limit,
63 d_limit,
64 output_limit,
65 elapsed: CuDuration::default(),
66 sampling,
67 last_output: PIDControlOutputPayload::default(),
68 }
69 }
70
71 pub fn reset(&mut self) {
72 self.integral = 0.0f32;
73 self.last_error = 0.0f32;
74 }
75
76 pub fn init_measurement(&mut self, measurement: f32) {
77 self.last_error = self.setpoint - measurement;
78 self.elapsed = self.sampling; }
80
81 pub fn next_control_output(
82 &mut self,
83 measurement: f32,
84 dt: CuDuration,
85 ) -> PIDControlOutputPayload {
86 self.elapsed += dt;
87
88 if self.elapsed < self.sampling {
89 return self.last_output.clone();
91 }
92
93 let error = self.setpoint - measurement;
94 let CuDuration(elapsed) = self.elapsed;
95 let dt = elapsed as f32 / 1_000_000f32; let p_unbounded = self.kp * error;
99 let p = p_unbounded.clamp(-self.p_limit, self.p_limit);
100
101 self.integral += error * dt;
103 let i_unbounded = self.ki * self.integral;
104 let i = i_unbounded.clamp(-self.i_limit, self.i_limit);
105
106 let derivative = (error - self.last_error) / dt;
108 let d_unbounded = self.kd * derivative;
109 let d = d_unbounded.clamp(-self.d_limit, self.d_limit);
110
111 self.last_error = error;
113
114 let output_unbounded = p + i + d;
116 let output = output_unbounded.clamp(-self.output_limit, self.output_limit);
117
118 let output = PIDControlOutputPayload { p, i, d, output };
119
120 self.last_output = output.clone();
121 self.elapsed = CuDuration::default();
122 output
123 }
124}
125
126pub struct GenericPIDTask<I>
128where
129 f32: for<'a> From<&'a I>,
130{
131 _marker: PhantomData<I>,
132 pid: PIDController,
133 first_run: bool,
134 last_tov: CuTime,
135 setpoint: f32,
136 cutoff: f32,
137}
138
139impl<I> CuTask for GenericPIDTask<I>
140where
141 f32: for<'a> From<&'a I>,
142 I: CuMsgPayload,
143{
144 type Input<'m> = input_msg!(I);
145 type Output<'m> = output_msg!(PIDControlOutputPayload);
146
147 fn new(config: Option<&ComponentConfig>) -> CuResult<Self>
148 where
149 Self: Sized,
150 {
151 match config {
152 Some(config) => {
153 debug!("PIDTask config: {:?}", config);
154 let setpoint: f32 = config
155 .get::<f64>("setpoint")
156 .ok_or("'setpoint' not found in config")?
157 as f32;
158
159 let cutoff: f32 = config.get::<f64>("cutoff").ok_or(
160 "'cutoff' not found in config, please set an operating +/- limit on the input.",
161 )? as f32;
162
163 let kp = if let Some(kp) = config.get::<f64>("kp") {
165 Ok(kp as f32)
166 } else {
167 Err(CuError::from(
168 "'kp' not found in the config. We need at least 'kp' to make the PID algorithm work.",
169 ))
170 }?;
171
172 let p_limit = getcfg(config, "pl", 2.0f32);
173 let ki = getcfg(config, "ki", 0.0f32);
174 let i_limit = getcfg(config, "il", 1.0f32);
175 let kd = getcfg(config, "kd", 0.0f32);
176 let d_limit = getcfg(config, "dl", 2.0f32);
177 let output_limit = getcfg(config, "ol", 1.0f32);
178
179 let sampling = if let Some(value) = config.get::<u32>("sampling_ms") {
180 CuDuration::from(value as u64 * 1_000_000u64)
181 } else {
182 CuDuration::default()
183 };
184
185 let pid: PIDController = PIDController::new(
186 kp,
187 ki,
188 kd,
189 setpoint,
190 p_limit,
191 i_limit,
192 d_limit,
193 output_limit,
194 sampling,
195 );
196
197 Ok(Self {
198 _marker: PhantomData,
199 pid,
200 first_run: true,
201 last_tov: CuTime::default(),
202 setpoint,
203 cutoff,
204 })
205 }
206 None => Err(CuError::from("PIDTask needs a config.")),
207 }
208 }
209
210 fn process(
211 &mut self,
212 _clock: &RobotClock,
213 input: &Self::Input<'_>,
214 output: &mut Self::Output<'_>,
215 ) -> CuResult<()> {
216 match input.payload() {
217 Some(payload) => {
218 let tov = match input.tov {
219 Tov::Time(single) => single,
220 _ => return Err("Unexpected variant for a TOV of PID".into()),
221 };
222
223 let measure: f32 = payload.into();
224
225 if self.first_run {
226 self.first_run = false;
227 self.last_tov = tov;
228 self.pid.init_measurement(measure);
229 output.clear_payload();
230 return Ok(());
231 }
232 let dt = tov - self.last_tov;
233 self.last_tov = tov;
234
235 let state = self.pid.next_control_output(measure, dt);
237 if measure > self.setpoint + self.cutoff {
239 return Err(
240 format!("{} > {} (cutoff)", measure, self.setpoint + self.cutoff).into(),
241 );
242 }
243 if measure < self.setpoint - self.cutoff {
244 return Err(
245 format!("{} < {} (cutoff)", measure, self.setpoint - self.cutoff).into(),
246 );
247 }
248 output.metadata.set_status(format!(
249 "{:>5.2} {:>5.2} {:>5.2} {:>5.2}",
250 &state.output, &state.p, &state.i, &state.d
251 ));
252 output.set_payload(state);
253 }
254 None => output.clear_payload(),
255 };
256 Ok(())
257 }
258
259 fn stop(&mut self, _clock: &RobotClock) -> CuResult<()> {
260 self.pid.reset();
261 self.first_run = true;
262 Ok(())
263 }
264}
265
266impl<I> Freezable for GenericPIDTask<I>
268where
269 f32: for<'a> From<&'a I>,
270{
271 fn freeze<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
272 Encode::encode(&self.pid.integral, encoder)?;
273 Encode::encode(&self.pid.last_error, encoder)?;
274 Encode::encode(&self.pid.elapsed, encoder)?;
275 Encode::encode(&self.pid.last_output, encoder)?;
276 Ok(())
277 }
278
279 fn thaw<D: Decoder>(&mut self, decoder: &mut D) -> Result<(), DecodeError> {
280 self.pid.integral = Decode::decode(decoder)?;
281 self.pid.last_error = Decode::decode(decoder)?;
282 self.pid.elapsed = Decode::decode(decoder)?;
283 self.pid.last_output = Decode::decode(decoder)?;
284 Ok(())
285 }
286}
287
288fn getcfg(config: &ComponentConfig, key: &str, default: f32) -> f32 {
290 if let Some(value) = config.get::<f64>(key) {
291 value as f32
292 } else {
293 default
294 }
295}