logic_mesh/blocks/control/
pid.rs1use std::time::Duration;
4
5use uuid::Uuid;
6
7use crate::base::output::props::OutputProps;
8use crate::blocks::utils::input_as_number;
9use crate::blocks::utils::input_to_millis_or_default;
10use crate::{
11 base::{
12 block::{Block, BlockDesc, BlockProps, BlockState},
13 input::{input_reader::InputReader, Input, InputProps},
14 output::Output,
15 },
16 blocks::utils::input_as_float_or_default,
17};
18use libhaystack::val::kind::HaystackKind;
19
20use crate::{blocks::InputImpl, blocks::OutputImpl};
21
22#[block]
25#[derive(BlockProps, Debug)]
26#[category = "control"]
27pub struct Pid {
28 #[input(kind = "Number")]
29 pub input: InputImpl,
30
31 #[input(kind = "Number")]
32 pub sp: InputImpl,
33
34 #[input(kind = "Number")]
35 pub kp: InputImpl,
36
37 #[input(kind = "Number")]
38 pub ki: InputImpl,
39
40 #[input(kind = "Number")]
41 pub kd: InputImpl,
42
43 #[input(kind = "Number")]
44 pub interval: InputImpl,
45
46 #[input(kind = "Number")]
47 pub min: InputImpl,
48
49 #[input(kind = "Number")]
50 pub max: InputImpl,
51
52 #[input(kind = "Number")]
53 pub bias: InputImpl,
54
55 #[output(kind = "Number")]
56 pub out: OutputImpl,
57
58 integral: f64,
59 derivative: f64,
60 last_error: f64,
61 last_value: f64,
62}
63
64impl Block for Pid {
65 async fn execute(&mut self) {
66 let millis = input_to_millis_or_default(&self.interval.val);
67
68 self.wait_on_inputs(Duration::from_millis(millis)).await;
69
70 if !self.out.is_connected() {
71 return;
72 }
73
74 let kp = input_as_number(&self.kp).map(|v| v.value).unwrap_or(0.98);
75 let ki = input_as_number(&self.ki).map(|v| v.value).unwrap_or(0.002);
76 let kd = input_as_number(&self.kd).map(|v| v.value).unwrap_or(0.25);
77 let bias = input_as_number(&self.kd).map(|v| v.value).unwrap_or(100.0);
78
79 if kp <= 0.0 || ki <= 0.0 || kd <= 0.0 {
80 return;
81 }
82
83 let sp = input_as_float_or_default(&self.sp);
84 let min = input_as_number(&self.min).map(|v| v.value).unwrap_or(0.0);
85 let max = input_as_number(&self.max).map(|v| v.value).unwrap_or(100.0);
86 let time = millis as f64;
87
88 let cur_value = if self.input.is_connected() {
89 input_as_number(&self.input).map(|v| v.value).unwrap_or(0.0)
90 } else {
91 TryInto::<f64>::try_into(self.out.value()).unwrap_or(0.0)
92 };
93
94 let error = sp - cur_value;
96
97 let proportional = kp * error;
99
100 self.integral += ki * time * (error + self.last_error);
102 self.integral = self.integral.clamp(min, max);
103
104 self.derivative = -(bias * kd * (cur_value - self.last_value)
106 + (bias - time) * self.derivative)
107 / (bias + time);
108
109 let mut output = proportional + self.integral + self.derivative;
110
111 if output > max {
112 output = max;
113 } else if output < min {
114 output = min;
115 }
116
117 self.last_value = output;
118 self.last_error = error;
119
120 self.out.set(output.into());
121 }
122}
123
124#[cfg(test)]
125mod test {
126
127 use crate::{
128 base::block::test_utils::write_block_inputs,
129 base::{block::Block, input::input_reader::InputReader, link::BaseLink},
130 blocks::control::Pid,
131 };
132
133 #[tokio::test]
134 async fn test_pid_block() {
135 let mut block = Pid::new();
136
137 for _ in write_block_inputs(&mut [
138 (&mut block.interval, (1).into()),
139 (&mut block.sp, (100).into()),
140 (&mut block.kp, (1.0).into()),
141 (&mut block.ki, (1.0).into()),
142 (&mut block.kd, (1.0).into()),
143 ])
144 .await
145 {
146 block.read_inputs().await;
147 }
148
149 block
150 .out
151 .links
152 .push(BaseLink::new(uuid::Uuid::new_v4(), "invalid".to_string()));
153
154 block.execute().await;
155 block.execute().await;
156 block.execute().await;
157 block.execute().await;
158
159 assert_eq!(block.out.value, 100.into());
160 }
161}