thermostat/src/pid.rs

144 lines
3.9 KiB
Rust
Raw Normal View History

2024-10-14 11:52:15 +08:00
use serde::{Deserialize, Serialize};
2020-09-25 03:35:15 +08:00
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2020-03-19 04:51:30 +08:00
pub struct Parameters {
2020-12-18 23:29:53 +08:00
/// Gain coefficient for proportional term
pub kp: f32,
2020-12-18 23:29:53 +08:00
/// Gain coefficient for integral term
pub ki: f32,
2020-12-18 23:29:53 +08:00
/// Gain coefficient for derivative term
pub kd: f32,
2020-12-18 23:29:53 +08:00
/// Output limit minimum
pub output_min: f32,
2020-12-18 23:29:53 +08:00
/// Output limit maximum
pub output_max: f32,
2020-03-19 04:51:30 +08:00
}
2020-03-20 01:34:57 +08:00
impl Default for Parameters {
fn default() -> Self {
Parameters {
2021-01-08 11:31:33 +08:00
kp: 0.0,
ki: 0.0,
kd: 0.0,
output_min: -2.0,
2020-09-18 06:41:32 +08:00
output_max: 2.0,
2020-03-20 01:34:57 +08:00
}
}
}
2020-03-19 04:51:30 +08:00
#[derive(Clone)]
pub struct Controller {
2020-03-20 05:00:22 +08:00
pub parameters: Parameters,
2024-10-14 11:52:15 +08:00
pub target: f64,
u1: f64,
x1: f64,
x2: f64,
pub y1: f64,
2020-03-19 04:51:30 +08:00
}
impl Controller {
pub const fn new(parameters: Parameters) -> Controller {
Controller {
2024-10-07 12:54:19 +08:00
parameters,
2024-10-14 11:52:15 +08:00
target: 0.0,
u1: 0.0,
x1: 0.0,
x2: 0.0,
y1: 0.0,
2020-03-19 04:51:30 +08:00
}
}
// Based on https://hackmd.io/IACbwcOTSt6Adj3_F9bKuw PID implementation
// Input x(t), target u(t), output y(t)
2024-10-14 11:52:15 +08:00
// y0' = y1 - ki * u0
// + x0 * (kp + ki + kd)
// - x1 * (kp + 2kd)
// + x2 * kd
// y0 = clip(y0', ymin, ymax)
pub fn update(&mut self, input: f64) -> f64 {
let mut output: f64 = self.y1 - self.target * f64::from(self.parameters.ki)
2024-10-14 11:52:15 +08:00
+ input * f64::from(self.parameters.kp + self.parameters.ki + self.parameters.kd)
- self.x1 * f64::from(self.parameters.kp + 2.0 * self.parameters.kd)
+ self.x2 * f64::from(self.parameters.kd);
if output < self.parameters.output_min.into() {
output = self.parameters.output_min.into();
2020-03-19 04:51:30 +08:00
}
if output > self.parameters.output_max.into() {
output = self.parameters.output_max.into();
2020-03-19 04:51:30 +08:00
}
self.x2 = self.x1;
self.x1 = input;
self.u1 = self.target;
2024-10-14 11:52:15 +08:00
self.y1 = output;
2020-03-19 04:51:30 +08:00
output
}
2020-10-01 02:06:47 +08:00
pub fn summary(&self, channel: usize) -> Summary {
Summary {
channel,
parameters: self.parameters.clone(),
target: self.target,
}
}
2021-01-08 11:25:01 +08:00
pub fn update_ki(&mut self, new_ki: f32) {
self.parameters.ki = new_ki;
}
2020-10-01 02:06:47 +08:00
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Summary {
channel: usize,
parameters: Parameters,
target: f64,
}
2020-03-19 04:51:30 +08:00
#[cfg(test)]
mod test {
use super::*;
const PARAMETERS: Parameters = Parameters {
kp: 0.03,
ki: 0.002,
kd: 0.15,
2020-03-19 04:51:30 +08:00
output_min: -10.0,
output_max: 10.0,
};
#[test]
fn test_controller() {
// Initial and ambient temperature
2024-10-14 11:52:15 +08:00
const DEFAULT: f64 = 20.0;
// Target temperature
const TARGET: f64 = 40.0;
// Control tolerance
const ERROR: f64 = 0.01;
// System response delay
const DELAY: usize = 10;
// Heat lost
2024-10-14 11:52:15 +08:00
const LOSS: f64 = 0.05;
// Limit simulation cycle, reaching this limit before settling fails test
const CYCLE_LIMIT: u32 = 1000;
2020-03-19 04:51:30 +08:00
let mut pid = Controller::new(PARAMETERS.clone());
pid.target = TARGET;
2020-03-19 04:51:30 +08:00
let mut values = [DEFAULT; DELAY];
let mut t = 0;
let mut total_t = 0;
2021-01-16 11:04:24 +08:00
let mut output: f64 = 0.0;
2020-03-19 04:51:30 +08:00
let target = (TARGET - ERROR)..=(TARGET + ERROR);
while !values.iter().all(|value| target.contains(value)) && total_t < CYCLE_LIMIT {
2020-03-19 04:51:30 +08:00
let next_t = (t + 1) % DELAY;
// Feed the oldest temperature
output = pid.update(values[next_t]);
// Overwrite oldest with previous temperature - output
values[next_t] = values[t] - output - (values[t] - DEFAULT) * LOSS;
2020-03-19 04:51:30 +08:00
t = next_t;
total_t += 1;
println!("{}", values[t].to_string());
2020-03-19 04:51:30 +08:00
}
assert_ne!(CYCLE_LIMIT, total_t);
2020-03-19 04:51:30 +08:00
}
}