bhtune_runtime/tune/
config.rs1#![allow(rustdoc::broken_intra_doc_links)]
2
3use bhtune_core::{ControllerType, DcsTemplate, LoopConfig, LoopTags, ProcessType, TagOrValue};
4use bhtune_db::models::EffectiveTuning;
5
6use crate::driver::{SIMULATOR_MV_TAG, SIMULATOR_PV_TAG};
7use crate::tune::{DriverKind, TuneRequest};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub(super) struct EffectiveTiming {
13 pub(super) mrft_delay_secs: u32,
14 pub(super) poll_interval_ms: u64,
15 pub(super) timeout_secs: u64,
16 pub(super) op_timeout_secs: u64,
17 pub(super) restore_timeout_secs: u64,
18}
19impl From<crate::config::EffectiveTuningConfig> for EffectiveTiming {
20 fn from(value: crate::config::EffectiveTuningConfig) -> Self {
21 Self {
22 mrft_delay_secs: value.mrft_delay_secs,
23 poll_interval_ms: value.poll_interval_ms,
24 timeout_secs: value.timeout_secs,
25 op_timeout_secs: value.op_timeout_secs,
26 restore_timeout_secs: value.restore_timeout_secs,
27 }
28 }
29}
30impl From<EffectiveTiming> for EffectiveTuning {
31 fn from(value: EffectiveTiming) -> Self {
32 Self {
33 mrft_delay_secs: value.mrft_delay_secs,
34 poll_interval_ms: value.poll_interval_ms,
35 timeout_secs: value.timeout_secs,
36 op_timeout_secs: value.op_timeout_secs,
37 restore_timeout_secs: value.restore_timeout_secs,
38 }
39 }
40}
41#[cfg(test)]
42pub(super) fn test_effective_timing(args: &TuneRequest) -> EffectiveTiming {
43 EffectiveTiming {
44 mrft_delay_secs: args.mrft_delay,
45 poll_interval_ms: args.poll_interval_ms,
46 timeout_secs: args.timeout_secs,
47 op_timeout_secs: args.op_timeout_secs,
48 restore_timeout_secs: args.restore_timeout_secs,
49 }
50}
51pub fn validate_restore_timeout_secs(
57 driver: DriverKind,
58 restore_timeout_secs: u64,
59) -> anyhow::Result<()> {
60 if restore_timeout_secs == 0 {
61 anyhow::bail!("[tuning].restore_timeout_secs must be greater than zero");
62 }
63 if driver == DriverKind::Opcda
64 && restore_timeout_secs < crate::config::MIN_OPC_RESTORE_TIMEOUT_SECS
65 {
66 anyhow::bail!(
67 "[tuning].restore_timeout_secs must be at least {} seconds for OPC DA MV confirmation",
68 crate::config::MIN_OPC_RESTORE_TIMEOUT_SECS
69 );
70 }
71 Ok(())
72}
73pub(super) fn build_loop_config_with_timing(
74 args: &TuneRequest,
75 timing: EffectiveTiming,
76) -> anyhow::Result<LoopConfig> {
77 let process_type: ProcessType = args.process_type;
78 let controller_type: ControllerType = args.controller_type;
79
80 if !controller_type.is_allowed_for(process_type) {
81 anyhow::bail!(
82 "{controller_type:?} controller is not valid for {process_type:?} (PID is only offered for the two Temperature process types)"
83 );
84 }
85
86 let config = LoopConfig {
87 process_type,
88 controller_type,
89 relay_amp_percent: args.relay_amp,
90 num_cycles_skip: args
91 .cycles_skip
92 .unwrap_or_else(|| process_type.default_cycles_skip()),
93 num_cycles_count: args
94 .cycles_count
95 .unwrap_or_else(|| process_type.default_cycles_test()),
96 noise_protection_secs: args
97 .noise_protection_secs
98 .unwrap_or_else(|| process_type.default_noise_protection_secs()),
99 mrft_delay_secs: timing.mrft_delay_secs,
100 };
101 config.validate()?;
106 Ok(config)
107}
108#[cfg(test)]
109pub(super) fn build_loop_config(args: &TuneRequest) -> anyhow::Result<LoopConfig> {
110 build_loop_config_with_timing(args, test_effective_timing(args))
111}
112pub(super) fn build_loop_tags(
119 args: &TuneRequest,
120 template: &DcsTemplate,
121) -> anyhow::Result<LoopTags> {
122 match args.driver {
123 DriverKind::Opcda => {
124 let mut tags = LoopTags::derive_from_pv_tag(&args.tagname, template);
125 if let Some(overrides) = &args.tag_overrides {
126 overrides.apply_to(&mut tags);
127 }
128 if let Some(v) = args.pv_range_high {
131 tags.upper_pv_range = TagOrValue::Value(v);
132 }
133 if let Some(v) = args.pv_range_low {
134 tags.lower_pv_range = TagOrValue::Value(v);
135 }
136 if let Some(v) = args.mv_range_high {
137 tags.upper_mv_range = TagOrValue::Value(v);
138 }
139 if let Some(v) = args.mv_range_low {
140 tags.lower_mv_range = TagOrValue::Value(v);
141 }
142 if let Some(d) = args.direction {
143 tags.controller_direction = TagOrValue::Value(d);
144 }
145 Ok(tags)
146 }
147 DriverKind::Simulator => {
148 let pv_range_high = args.pv_range_high.ok_or_else(|| {
149 anyhow::anyhow!(
150 "--pv-range-high is required with --driver simulator (or use `bhtune simulate`)"
151 )
152 })?;
153 let pv_range_low = args.pv_range_low.ok_or_else(|| {
154 anyhow::anyhow!(
155 "--pv-range-low is required with --driver simulator (or use `bhtune simulate`)"
156 )
157 })?;
158 let mv_range_high = args.mv_range_high.ok_or_else(|| {
159 anyhow::anyhow!(
160 "--mv-range-high is required with --driver simulator (or use `bhtune simulate`)"
161 )
162 })?;
163 let mv_range_low = args.mv_range_low.ok_or_else(|| {
164 anyhow::anyhow!(
165 "--mv-range-low is required with --driver simulator (or use `bhtune simulate`)"
166 )
167 })?;
168 let direction = args.direction.ok_or_else(|| {
169 anyhow::anyhow!(
170 "--direction is required with --driver simulator (or use `bhtune simulate`)"
171 )
172 })?;
173
174 Ok(LoopTags {
175 process_variable: SIMULATOR_PV_TAG.to_string(),
176 manipulated_variable: SIMULATOR_MV_TAG.to_string(),
177 setpoint_variable: None,
178 controller_mode: None,
179 mode_attribute: None,
180 upper_pv_range: TagOrValue::Value(pv_range_high),
181 lower_pv_range: TagOrValue::Value(pv_range_low),
182 upper_mv_range: TagOrValue::Value(mv_range_high),
183 lower_mv_range: TagOrValue::Value(mv_range_low),
184 controller_direction: TagOrValue::Value(direction),
185 proportional_constant: None,
186 integral_constant: None,
187 derivative_constant: None,
188 })
189 }
190 }
191}