Skip to main content

pvxs_sys/
alarms.rs

1// Copyright 2026 Tine Zata
2// SPDX-License-Identifier: MPL-2.0
3use crate::{AlarmMetadata, ControlMetadata};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
6pub enum AlarmSeverity {
7    NoAlarm = 0,
8    Minor = 1,
9    Major = 2,
10    Invalid = 3,
11    UndefinedAlarm = 4,
12}
13impl Default for AlarmSeverity {
14    fn default() -> Self {
15        AlarmSeverity::NoAlarm
16    }
17}
18
19impl From<i32> for AlarmSeverity {
20    fn from(value: i32) -> Self {
21        match value {
22            0 => AlarmSeverity::NoAlarm,
23            1 => AlarmSeverity::Minor,
24            2 => AlarmSeverity::Major,
25            3 => AlarmSeverity::Invalid,
26            4 => AlarmSeverity::UndefinedAlarm,
27            _ => AlarmSeverity::UndefinedAlarm,
28        }
29    }
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
33pub enum AlarmStatus {
34    NoAlarm = 0,
35    DeviceStatus = 1,
36    DriverStatus = 2,
37    RecordStatus = 3,
38    DbStatus = 4,
39    ConfigStatus = 5,
40    UndefinedStatus = 6,
41    ClientStatus = 7,
42}
43
44impl Default for AlarmStatus {
45    fn default() -> Self {
46        AlarmStatus::NoAlarm
47    }
48}
49
50impl From<i32> for AlarmStatus {
51    fn from(value: i32) -> Self {
52        match value {
53            0 => AlarmStatus::NoAlarm,
54            1 => AlarmStatus::DeviceStatus,
55            2 => AlarmStatus::DriverStatus,
56            3 => AlarmStatus::RecordStatus,
57            4 => AlarmStatus::DbStatus,
58            5 => AlarmStatus::ConfigStatus,
59            6 => AlarmStatus::UndefinedStatus,
60            7 => AlarmStatus::ClientStatus,
61            _ => AlarmStatus::UndefinedStatus,
62        }
63    }
64}
65
66#[derive(Clone, Debug, Default)]
67pub struct AlarmConfig {
68    pub(crate) control: Option<ControlMetadata>,
69    pub(crate) alarm_metadata: Option<AlarmMetadata>,
70}
71
72#[derive(Clone, Debug)]
73pub struct AlarmResult {
74    pub(crate) allow: bool,
75    pub(crate) severity: AlarmSeverity,
76    pub(crate) status: AlarmStatus,
77    pub(crate) message: String,
78}
79
80pub fn compute_alarm_for_scalar(value: f64, config: &AlarmConfig) -> AlarmResult {
81    // Control limits: if present, reject updates outside limits
82    if let Some(control) = &config.control {
83        let hysteresis = config
84            .alarm_metadata
85            .as_ref()
86            .map_or(0.0, |m| m.hysteresis as f64);
87        if value < control.limit_low + hysteresis || value > control.limit_high - hysteresis {
88            return AlarmResult {
89                allow: false,
90                severity: AlarmSeverity::Invalid,
91                status: AlarmStatus::RecordStatus,
92                message: "OUT_OF_CONTROL_LIMITS".to_string(),
93            };
94        }
95    }
96
97    if let Some(value_alarm) = &config.alarm_metadata {
98        if value_alarm.active {
99            if value <= value_alarm.low_alarm_limit + value_alarm.hysteresis as f64 {
100                return AlarmResult {
101                    allow: true,
102                    severity: value_alarm.low_alarm_severity,
103                    status: AlarmStatus::DeviceStatus,
104                    message: "LOW_ALARM".to_string(),
105                };
106            }
107            if value <= value_alarm.low_warning_limit + value_alarm.hysteresis as f64 {
108                return AlarmResult {
109                    allow: true,
110                    severity: value_alarm.low_warning_severity,
111                    status: AlarmStatus::DeviceStatus,
112                    message: "LOW_WARNING".to_string(),
113                };
114            }
115            if value >= value_alarm.high_alarm_limit - value_alarm.hysteresis as f64 {
116                return AlarmResult {
117                    allow: true,
118                    severity: value_alarm.high_alarm_severity,
119                    status: AlarmStatus::DeviceStatus,
120                    message: "HIGH_ALARM".to_string(),
121                };
122            }
123            if value >= value_alarm.high_warning_limit - value_alarm.hysteresis as f64 {
124                return AlarmResult {
125                    allow: true,
126                    severity: value_alarm.high_warning_severity,
127                    status: AlarmStatus::DeviceStatus,
128                    message: "HIGH_WARNING".to_string(),
129                };
130            }
131        }
132    }
133
134    AlarmResult {
135        allow: true,
136        severity: AlarmSeverity::NoAlarm,
137        status: AlarmStatus::NoAlarm,
138        message: "OK".to_string(),
139    }
140}