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