1#![allow(rustdoc::broken_intra_doc_links)]
2
3use std::collections::HashMap;
4
5use bhtune_core::{ControllerDirection, DcsTemplate, TagOrValue};
6use bhtune_db::models::SampleQuality;
7use bhtune_driver::{Driver, TagValue, TagWrite};
8
9pub(super) fn check_quality(
15 tag: &str,
16 quality: bhtune_driver::Quality,
17 allow_uncertain: bool,
18) -> anyhow::Result<()> {
19 match quality {
20 bhtune_driver::Quality::Good => Ok(()),
21 bhtune_driver::Quality::Uncertain if allow_uncertain => {
22 tracing::warn!(
23 tag,
24 "accepting Uncertain-quality reading because Config > OPC quality policy \
25 (allow_uncertain_quality) permits it"
26 );
27 Ok(())
28 }
29 bhtune_driver::Quality::Uncertain => {
30 anyhow::bail!(
31 "tag '{tag}' reported OPC quality Uncertain; refusing to trust it for a \
32 tuning-critical reading (set Config > OPC quality policy \
33 `allow_uncertain_quality = true` to accept Uncertain readings; Bad is never \
34 accepted)"
35 )
36 }
37 bhtune_driver::Quality::Bad => {
38 anyhow::bail!(
39 "tag '{tag}' reported OPC quality Bad; refusing to trust it for a \
40 tuning-critical reading"
41 )
42 }
43 }
44}
45pub fn sample_quality_from_driver(quality: bhtune_driver::Quality) -> SampleQuality {
54 match quality {
55 bhtune_driver::Quality::Good => SampleQuality::Good,
56 bhtune_driver::Quality::Uncertain => SampleQuality::Uncertain,
57 bhtune_driver::Quality::Bad => SampleQuality::Bad,
58 }
59}
60pub(super) async fn read_raw(
61 driver: &dyn Driver,
62 tag: &str,
63 allow_uncertain: bool,
64) -> anyhow::Result<String> {
65 let values = driver.read(&[tag.to_string()]).await?;
66 let value = values
67 .into_iter()
68 .next()
69 .ok_or_else(|| anyhow::anyhow!("driver returned no value for tag '{tag}'"))?;
70 check_quality(tag, value.quality, allow_uncertain)?;
71 Ok(value.value)
72}
73pub(super) async fn read_f32(
74 driver: &dyn Driver,
75 tag: &str,
76 allow_uncertain: bool,
77) -> anyhow::Result<f32> {
78 let raw = read_raw(driver, tag, allow_uncertain).await?;
79 parse_f32_value(tag, &raw)
80}
81pub(super) async fn resolve_f32(
82 driver: &dyn Driver,
83 tag_or_value: &TagOrValue<f32>,
84 allow_uncertain: bool,
85) -> anyhow::Result<f32> {
86 match tag_or_value {
87 TagOrValue::Value(v) => {
88 if !v.is_finite() {
89 anyhow::bail!("value {v} is not a finite number");
90 }
91 Ok(*v)
92 }
93 TagOrValue::Tag(tag) => read_f32(driver, tag, allow_uncertain).await,
94 }
95}
96pub(super) async fn resolve_direction(
97 driver: &dyn Driver,
98 tag_or_value: &TagOrValue<ControllerDirection>,
99 template: &DcsTemplate,
100 allow_uncertain: bool,
101) -> anyhow::Result<ControllerDirection> {
102 match tag_or_value {
103 TagOrValue::Value(d) => Ok(*d),
104 TagOrValue::Tag(tag) => {
105 let raw = read_raw(driver, tag, allow_uncertain).await?;
106 Ok(ControllerDirection::from_raw_tag_value(
107 &raw,
108 &template.controller_action_direct_value,
109 ))
110 }
111 }
112}
113pub(super) fn parse_f32_value(tag: &str, raw: &str) -> anyhow::Result<f32> {
114 let value: f32 = raw
115 .trim()
116 .parse::<f32>()
117 .map_err(|_| anyhow::anyhow!("tag '{tag}' value '{raw}' is not a number"))?;
118 if !value.is_finite() {
119 anyhow::bail!("tag '{tag}' value '{raw}' is not a finite number");
120 }
121 Ok(value)
122}
123pub(super) fn read_batch_raw(
124 values: &HashMap<String, TagValue>,
125 tag: &str,
126 allow_uncertain: bool,
127) -> anyhow::Result<String> {
128 let value = values
129 .get(tag)
130 .ok_or_else(|| anyhow::anyhow!("driver returned no value for tag '{tag}'"))?;
131 check_quality(tag, value.quality, allow_uncertain)?;
132 Ok(value.value.clone())
133}
134pub(super) fn read_batch_f32(
135 values: &HashMap<String, TagValue>,
136 tag: &str,
137 allow_uncertain: bool,
138) -> anyhow::Result<f32> {
139 let raw = read_batch_raw(values, tag, allow_uncertain)?;
140 parse_f32_value(tag, &raw)
141}
142pub(super) async fn resolve_f32_from_batch(
143 driver: &dyn Driver,
144 values: &HashMap<String, TagValue>,
145 tag_or_value: &TagOrValue<f32>,
146 allow_uncertain: bool,
147) -> anyhow::Result<f32> {
148 match tag_or_value {
149 TagOrValue::Value(_) => resolve_f32(driver, tag_or_value, allow_uncertain).await,
150 TagOrValue::Tag(tag) => read_batch_f32(values, tag, allow_uncertain),
151 }
152}
153pub(super) async fn resolve_direction_from_batch(
154 driver: &dyn Driver,
155 values: &HashMap<String, TagValue>,
156 tag_or_value: &TagOrValue<ControllerDirection>,
157 template: &DcsTemplate,
158 allow_uncertain: bool,
159) -> anyhow::Result<ControllerDirection> {
160 match tag_or_value {
161 TagOrValue::Value(_) => {
162 resolve_direction(driver, tag_or_value, template, allow_uncertain).await
163 }
164 TagOrValue::Tag(tag) => {
165 let raw = read_batch_raw(values, tag, allow_uncertain)?;
166 Ok(ControllerDirection::from_raw_tag_value(
167 &raw,
168 &template.controller_action_direct_value,
169 ))
170 }
171 }
172}
173#[cfg(test)]
179pub(super) async fn read_pv_sample(
180 driver: &dyn Driver,
181 tag: &str,
182) -> anyhow::Result<(f32, bhtune_driver::Quality)> {
183 read_numeric_sample(driver, tag).await
184}
185pub(super) async fn read_numeric_sample(
186 driver: &dyn Driver,
187 tag: &str,
188) -> anyhow::Result<(f32, bhtune_driver::Quality)> {
189 let values = driver.read(&[tag.to_string()]).await?;
190 let value = values
191 .into_iter()
192 .next()
193 .ok_or_else(|| anyhow::anyhow!("driver returned no value for tag '{tag}'"))?;
194 let numeric: f32 = value
195 .value
196 .trim()
197 .parse::<f32>()
198 .map_err(|_| anyhow::anyhow!("tag '{tag}' value '{}' is not a number", value.value))?;
199 if !numeric.is_finite() {
200 anyhow::bail!("tag '{tag}' value '{}' is not a finite number", value.value);
201 }
202 Ok((numeric, value.quality))
203}
204pub(super) async fn read_poll_batch(
205 driver: &dyn Driver,
206 pv_tag: &str,
207 mv_tag: Option<&str>,
208) -> anyhow::Result<HashMap<String, TagValue>> {
209 let mut requested_tags = vec![pv_tag.to_string()];
210 if let Some(mv_tag) = mv_tag
211 && mv_tag != pv_tag
212 {
213 requested_tags.push(mv_tag.to_string());
214 }
215
216 Ok(driver
217 .read(&requested_tags)
218 .await?
219 .into_iter()
220 .map(|value| (value.tag.clone(), value))
221 .collect())
222}
223pub(super) fn read_numeric_from_batch(
224 values: &HashMap<String, TagValue>,
225 tag: &str,
226) -> anyhow::Result<(f32, bhtune_driver::Quality)> {
227 let value = values
228 .get(tag)
229 .ok_or_else(|| anyhow::anyhow!("driver returned no value for tag '{tag}'"))?;
230 let numeric = parse_f32_value(tag, &value.value)?;
231 Ok((numeric, value.quality))
232}
233pub(super) async fn write_raw(driver: &dyn Driver, tag: &str, value: String) -> anyhow::Result<()> {
234 let outcome = driver.write(&tag.to_string(), TagWrite::Raw(value)).await?;
235 if outcome.success {
236 Ok(())
237 } else {
238 anyhow::bail!(
239 "write to '{tag}' was rejected: {}",
240 outcome
241 .error_message
242 .unwrap_or_else(|| "unknown reason".to_string())
243 )
244 }
245}
246pub(super) async fn write_value(driver: &dyn Driver, tag: &str, value: f32) -> anyhow::Result<()> {
247 let outcome = driver
248 .write(&tag.to_string(), TagWrite::Float(value))
249 .await?;
250 if outcome.success {
251 Ok(())
252 } else {
253 anyhow::bail!(
254 "write to '{tag}' was rejected: {}",
255 outcome
256 .error_message
257 .unwrap_or_else(|| "unknown reason".to_string())
258 )
259 }
260}