Skip to main content

bhtune_core/
pid_config.rs

1//! Enums for how a DCS/PLC expresses PID parameters, avoiding the fragile pattern of
2//! comparing live values against magic display strings (`"Kp - Proportional Gain"`, etc.).
3
4use serde::{Deserialize, Serialize};
5
6/// How a DCS expresses the proportional term.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
9#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
10#[serde(rename_all = "snake_case")]
11pub enum ProportionalType {
12    /// Kp: dimensionless gain.
13    Gain,
14    /// PB: proportional band, as a percentage. `PB = 100 / Kp`.
15    Band,
16}
17
18/// How a DCS expresses the integral term.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
21#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
22#[serde(rename_all = "snake_case")]
23pub enum IntegralType {
24    /// Ti: reset time.
25    ResetTime,
26    /// Ri: reset rate, `Ri = 1 / Ti`.
27    ResetRate,
28    /// Ki: reset gain, `Ki = Kp / Ti`.
29    ResetGain,
30}
31
32/// How a DCS expresses the derivative term.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
35#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
36#[serde(rename_all = "snake_case")]
37pub enum DerivativeType {
38    /// Td: derivative time.
39    DerivativeTime,
40    /// Kd: derivative gain, `Kd = Kp * Td`.
41    DerivativeGain,
42}
43
44/// The time unit a DCS expects for integral/derivative parameters.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
46#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
47#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
48#[serde(rename_all = "snake_case")]
49pub enum TimeUnit {
50    Seconds,
51    Minutes,
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn proportional_type_serde_round_trip() {
60        for pt in [ProportionalType::Gain, ProportionalType::Band] {
61            let json = serde_json::to_string(&pt).unwrap();
62            let back: ProportionalType = serde_json::from_str(&json).unwrap();
63            assert_eq!(pt, back);
64        }
65    }
66
67    #[test]
68    fn integral_type_serde_round_trip() {
69        for it in [
70            IntegralType::ResetTime,
71            IntegralType::ResetRate,
72            IntegralType::ResetGain,
73        ] {
74            let json = serde_json::to_string(&it).unwrap();
75            let back: IntegralType = serde_json::from_str(&json).unwrap();
76            assert_eq!(it, back);
77        }
78    }
79
80    #[test]
81    fn derivative_type_serde_round_trip() {
82        for dt in [
83            DerivativeType::DerivativeTime,
84            DerivativeType::DerivativeGain,
85        ] {
86            let json = serde_json::to_string(&dt).unwrap();
87            let back: DerivativeType = serde_json::from_str(&json).unwrap();
88            assert_eq!(dt, back);
89        }
90    }
91
92    #[test]
93    fn time_unit_serde_round_trip() {
94        for tu in [TimeUnit::Seconds, TimeUnit::Minutes] {
95            let json = serde_json::to_string(&tu).unwrap();
96            let back: TimeUnit = serde_json::from_str(&json).unwrap();
97            assert_eq!(tu, back);
98        }
99    }
100
101    #[test]
102    fn serde_uses_snake_case() {
103        assert_eq!(
104            serde_json::to_string(&ProportionalType::Band).unwrap(),
105            "\"band\""
106        );
107        assert_eq!(
108            serde_json::to_string(&IntegralType::ResetGain).unwrap(),
109            "\"reset_gain\""
110        );
111        assert_eq!(
112            serde_json::to_string(&DerivativeType::DerivativeGain).unwrap(),
113            "\"derivative_gain\""
114        );
115        assert_eq!(
116            serde_json::to_string(&TimeUnit::Minutes).unwrap(),
117            "\"minutes\""
118        );
119    }
120}