1use crate::protocol::pid::Pid;
4use crate::vehicle::{ThresholdResult, VehicleSpec};
5
6pub fn evaluate_pid_threshold(
10 spec: Option<&VehicleSpec>,
11 pid: Pid,
12 value: f64,
13) -> Option<ThresholdResult> {
14 let spec = spec?;
15 let thresholds = spec.thresholds.as_ref()?;
16
17 let names = pid_threshold_name(pid)?;
19
20 for named in thresholds.engine.iter().chain(thresholds.transmission.iter()) {
22 if names.iter().any(|n| named.name == *n) {
23 return named.threshold.evaluate(value, pid.name());
24 }
25 }
26 None
27}
28
29pub fn evaluate_enhanced_threshold(
31 spec: Option<&VehicleSpec>,
32 did: u16,
33 value: f64,
34) -> Option<ThresholdResult> {
35 let spec = spec?;
36 let thresholds = spec.thresholds.as_ref()?;
37
38 let did_name = format!("{:#06X}", did);
40
41 for named in thresholds.engine.iter().chain(thresholds.transmission.iter()) {
42 if named.name == did_name {
43 return named.threshold.evaluate(value, &did_name);
44 }
45 }
46 None
47}
48
49fn pid_threshold_name(pid: Pid) -> Option<&'static [&'static str]> {
54 match pid.0 {
55 0x05 => Some(&["coolant_temp", "coolant_temp_c"]),
56 0x0C => Some(&["rpm"]),
57 0x5C => Some(&["oil_temp", "oil_temp_c"]),
58 0x0B => Some(&["intake_map_kpa"]),
59 0x10 => Some(&["maf_gs"]),
60 0x42 => Some(&["battery_voltage"]),
61 0x2F => Some(&["fuel_tank_pct"]),
62 0x5E => Some(&["fuel_rate_lh"]),
63 0x46 => Some(&["ambient_temp_c", "ambient_temp"]),
64 0x0F => Some(&["intake_air_temp_c", "intake_air_temp"]),
65 0x33 => Some(&["barometric_kpa"]),
66 _ => None,
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::vehicle::{
74 CommunicationSpec, EngineSpec, NamedThreshold, SpecIdentity, Threshold, ThresholdSet,
75 VehicleSpec,
76 };
77
78 fn make_spec_with_thresholds() -> VehicleSpec {
79 VehicleSpec {
80 spec_version: Some("1.0".into()),
81 identity: SpecIdentity {
82 name: "Test".into(),
83 model_years: (2020, 2020),
84 makes: vec!["Test".into()],
85 models: vec!["Test".into()],
86 engine: EngineSpec {
87 code: "TEST".into(),
88 displacement_l: 2.0,
89 cylinders: 4,
90 layout: "I4".into(),
91 aspiration: "NA".into(),
92 fuel_type: "Gasoline".into(),
93 fuel_system: None,
94 compression_ratio: None,
95 max_power_kw: None,
96 max_torque_nm: None,
97 redline_rpm: 6500,
98 idle_rpm_warm: 700,
99 idle_rpm_cold: 900,
100 firing_order: None,
101 ecm_hardware: None,
102 },
103 transmission: None,
104 vin_match: None,
105 },
106 communication: CommunicationSpec {
107 buses: vec![],
108 elm327_protocol_code: None,
109 },
110 thresholds: Some(ThresholdSet {
111 engine: vec![
112 NamedThreshold {
113 name: "coolant_temp_c".into(),
114 threshold: Threshold {
115 min: Some(0.0),
116 max: Some(130.0),
117 warning_low: None,
118 warning_high: Some(105.0),
119 critical_low: None,
120 critical_high: Some(115.0),
121 unit: "\u{00B0}C".into(),
122 },
123 },
124 NamedThreshold {
125 name: "rpm".into(),
126 threshold: Threshold {
127 min: Some(0.0),
128 max: Some(7000.0),
129 warning_low: None,
130 warning_high: Some(6000.0),
131 critical_low: None,
132 critical_high: Some(6500.0),
133 unit: "RPM".into(),
134 },
135 },
136 ],
137 transmission: vec![],
138 }),
139 dtc_library: None,
140 polling_groups: vec![],
141 diagnostic_rules: vec![],
142 known_issues: vec![],
143 enhanced_pids: vec![],
144 }
145 }
146
147 #[test]
148 fn test_evaluate_normal() {
149 let spec = make_spec_with_thresholds();
150 let result = evaluate_pid_threshold(Some(&spec), Pid::COOLANT_TEMP, 90.0);
151 assert!(result.is_none()); }
153
154 #[test]
155 fn test_evaluate_warning() {
156 let spec = make_spec_with_thresholds();
157 let result = evaluate_pid_threshold(Some(&spec), Pid::COOLANT_TEMP, 110.0);
158 assert!(result.is_some());
159 assert_eq!(result.unwrap().level, crate::vehicle::AlertLevel::Warning);
160 }
161
162 #[test]
163 fn test_evaluate_critical() {
164 let spec = make_spec_with_thresholds();
165 let result = evaluate_pid_threshold(Some(&spec), Pid::COOLANT_TEMP, 118.0);
166 assert!(result.is_some());
167 assert_eq!(result.unwrap().level, crate::vehicle::AlertLevel::Critical);
168 }
169
170 #[test]
171 fn test_evaluate_no_spec() {
172 let result = evaluate_pid_threshold(None, Pid::COOLANT_TEMP, 110.0);
173 assert!(result.is_none());
174 }
175
176 #[test]
177 fn test_evaluate_no_threshold_for_pid() {
178 let spec = make_spec_with_thresholds();
179 let result = evaluate_pid_threshold(Some(&spec), Pid::VEHICLE_SPEED, 200.0);
180 assert!(result.is_none()); }
182
183 #[test]
184 fn test_evaluate_rpm_warning() {
185 let spec = make_spec_with_thresholds();
186 let result = evaluate_pid_threshold(Some(&spec), Pid::ENGINE_RPM, 6200.0);
187 assert!(result.is_some());
188 assert_eq!(result.unwrap().level, crate::vehicle::AlertLevel::Warning);
189 }
190}