1use std::sync::Arc;
11use std::sync::Mutex;
12
13#[cfg(feature = "wasm")]
14use web_time::{Duration, Instant};
15
16#[cfg(not(feature = "wasm"))]
17use std::time::{Duration, Instant};
18
19#[cfg(feature = "debugging")]
20mod debug;
21
22#[cfg(feature = "debugging")]
23pub use debug::{ControllerDebugger, DebugConfig};
24
25#[derive(Debug, Clone)]
29pub struct ControllerConfig {
30 kp: f64, ki: f64, kd: f64, min_output: f64, max_output: f64, anti_windup: bool, setpoint: f64, deadband: f64, }
39
40#[derive(Debug, Clone, PartialEq)]
42pub enum PidError {
43 InvalidParameter(&'static str),
45 MutexPoisoned,
47}
48
49impl std::fmt::Display for PidError {
50 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
51 match self {
52 PidError::InvalidParameter(param) => write!(f, "Invalid parameter: {}", param),
53 PidError::MutexPoisoned => write!(f, "Mutex was poisoned"),
54 }
55 }
56}
57
58impl std::error::Error for PidError {}
59
60impl Default for ControllerConfig {
61 fn default() -> Self {
62 ControllerConfig {
63 kp: 1.0,
64 ki: 0.0,
65 kd: 0.0,
66 min_output: -f64::INFINITY,
67 max_output: f64::INFINITY,
68 anti_windup: true,
69 setpoint: 0.0,
70 deadband: 0.0,
71 }
72 }
73}
74
75impl ControllerConfig {
76 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn with_kp(mut self, kp: f64) -> Self {
100 if !kp.is_finite() {
101 panic!("Kp must be a finite number, got: {}", kp);
102 }
103 self.kp = kp;
104 self
105 }
106
107 pub fn with_ki(mut self, ki: f64) -> Self {
126 if !ki.is_finite() {
127 panic!("Ki must be a finite number, got: {}", ki);
128 }
129 self.ki = ki;
130 self
131 }
132
133 pub fn with_kd(mut self, kd: f64) -> Self {
152 if !kd.is_finite() {
153 panic!("Kd must be a finite number, got: {}", kd);
154 }
155 self.kd = kd;
156 self
157 }
158
159 pub fn with_output_limits(mut self, min: f64, max: f64) -> Self {
161 self.min_output = min;
162 self.max_output = max;
163 self
164 }
165
166 pub fn with_anti_windup(mut self, enable: bool) -> Self {
168 self.anti_windup = enable;
169 self
170 }
171
172 pub fn with_setpoint(mut self, setpoint: f64) -> Self {
186 if !setpoint.is_finite() {
187 panic!("Setpoint must be a finite number, got: {}", setpoint);
188 }
189 self.setpoint = setpoint;
190 self
191 }
192
193 pub fn with_deadband(mut self, deadband: f64) -> Self {
213 if !deadband.is_finite() {
214 panic!("Deadband must be a finite number, got: {}", deadband);
215 }
216
217 self.deadband = deadband.abs(); self
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct ControllerStatistics {
225 pub average_error: f64, pub max_overshoot: f64, pub settling_time: f64, pub rise_time: f64, }
230
231pub struct PidController {
243 config: ControllerConfig, integral: f64, prev_error: f64, first_run: bool, start_time: Instant,
250 error_sum: f64,
251 error_count: u64,
252 max_error: f64,
253 reached_setpoint: bool,
254 rise_time: Option<Duration>,
255 settle_time: Option<Duration>,
256 settled_threshold: f64, #[cfg(feature = "debugging")]
260 debugger: Option<ControllerDebugger>,
261}
262
263impl PidController {
264 pub fn new(config: ControllerConfig) -> Self {
266 PidController {
267 config,
268 integral: 0.0,
269 prev_error: 0.0,
270 first_run: true,
271 start_time: Instant::now(),
272 error_sum: 0.0,
273 error_count: 0,
274 max_error: 0.0,
275 reached_setpoint: false,
276 rise_time: None,
277 settle_time: None,
278 settled_threshold: 0.05, #[cfg(feature = "debugging")]
280 debugger: None,
281 }
282 }
283
284 pub fn compute(&mut self, process_value: f64, dt: f64) -> f64 {
297 let error = self.config.setpoint - process_value;
299
300 self.update_statistics(error);
302
303 let working_error = if error.abs() <= self.config.deadband {
305 0.0
306 } else {
307 error - self.config.deadband * error.signum()
309 };
310
311 if self.first_run {
313 self.first_run = false;
314 self.prev_error = working_error;
315 return 0.0; }
317
318 let p_term = self.config.kp * working_error;
320
321 self.integral += working_error * dt;
323 let i_term = self.config.ki * self.integral;
324
325 let d_term = if dt > 0.0 {
327 self.config.kd * ((working_error - self.prev_error) / dt)
328 } else {
329 0.0
330 };
331
332 self.prev_error = working_error;
334
335 let mut output = p_term + i_term + d_term;
337
338 if output > self.config.max_output {
340 output = self.config.max_output;
341
342 if self.config.anti_windup {
344 self.integral -= working_error * dt;
345 }
346 } else if output < self.config.min_output {
347 output = self.config.min_output;
348
349 if self.config.anti_windup {
351 self.integral -= working_error * dt;
352 }
353 }
354
355 #[cfg(feature = "debugging")]
357 if let Some(ref mut debugger) = self.debugger {
358 debugger.log_pid_state(working_error, p_term, i_term, d_term, output, dt);
359 }
360
361 output
362 }
363
364 pub fn reset(&mut self) {
366 self.integral = 0.0;
367 self.prev_error = 0.0;
368 self.first_run = true;
369 self.start_time = Instant::now();
370 self.error_sum = 0.0;
371 self.error_count = 0;
372 self.max_error = 0.0;
373 self.reached_setpoint = false;
374 self.rise_time = None;
375 self.settle_time = None;
376 }
377
378 pub fn set_kp(&mut self, kp: f64) -> Result<(), PidError> {
392 if !kp.is_finite() {
393 return Err(PidError::InvalidParameter("kp must be a finite number"));
394 }
395 self.config.kp = kp;
396 Ok(())
397 }
398
399 pub fn set_ki(&mut self, ki: f64) -> Result<(), PidError> {
413 if !ki.is_finite() {
414 return Err(PidError::InvalidParameter("ki must be a finite number"));
415 }
416 self.config.ki = ki;
417 Ok(())
418 }
419
420 pub fn set_kd(&mut self, kd: f64) -> Result<(), PidError> {
434 if !kd.is_finite() {
435 return Err(PidError::InvalidParameter("kd must be a finite number"));
436 }
437 self.config.kd = kd;
438 Ok(())
439 }
440
441 pub fn set_output_limits(&mut self, min: f64, max: f64) {
443 self.config.min_output = min;
444 self.config.max_output = max;
445 }
446
447 pub fn set_anti_windup(&mut self, enable: bool) {
449 self.config.anti_windup = enable;
450 }
451
452 pub fn set_setpoint(&mut self, setpoint: f64) -> Result<(), PidError> {
462 if !setpoint.is_finite() {
463 return Err(PidError::InvalidParameter(
464 "setpoint must be a finite number",
465 ));
466 }
467
468 self.config.setpoint = setpoint;
469 Ok(())
470 }
471
472 pub fn setpoint(&self) -> f64 {
474 self.config.setpoint
475 }
476
477 pub fn get_statistics(&self) -> ControllerStatistics {
479 let avg_error = if self.error_count > 0 {
480 self.error_sum / self.error_count as f64
481 } else {
482 0.0
483 };
484
485 let settling_time = match self.settle_time {
486 Some(time) => time.as_secs_f64(),
487 None => (Instant::now() - self.start_time).as_secs_f64(),
488 };
489
490 let rise_time = match self.rise_time {
491 Some(time) => time.as_secs_f64(),
492 None => f64::NAN,
493 };
494
495 ControllerStatistics {
496 average_error: avg_error,
497 max_overshoot: self.max_error,
498 settling_time,
499 rise_time,
500 }
501 }
502
503 pub fn set_settled_threshold(&mut self, threshold: f64) {
505 self.settled_threshold = threshold;
506 }
507
508 pub fn set_deadband(&mut self, deadband: f64) -> Result<(), PidError> {
518 if !deadband.is_finite() {
519 return Err(PidError::InvalidParameter(
520 "deadband must be a finite number",
521 ));
522 }
523
524 self.config.deadband = deadband.abs(); Ok(())
526 }
527
528 fn update_statistics(&mut self, error: f64) {
530 self.error_sum += error.abs();
532 self.error_count += 1;
533
534 if error.abs() > self.max_error {
536 self.max_error = error.abs();
537 }
538
539 if !self.reached_setpoint && error.abs() <= self.settled_threshold {
541 self.reached_setpoint = true;
542 self.rise_time = Some(Instant::now() - self.start_time);
543 }
544
545 if self.reached_setpoint
547 && error.abs() <= self.settled_threshold
548 && self.settle_time.is_none()
549 {
550 self.settle_time = Some(Instant::now() - self.start_time);
551 } else if error.abs() > self.settled_threshold {
552 self.settle_time = None;
554 }
555 }
556
557 #[cfg(feature = "debugging")]
558 pub fn with_debugging(mut self, debug_config: DebugConfig) -> Self {
559 self.debugger = Some(ControllerDebugger::new(debug_config));
560 self
561 }
562}
563
564pub struct ThreadSafePidController {
569 controller: Arc<Mutex<PidController>>,
570}
571
572impl Clone for ThreadSafePidController {
573 fn clone(&self) -> Self {
574 ThreadSafePidController {
575 controller: Arc::clone(&self.controller),
576 }
577 }
578}
579
580impl ThreadSafePidController {
581 pub fn new(config: ControllerConfig) -> Self {
583 ThreadSafePidController {
584 controller: Arc::new(Mutex::new(PidController::new(config))),
585 }
586 }
587
588 pub fn compute(&self, process_value: f64, dt: f64) -> Result<f64, PidError> {
620 let mut controller = self
621 .controller
622 .lock()
623 .map_err(|_| PidError::MutexPoisoned)?;
624 Ok(controller.compute(process_value, dt))
625 }
626
627 pub fn reset(&self) -> Result<(), PidError> {
629 let mut controller = self
630 .controller
631 .lock()
632 .map_err(|_| PidError::MutexPoisoned)?;
633 controller.reset();
634 Ok(())
635 }
636
637 pub fn get_control_signal(&self) -> Result<f64, PidError> {
639 let controller = self
640 .controller
641 .lock()
642 .map_err(|_| PidError::MutexPoisoned)?;
643
644 if controller.first_run {
646 return Ok(0.0);
647 }
648
649 let p_term = controller.config.kp * controller.prev_error;
652 let i_term = controller.config.ki * controller.integral;
653 let d_term = 0.0; let mut output = p_term + i_term + d_term;
656
657 if output > controller.config.max_output {
659 output = controller.config.max_output;
660 } else if output < controller.config.min_output {
661 output = controller.config.min_output;
662 }
663
664 Ok(output)
665 }
666
667 pub fn set_kp(&self, kp: f64) -> Result<(), PidError> {
669 let mut controller = self
670 .controller
671 .lock()
672 .map_err(|_| PidError::MutexPoisoned)?;
673 controller.set_kp(kp)
674 }
675
676 pub fn set_ki(&self, ki: f64) -> Result<(), PidError> {
678 let mut controller = self
679 .controller
680 .lock()
681 .map_err(|_| PidError::MutexPoisoned)?;
682 controller.set_ki(ki)
683 }
684
685 pub fn set_kd(&self, kd: f64) -> Result<(), PidError> {
687 let mut controller = self
688 .controller
689 .lock()
690 .map_err(|_| PidError::MutexPoisoned)?;
691 controller.set_kd(kd)
692 }
693
694 pub fn set_output_limits(&self, min: f64, max: f64) -> Result<(), PidError> {
696 let mut controller = self
697 .controller
698 .lock()
699 .map_err(|_| PidError::MutexPoisoned)?;
700 controller.set_output_limits(min, max);
701 Ok(())
702 }
703
704 pub fn set_setpoint(&self, setpoint: f64) -> Result<(), PidError> {
714 let mut controller = self
715 .controller
716 .lock()
717 .map_err(|_| PidError::MutexPoisoned)?;
718 controller.set_setpoint(setpoint)
719 }
720
721 pub fn get_statistics(&self) -> Result<ControllerStatistics, PidError> {
723 let controller = self
724 .controller
725 .lock()
726 .map_err(|_| PidError::MutexPoisoned)?;
727 Ok(controller.get_statistics())
728 }
729
730 pub fn set_deadband(&self, deadband: f64) -> Result<(), PidError> {
740 let mut controller = self
741 .controller
742 .lock()
743 .map_err(|_| PidError::MutexPoisoned)?;
744 controller.set_deadband(deadband)
745 }
746
747 #[cfg(feature = "debugging")]
749 pub fn with_debugging(self, debug_config: DebugConfig) -> Result<Self, PidError> {
750 let lock = self
752 .controller
753 .lock()
754 .map_err(|_| PidError::MutexPoisoned)?;
755
756 let debug_config_clone = debug_config.clone();
759
760 #[allow(unused_mut)]
763 let mut pid_controller = PidController {
764 config: lock.config.clone(),
765 integral: lock.integral,
766 prev_error: lock.prev_error,
767 first_run: lock.first_run,
768 start_time: lock.start_time,
769 error_sum: lock.error_sum,
770 error_count: lock.error_count,
771 max_error: lock.max_error,
772 reached_setpoint: lock.reached_setpoint,
773 rise_time: lock.rise_time,
774 settle_time: lock.settle_time,
775 settled_threshold: lock.settled_threshold,
776 debugger: Some(ControllerDebugger::new(debug_config_clone)),
777 };
778
779 Ok(ThreadSafePidController {
781 controller: Arc::new(Mutex::new(pid_controller)),
782 })
783 }
784}
785
786#[cfg(test)]
787mod tests {
788 use super::*;
789 use std::thread;
790 use std::time::Duration;
791
792 #[test]
793 fn test_pid_control() {
794 let config = ControllerConfig::new()
796 .with_kp(2.0) .with_ki(0.5) .with_kd(0.05)
799 .with_output_limits(-100.0, 100.0)
800 .with_setpoint(10.0); let mut controller = PidController::new(config);
803
804 let mut process_value = 0.0;
806 let dt = 0.1; for _ in 0..200 {
810 let control_signal = controller.compute(process_value, dt);
812
813 process_value += control_signal * dt * 0.1;
815
816 if process_value > 9.0 && process_value < 11.0 {
818 break;
819 }
820 }
821
822 assert!((process_value - 10.0).abs() < 1.0);
824 }
825
826 #[test]
827 fn test_anti_windup() {
828 println!("\n--- Testing Anti-Windup Effect ---");
829
830 let config_with_aw = ControllerConfig::new()
832 .with_kp(0.5)
833 .with_ki(0.5)
834 .with_kd(0.0)
835 .with_setpoint(10.0)
836 .with_output_limits(-1.0, 1.0) .with_anti_windup(true); let config_without_aw = ControllerConfig::new()
840 .with_kp(0.5)
841 .with_ki(0.5)
842 .with_kd(0.0)
843 .with_setpoint(10.0)
844 .with_output_limits(-1.0, 1.0) .with_anti_windup(false); let mut controller_with_aw = PidController::new(config_with_aw);
848 let mut controller_without_aw = PidController::new(config_without_aw);
849
850 controller_with_aw.compute(0.0, 0.1);
852 controller_without_aw.compute(0.0, 0.1);
853
854 let dt = 0.1;
857 let process_value = 0.0;
858
859 println!("Running controllers with large error (10.0) for 20 iterations");
860
861 for i in 0..20 {
863 let out_with_aw = controller_with_aw.compute(process_value, dt);
864 let out_without_aw = controller_without_aw.compute(process_value, dt);
865
866 if i > 2 {
868 assert_eq!(
869 out_with_aw, 1.0,
870 "Controller with anti-windup should saturate"
871 );
872 assert_eq!(
873 out_without_aw, 1.0,
874 "Controller without anti-windup should saturate"
875 );
876 }
877
878 if i % 5 == 0 {
879 println!(
880 "Iteration {}: With AW = {:.4}, Without AW = {:.4}",
881 i, out_with_aw, out_without_aw
882 );
883 }
884 }
885
886 let integral_with_aw = controller_with_aw.integral;
889 let integral_without_aw = controller_without_aw.integral;
890
891 println!("Integral term after saturation:");
892 println!(" With anti-windup: {:.4}", integral_with_aw);
893 println!(" Without anti-windup: {:.4}", integral_without_aw);
894
895 assert!(
897 integral_with_aw < integral_without_aw,
898 "Anti-windup should limit integral term during saturation"
899 );
900
901 println!("\nTesting recovery by setting process value to 9.5");
904 let recovery_value = 9.5; let recovery_with_aw = controller_with_aw.compute(recovery_value, dt);
907 let recovery_without_aw = controller_without_aw.compute(recovery_value, dt);
908
909 println!("Recovery outputs:");
910 println!(" With anti-windup: {:.4}", recovery_with_aw);
911 println!(" Without anti-windup: {:.4}", recovery_without_aw);
912
913 assert!(
916 recovery_with_aw < recovery_without_aw,
917 "Controller with anti-windup should recover faster"
918 );
919 }
920
921 #[test]
922 fn test_deadband() {
923 let config = ControllerConfig::new()
925 .with_kp(1.0) .with_ki(0.0)
927 .with_kd(0.0)
928 .with_deadband(5.0)
929 .with_setpoint(0.0) .with_output_limits(-100.0, 100.0);
931
932 let mut controller = PidController::new(config);
933
934 let process_small_error = -3.0;
937 let output1 = controller.compute(process_small_error, 0.1);
938 assert_eq!(
939 output1, 0.0,
940 "Error within deadband should produce zero output"
941 );
942
943 let process_small_negative_error = 4.0;
946 let output2 = controller.compute(process_small_negative_error, 0.1);
947 assert_eq!(
948 output2, 0.0,
949 "Negative error within deadband should produce zero output"
950 );
951
952 let process_large_error = -15.0;
956 let output3 = controller.compute(process_large_error, 0.1);
957 assert_eq!(
958 output3, 10.0,
959 "Error outside deadband should be reduced by deadband"
960 );
961
962 let process_large_negative_error = 25.0;
966 let output4 = controller.compute(process_large_negative_error, 0.1);
967 assert_eq!(
968 output4, -20.0,
969 "Negative error outside deadband should be reduced by deadband"
970 );
971
972 controller.set_deadband(10.0).unwrap();
974
975 let output5 = controller.compute(-15.0, 0.1);
979 assert_eq!(output5, 5.0, "Output should reflect updated deadband value");
980
981 assert!(controller.set_deadband(f64::NAN).is_err());
983 assert!(controller.set_deadband(f64::INFINITY).is_err());
984 }
985
986 #[test]
987 fn test_thread_safe_controller() {
988 let config = ControllerConfig::new()
990 .with_kp(1.0)
991 .with_ki(0.1)
992 .with_kd(0.0)
993 .with_output_limits(-10.0, 10.0)
994 .with_setpoint(10.0);
995
996 let controller = ThreadSafePidController::new(config);
997
998 let thread_controller = controller.clone();
1000
1001 let handle = thread::spawn(move || {
1003 for i in 0..100 {
1004 let process_value = i as f64 * 0.1;
1005 match thread_controller.compute(process_value, 0.01) {
1007 Ok(_) => {}
1008 Err(e) => panic!("Failed to compute: {:?}", e),
1009 }
1010 thread::sleep(Duration::from_millis(1));
1011 }
1012 });
1013
1014 for _ in 0..10 {
1016 let _ = controller
1018 .get_control_signal()
1019 .expect("Failed to get control signal");
1020 thread::sleep(Duration::from_millis(5));
1021 }
1022
1023 handle.join().unwrap();
1025
1026 let stats = controller
1028 .get_statistics()
1029 .expect("Failed to get statistics");
1030 assert!(stats.average_error > 0.0);
1031 }
1032
1033 #[test]
1034 fn test_parameter_validation() {
1035 let mut controller = PidController::new(ControllerConfig::default());
1036
1037 assert!(controller.set_setpoint(100.0).is_ok());
1039 assert!(controller.set_setpoint(-100.0).is_ok());
1040 assert!(controller.set_setpoint(f64::NAN).is_err());
1041 assert!(controller.set_setpoint(f64::INFINITY).is_err());
1042
1043 assert!(controller.set_deadband(0.0).is_ok());
1045 assert!(controller.set_deadband(10.0).is_ok());
1046 assert!(controller.set_deadband(-5.0).is_ok()); assert!(controller.set_deadband(f64::NAN).is_err());
1048 assert!(controller.set_deadband(f64::INFINITY).is_err());
1049
1050 assert!(controller.set_kp(1.0).is_ok());
1052 assert!(controller.set_kp(-1.0).is_ok()); assert!(controller.set_kp(f64::NAN).is_err());
1054 assert!(controller.set_kp(f64::INFINITY).is_err());
1055
1056 assert!(controller.set_ki(0.5).is_ok());
1057 assert!(controller.set_ki(-0.5).is_ok()); assert!(controller.set_ki(f64::NAN).is_err());
1059 assert!(controller.set_ki(f64::INFINITY).is_err());
1060
1061 assert!(controller.set_kd(0.1).is_ok());
1062 assert!(controller.set_kd(-0.1).is_ok()); assert!(controller.set_kd(f64::NAN).is_err());
1064 assert!(controller.set_kd(f64::INFINITY).is_err());
1065 }
1066
1067 #[test]
1068 fn test_negative_gains() {
1069 let config = ControllerConfig::new()
1071 .with_kp(-2.0) .with_ki(0.0) .with_kd(0.0) .with_setpoint(0.0) .with_output_limits(-100.0, 100.0); let mut controller = PidController::new(config);
1078
1079 let init_output = controller.compute(0.0, 0.1);
1081 assert_eq!(init_output, 0.0, "First run should return 0.0");
1082
1083 let process_value_negative = -5.0;
1086 let output_for_negative_process = controller.compute(process_value_negative, 0.1);
1087 assert_eq!(
1088 output_for_negative_process, -10.0,
1089 "With Kp=-2.0, setpoint=0.0, process_value=-5.0 should give output=-10.0, got {}",
1090 output_for_negative_process
1091 );
1092
1093 controller.reset();
1095 let _ = controller.compute(0.0, 0.1); let process_value_positive = 5.0;
1100 let output_for_positive_process = controller.compute(process_value_positive, 0.1);
1101 assert_eq!(
1102 output_for_positive_process, 10.0,
1103 "With Kp=-2.0, setpoint=0.0, process_value=5.0 should give output=10.0, got {}",
1104 output_for_positive_process
1105 );
1106 }
1107
1108 #[test]
1109 fn test_steady_state_precision() {
1110 let config = ControllerConfig::new()
1112 .with_kp(10.0)
1113 .with_ki(2.0)
1114 .with_kd(8.0)
1115 .with_output_limits(0.0, 100.0)
1116 .with_setpoint(10.0)
1117 .with_anti_windup(true);
1118
1119 let mut controller = PidController::new(config);
1120
1121 let mut value = 0.0;
1123 let dt = 0.05;
1124
1125 println!(
1126 "Testing controller precision with default settled_threshold: {}",
1127 controller.settled_threshold
1128 );
1129
1130 for i in 0..200 {
1132 let control_signal = controller.compute(value, dt);
1133
1134 value += control_signal * dt * 0.01;
1136
1137 if i % 20 == 0 {
1139 println!(
1140 "Iteration {}: Value = {:.6}, Error = {:.6}, Control = {:.6}",
1141 i,
1142 value,
1143 10.0 - value,
1144 control_signal
1145 );
1146 }
1147 }
1148
1149 let final_error = (10.0 - value).abs();
1151 println!("Final value: {:.6}, Error: {:.6}", value, final_error);
1152
1153 println!("\nResetting and using smaller settled_threshold...");
1155 controller.reset();
1156 controller.set_settled_threshold(0.001);
1157 value = 0.0;
1158
1159 for i in 0..200 {
1161 let control_signal = controller.compute(value, dt);
1162 value += control_signal * dt * 0.01;
1163
1164 if i % 20 == 0 {
1165 println!(
1166 "Iteration {}: Value = {:.6}, Error = {:.6}, Control = {:.6}",
1167 i,
1168 value,
1169 10.0 - value,
1170 control_signal
1171 );
1172 }
1173 }
1174
1175 let final_error_2 = (10.0 - value).abs();
1177 println!(
1178 "Final value with smaller threshold: {:.6}, Error: {:.6}",
1179 value, final_error_2
1180 );
1181 }
1182
1183 #[test]
1184 fn test_deadband_effect() {
1185 println!("\n--- Testing Deadband Effect ---");
1186
1187 let config = ControllerConfig::new()
1189 .with_kp(10.0)
1190 .with_ki(2.0)
1191 .with_kd(0.0) .with_output_limits(0.0, 100.0)
1193 .with_setpoint(10.0)
1194 .with_deadband(0.0); let mut controller = PidController::new(config);
1197
1198 let mut value = 9.95; let dt = 0.05;
1201
1202 println!("Starting with process value: {:.6}", value);
1203
1204 for i in 0..100 {
1206 let control_signal = controller.compute(value, dt);
1207 value += control_signal * dt * 0.01;
1208
1209 if i % 10 == 0 || i > 90 {
1210 println!(
1211 "Iteration {}: Value = {:.6}, Error = {:.6}, Integral = {:.6}",
1212 i,
1213 value,
1214 10.0 - value,
1215 controller.integral
1216 );
1217 }
1218 }
1219
1220 let final_value_no_deadband = value;
1222 println!(
1223 "Final value with zero deadband: {:.6}",
1224 final_value_no_deadband
1225 );
1226
1227 let config = ControllerConfig::new()
1229 .with_kp(10.0)
1230 .with_ki(2.0)
1231 .with_kd(0.0)
1232 .with_output_limits(0.0, 100.0)
1233 .with_setpoint(10.0)
1234 .with_deadband(0.05); let mut controller = PidController::new(config);
1237
1238 value = 9.95;
1240
1241 println!(
1242 "\nStarting with process value: {:.6} and deadband: 0.05",
1243 value
1244 );
1245
1246 for i in 0..100 {
1248 let control_signal = controller.compute(value, dt);
1249 value += control_signal * dt * 0.01;
1250
1251 if i % 10 == 0 || i > 90 {
1252 let error = 10.0 - value;
1254 let working_error = if error.abs() <= 0.05 {
1255 0.0
1256 } else {
1257 error - 0.05 * error.signum()
1258 };
1259
1260 println!(
1261 "Iteration {}: Value = {:.6}, Raw Error = {:.6}, Working Error = {:.6}",
1262 i, value, error, working_error
1263 );
1264 }
1265 }
1266
1267 let final_value_with_deadband = value;
1269 println!(
1270 "Final value with 0.05 deadband: {:.6}",
1271 final_value_with_deadband
1272 );
1273
1274 println!("\nComparing results:");
1276 println!("Without deadband: {:.6}", final_value_no_deadband);
1277 println!("With 0.05 deadband: {:.6}", final_value_with_deadband);
1278 println!(
1279 "Difference: {:.6}",
1280 (final_value_no_deadband - final_value_with_deadband).abs()
1281 );
1282 }
1283
1284 #[test]
1285 fn test_anti_windup_effect_on_precision() {
1286 println!("\n--- Testing Anti-Windup Effect on Precision ---");
1287
1288 let config_with_aw = ControllerConfig::new()
1290 .with_kp(10.0)
1291 .with_ki(2.0)
1292 .with_kd(0.0)
1293 .with_output_limits(0.0, 100.0)
1294 .with_setpoint(10.0)
1295 .with_deadband(0.0)
1296 .with_anti_windup(true);
1297
1298 let config_without_aw = ControllerConfig::new()
1300 .with_kp(10.0)
1301 .with_ki(2.0)
1302 .with_kd(0.0)
1303 .with_output_limits(0.0, 100.0)
1304 .with_setpoint(10.0)
1305 .with_deadband(0.0)
1306 .with_anti_windup(false);
1307
1308 let mut controller_with_aw = PidController::new(config_with_aw);
1309 let mut controller_without_aw = PidController::new(config_without_aw);
1310
1311 let mut value_with_aw = 0.0;
1313 let mut value_without_aw = 0.0;
1314 let dt = 0.05;
1315
1316 for i in 0..200 {
1318 let control_with_aw = controller_with_aw.compute(value_with_aw, dt);
1319 let control_without_aw = controller_without_aw.compute(value_without_aw, dt);
1320
1321 value_with_aw += control_with_aw * dt * 0.01;
1323 value_without_aw += control_without_aw * dt * 0.01;
1324
1325 if i % 20 == 0 {
1326 println!(
1327 "Iteration {}: With AW = {:.6}, Without AW = {:.6}, Diff = {:.6}",
1328 i,
1329 value_with_aw,
1330 value_without_aw,
1331 (value_with_aw - value_without_aw).abs()
1332 );
1333 }
1334 }
1335
1336 println!("\nFinal values after 200 iterations:");
1338 println!(
1339 "With anti-windup: {:.6}, Error = {:.6}",
1340 value_with_aw,
1341 (10.0 - value_with_aw).abs()
1342 );
1343 println!(
1344 "Without anti-windup: {:.6}, Error = {:.6}",
1345 value_without_aw,
1346 (10.0 - value_without_aw).abs()
1347 );
1348 }
1349
1350 #[test]
1351 fn test_gravity_compensation() {
1352 println!("\n--- Testing Gravity Effect on Steady-State ---");
1353
1354 let config = ControllerConfig::new()
1356 .with_kp(10.0)
1357 .with_ki(2.0)
1358 .with_kd(8.0)
1359 .with_output_limits(0.0, 100.0)
1360 .with_setpoint(10.0)
1361 .with_deadband(0.0);
1362
1363 let mut controller = PidController::new(config);
1364
1365 let mut altitude = 0.0;
1367 let mut velocity = 0.0;
1368 let dt = 0.05;
1369 let drone_mass = 1.2; let gravity = 9.81; let max_thrust = 30.0; println!("Running drone simulation with gravity");
1375 for i in 0..200 {
1376 let control_signal_percent = controller.compute(altitude, dt);
1378
1379 let thrust = control_signal_percent * max_thrust / 100.0;
1381
1382 let net_force = thrust - (drone_mass * gravity);
1384
1385 let acceleration = net_force / drone_mass;
1387
1388 velocity += acceleration * dt;
1390 altitude += velocity * dt;
1391
1392 if i % 20 == 0 {
1393 let hover_thrust_pct = gravity * drone_mass * 100.0 / max_thrust;
1394 println!(
1395 "Iteration {}: Alt = {:.4}, Vel = {:.4}, Thrust% = {:.2}, Hover% = {:.2}",
1396 i, altitude, velocity, control_signal_percent, hover_thrust_pct
1397 );
1398 }
1399 }
1400
1401 println!(
1403 "\nFinal altitude: {:.6}, Error: {:.6}",
1404 altitude,
1405 (10.0 - altitude).abs()
1406 );
1407 println!(
1408 "Exact hover thrust percentage: {:.6}%",
1409 gravity * drone_mass * 100.0 / max_thrust
1410 );
1411 }
1412
1413 #[test]
1414 fn test_close_to_setpoint_behavior() {
1415 println!("\n--- Testing Behavior Near Setpoint ---");
1416
1417 let config = ControllerConfig::new()
1419 .with_kp(10.0)
1420 .with_ki(2.0)
1421 .with_kd(0.0) .with_output_limits(0.0, 100.0)
1423 .with_setpoint(10.0)
1424 .with_deadband(0.0);
1425
1426 let mut controller = PidController::new(config);
1427
1428 let mut altitude = 9.95; let mut velocity = 0.0;
1431 let dt = 0.05;
1432 let drone_mass = 1.2; let gravity = 9.81; let max_thrust = 30.0; let hover_thrust_pct = gravity * drone_mass * 100.0 / max_thrust;
1438
1439 println!("Starting conditions:");
1440 println!("Altitude: 9.95 meters (error = 0.05)");
1441 println!("Velocity: 0.0 m/s");
1442 println!("Exact hover thrust: {:.4}%", hover_thrust_pct);
1443
1444 println!("\nStep | Altitude | Error | Control | P Term | I Term | Net Force | Accel");
1445 println!("-----|----------|-------|---------|--------|--------|-----------|------");
1446
1447 for i in 0..50 {
1450 let error = 10.0 - altitude;
1452
1453 controller.integral = 0.0; let p_only = controller.compute(altitude, dt);
1456
1457 controller.integral = error * i as f64 * dt; let control_signal = controller.compute(altitude, dt);
1460 let i_term = control_signal - p_only;
1461
1462 let thrust = control_signal * max_thrust / 100.0;
1464
1465 let weight_force = drone_mass * gravity;
1467 let net_force = thrust - weight_force; let acceleration = net_force / drone_mass;
1469
1470 if i % 5 == 0 || i < 5 {
1472 println!(
1473 "{:4} | {:8.5} | {:5.5} | {:7.4} | {:6.4} | {:6.4} | {:9.5} | {:6.4}",
1474 i, altitude, error, control_signal, p_only, i_term, net_force, acceleration
1475 );
1476 }
1477
1478 velocity += acceleration * dt;
1480 altitude += velocity * dt;
1481 }
1482
1483 println!("\nRunning full simulation with integral term accumulation:");
1485
1486 let config2 = ControllerConfig::new()
1488 .with_kp(10.0)
1489 .with_ki(2.0)
1490 .with_kd(0.0)
1491 .with_output_limits(0.0, 100.0)
1492 .with_setpoint(10.0)
1493 .with_deadband(0.0);
1494
1495 controller = PidController::new(config2);
1496 altitude = 9.95;
1497 velocity = 0.0;
1498
1499 println!("\nStep | Altitude | Error | Control | Integral | P Term | I Term");
1500 println!("-----|----------|-------|---------|----------|--------|-------");
1501
1502 for i in 0..100 {
1503 let error_before = 10.0 - altitude;
1505 let control_signal = controller.compute(altitude, dt);
1506
1507 let thrust = control_signal * max_thrust / 100.0;
1509 let weight_force = drone_mass * gravity;
1510 let net_force = thrust - weight_force;
1511 let acceleration = net_force / drone_mass;
1512
1513 if i % 10 == 0 || !(5..=95).contains(&i) {
1515 let p_term = 10.0 * error_before; let i_term = 2.0 * controller.integral; println!(
1519 "{:4} | {:8.5} | {:5.5} | {:7.4} | {:8.5} | {:6.4} | {:6.4}",
1520 i, altitude, error_before, control_signal, controller.integral, p_term, i_term
1521 );
1522 }
1523
1524 velocity += acceleration * dt;
1526 altitude += velocity * dt;
1527 }
1528
1529 println!("\nFinal altitude: {:.6}", altitude);
1530 println!("Difference from setpoint: {:.6}", (10.0 - altitude).abs());
1531
1532 println!("\nTesting different integral gains (Ki):");
1534
1535 for ki in [0.5, 1.0, 2.0, 4.0, 8.0].iter() {
1536 let config = ControllerConfig::new()
1537 .with_kp(10.0)
1538 .with_ki(*ki)
1539 .with_kd(0.0)
1540 .with_output_limits(0.0, 100.0)
1541 .with_setpoint(10.0)
1542 .with_deadband(0.0);
1543
1544 let mut controller = PidController::new(config);
1545 let mut altitude = 9.95;
1546 let mut velocity = 0.0;
1547
1548 for _ in 0..200 {
1550 let control_signal = controller.compute(altitude, dt);
1551 let thrust = control_signal * max_thrust / 100.0;
1552 let weight_force = drone_mass * gravity;
1553 let net_force = thrust - weight_force;
1554 let acceleration = net_force / drone_mass;
1555
1556 velocity += acceleration * dt;
1557 altitude += velocity * dt;
1558 }
1559
1560 println!(
1561 "Ki = {:.1}: Final altitude = {:.6}, Error = {:.6}",
1562 ki,
1563 altitude,
1564 (10.0 - altitude).abs()
1565 );
1566 }
1567 }
1568
1569 #[test]
1570 fn test_drone_simulation_converging_point() {
1571 println!("\n--- Testing Drone Simulation Convergence ---");
1572
1573 let config = ControllerConfig::new()
1575 .with_kp(10.0)
1576 .with_ki(2.0)
1577 .with_kd(8.0)
1578 .with_output_limits(0.0, 100.0)
1579 .with_setpoint(10.0)
1580 .with_deadband(0.0);
1581
1582 let mut controller = PidController::new(config);
1583
1584 let mut altitude = 0.0;
1586 let mut velocity: f64 = 0.0;
1587 let dt = 0.05;
1588 let drone_mass = 1.2;
1589 let gravity = 9.81;
1590 let max_thrust = 30.0;
1591 let motor_response_delay = 0.1;
1592 let drag_coefficient = 0.3;
1593
1594 let mut commanded_thrust = 0.0;
1596 let mut thrust;
1597
1598 println!("Running simulation with full physics model:");
1599
1600 println!("Step | Altitude | Error | Control | Actual Thrust | Net Force | Velocity");
1602 println!("-----|----------|-------|---------|--------------|-----------|----------");
1603
1604 for i in 0..1200 {
1606 if i % 200 == 0 {
1608 println!("--- {:1} seconds of simulation ---", i / 20);
1609 }
1610
1611 let control_signal = controller.compute(altitude, dt);
1613
1614 commanded_thrust =
1616 commanded_thrust + (control_signal - commanded_thrust) * dt / motor_response_delay;
1617
1618 thrust = commanded_thrust * max_thrust / 100.0;
1620
1621 let weight_force = drone_mass * gravity;
1623 let drag_force = drag_coefficient * velocity.abs() * velocity;
1624 let net_force = thrust - weight_force - drag_force;
1625 let acceleration = net_force / drone_mass;
1626
1627 velocity += acceleration * dt;
1629 altitude += velocity * dt;
1630
1631 if altitude < 0.0 {
1633 altitude = 0.0;
1634 velocity = 0.0;
1635 }
1636
1637 if i % 100 == 0 || (altitude > 9.9 && altitude < 10.0 && i % 20 == 0) {
1639 println!(
1640 "{:4} | {:8.5} | {:6.5} | {:7.4} | {:12.6} | {:9.5} | {:8.6}",
1641 i,
1642 altitude,
1643 10.0 - altitude,
1644 control_signal,
1645 thrust,
1646 net_force,
1647 velocity
1648 );
1649 }
1650
1651 if i > 400 && altitude > 9.9 && velocity.abs() < 0.001 {
1653 println!(
1654 "System stabilized at altitude {:.6} meters at step {}",
1655 altitude, i
1656 );
1657 println!("Error from setpoint: {:.6} meters", (10.0 - altitude).abs());
1658 break;
1659 }
1660 }
1661
1662 println!("\nTesting with instant motor response (no delay):");
1664
1665 let config2 = ControllerConfig::new()
1667 .with_kp(10.0)
1668 .with_ki(2.0)
1669 .with_kd(8.0)
1670 .with_output_limits(0.0, 100.0)
1671 .with_setpoint(10.0)
1672 .with_deadband(0.0);
1673
1674 controller = PidController::new(config2);
1675 altitude = 0.0;
1676 velocity = 0.0;
1677
1678 for i in 0..1200 {
1679 let control_signal = controller.compute(altitude, dt);
1680
1681 thrust = control_signal * max_thrust / 100.0;
1683
1684 let weight_force = drone_mass * gravity;
1685 let drag_force = drag_coefficient * velocity.abs() * velocity;
1686 let net_force = thrust - weight_force - drag_force;
1687 let acceleration = net_force / drone_mass;
1688
1689 velocity += acceleration * dt;
1690 altitude += velocity * dt;
1691
1692 if altitude < 0.0 {
1693 altitude = 0.0;
1694 velocity = 0.0;
1695 }
1696
1697 if i % 200 == 0 {
1698 println!(
1699 "Step {:4}: Altitude = {:.6}, Error = {:.6}",
1700 i,
1701 altitude,
1702 (10.0 - altitude).abs()
1703 );
1704 }
1705
1706 if i > 400 && altitude > 9.9 && velocity.abs() < 0.001 {
1707 println!(
1708 "System stabilized at altitude {:.6} meters at step {}",
1709 altitude, i
1710 );
1711 println!("Error from setpoint: {:.6} meters", (10.0 - altitude).abs());
1712 break;
1713 }
1714 }
1715
1716 println!("\nTesting with high integral gain (Ki = 10.0):");
1718
1719 let high_ki_config = ControllerConfig::new()
1720 .with_kp(10.0)
1721 .with_ki(10.0) .with_kd(8.0)
1723 .with_output_limits(0.0, 100.0)
1724 .with_setpoint(10.0)
1725 .with_deadband(0.0);
1726
1727 controller = PidController::new(high_ki_config);
1728 altitude = 0.0;
1729 velocity = 0.0;
1730 commanded_thrust = 0.0;
1731
1732 for i in 0..1200 {
1733 let control_signal = controller.compute(altitude, dt);
1734
1735 commanded_thrust =
1737 commanded_thrust + (control_signal - commanded_thrust) * dt / motor_response_delay;
1738
1739 thrust = commanded_thrust * max_thrust / 100.0;
1740
1741 let weight_force = drone_mass * gravity;
1742 let drag_force = drag_coefficient * velocity.abs() * velocity;
1743 let net_force = thrust - weight_force - drag_force;
1744 let acceleration = net_force / drone_mass;
1745
1746 velocity += acceleration * dt;
1747 altitude += velocity * dt;
1748
1749 if altitude < 0.0 {
1750 altitude = 0.0;
1751 velocity = 0.0;
1752 }
1753
1754 if i % 200 == 0 {
1755 println!(
1756 "Step {:4}: Altitude = {:.6}, Error = {:.6}",
1757 i,
1758 altitude,
1759 (10.0 - altitude).abs()
1760 );
1761 }
1762
1763 if i > 400 && altitude > 9.9 && velocity.abs() < 0.001 {
1764 println!(
1765 "System stabilized at altitude {:.6} meters at step {}",
1766 altitude, i
1767 );
1768 println!("Error from setpoint: {:.6} meters", (10.0 - altitude).abs());
1769 break;
1770 }
1771 }
1772 }
1773}