bacnet_objects/life_safety/
reset.rs1use std::panic::{catch_unwind, AssertUnwindSafe};
4use std::sync::Arc;
5
6use bacnet_types::enums::{
7 ErrorClass, ErrorCode, LifeSafetyOperation, LifeSafetyState, SilencedState,
8};
9use bacnet_types::error::Error;
10use bacnet_types::primitives::ObjectIdentifier;
11
12use crate::traits::LifeSafetyOperationEffect;
13
14use super::{life_safety_error, LifeSafetyPointObject, LifeSafetyZoneObject};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct LifeSafetyPointResetContext {
22 pub object_identifier: ObjectIdentifier,
24 pub operation: LifeSafetyOperation,
26 pub present_value: LifeSafetyState,
28 pub tracking_value: LifeSafetyState,
30 pub silenced: SilencedState,
32 pub operation_expected: LifeSafetyOperation,
34}
35
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
41pub struct LifeSafetyPointResetCommit {
42 pub present_value: Option<LifeSafetyState>,
44 pub tracking_value: Option<LifeSafetyState>,
46 pub silenced: Option<SilencedState>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct LifeSafetyZoneResetContext {
57 pub object_identifier: ObjectIdentifier,
59 pub operation: LifeSafetyOperation,
61 pub present_value: LifeSafetyState,
63 pub silenced: SilencedState,
65 pub operation_expected: LifeSafetyOperation,
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
74pub struct LifeSafetyZoneResetCommit {
75 pub present_value: Option<LifeSafetyState>,
77 pub silenced: Option<SilencedState>,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum LifeSafetyResetError {
86 UnsupportedVariant,
88 InvalidOperationInThisState,
90 ServiceRequestDenied,
92}
93
94pub type LifeSafetyPointResetExecutor = Arc<
102 dyn Fn(&LifeSafetyPointResetContext) -> Result<LifeSafetyPointResetCommit, LifeSafetyResetError>
103 + Send
104 + Sync,
105>;
106
107pub type LifeSafetyZoneResetExecutor = Arc<
115 dyn Fn(&LifeSafetyZoneResetContext) -> Result<LifeSafetyZoneResetCommit, LifeSafetyResetError>
116 + Send
117 + Sync,
118>;
119
120pub(super) fn is_reset_operation(operation: LifeSafetyOperation) -> bool {
121 matches!(
122 operation,
123 LifeSafetyOperation::RESET
124 | LifeSafetyOperation::RESET_ALARM
125 | LifeSafetyOperation::RESET_FAULT
126 )
127}
128
129fn reset_error(error: LifeSafetyResetError) -> Error {
130 match error {
131 LifeSafetyResetError::UnsupportedVariant => {
132 life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE)
133 }
134 LifeSafetyResetError::InvalidOperationInThisState => {
135 life_safety_error(ErrorCode::INVALID_OPERATION_IN_THIS_STATE)
136 }
137 LifeSafetyResetError::ServiceRequestDenied => Error::Protocol {
138 class: ErrorClass::SERVICES.to_raw() as u32,
139 code: ErrorCode::SERVICE_REQUEST_DENIED.to_raw() as u32,
140 },
141 }
142}
143
144fn optional_functionality_error() -> Error {
145 life_safety_error(ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED)
146}
147
148fn invalid_operation_error() -> Error {
149 life_safety_error(ErrorCode::INVALID_OPERATION_IN_THIS_STATE)
150}
151
152fn commit_value_error() -> Error {
153 life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE)
154}
155
156fn valid_life_safety_state(state: LifeSafetyState) -> bool {
157 LifeSafetyState::ALL_NAMED
158 .iter()
159 .any(|&(_, named)| named == state)
160 || (256..=65_535).contains(&state.to_raw())
161}
162
163fn valid_silenced_state(state: SilencedState) -> bool {
164 SilencedState::ALL_NAMED
165 .iter()
166 .any(|&(_, named)| named == state)
167}
168
169impl LifeSafetyPointObject {
170 pub(super) fn apply_reset_operation(
171 &mut self,
172 operation: LifeSafetyOperation,
173 ) -> Result<LifeSafetyOperationEffect, Error> {
174 if !is_reset_operation(operation) || self.operation_expected != operation.to_raw() {
175 return Err(invalid_operation_error());
176 }
177 let executor = self
178 .reset_executor
179 .clone()
180 .ok_or_else(optional_functionality_error)?;
181 let context = LifeSafetyPointResetContext {
182 object_identifier: self.oid,
183 operation,
184 present_value: LifeSafetyState::from_raw(self.present_value),
185 tracking_value: LifeSafetyState::from_raw(self.tracking_value),
186 silenced: SilencedState::from_raw(self.silenced),
187 operation_expected: LifeSafetyOperation::from_raw(self.operation_expected),
188 };
189 let result = match catch_unwind(AssertUnwindSafe(|| executor(&context))) {
190 Ok(result) => result,
191 Err(_) => return Err(reset_error(LifeSafetyResetError::ServiceRequestDenied)),
192 };
193 let commit = result.map_err(reset_error)?;
194
195 if commit
196 .present_value
197 .is_some_and(|value| !valid_life_safety_state(value))
198 || commit
199 .tracking_value
200 .is_some_and(|value| !valid_life_safety_state(value))
201 || commit
202 .silenced
203 .is_some_and(|value| !valid_silenced_state(value))
204 {
205 return Err(commit_value_error());
206 }
207
208 if let Some(value) = commit.present_value {
209 self.present_value = value.to_raw();
210 }
211 if let Some(value) = commit.tracking_value {
212 self.tracking_value = value.to_raw();
213 }
214 if let Some(value) = commit.silenced {
215 self.silenced = value.to_raw();
216 }
217 self.operation_expected = LifeSafetyOperation::NONE.to_raw();
218 Ok(LifeSafetyOperationEffect::Applied)
219 }
220}
221
222impl LifeSafetyZoneObject {
223 pub(super) fn apply_reset_operation(
224 &mut self,
225 operation: LifeSafetyOperation,
226 ) -> Result<LifeSafetyOperationEffect, Error> {
227 if !is_reset_operation(operation) || self.operation_expected != operation.to_raw() {
228 return Err(invalid_operation_error());
229 }
230 let executor = self
231 .reset_executor
232 .clone()
233 .ok_or_else(optional_functionality_error)?;
234 let context = LifeSafetyZoneResetContext {
235 object_identifier: self.oid,
236 operation,
237 present_value: LifeSafetyState::from_raw(self.present_value),
238 silenced: SilencedState::from_raw(self.silenced),
239 operation_expected: LifeSafetyOperation::from_raw(self.operation_expected),
240 };
241 let result = match catch_unwind(AssertUnwindSafe(|| executor(&context))) {
242 Ok(result) => result,
243 Err(_) => return Err(reset_error(LifeSafetyResetError::ServiceRequestDenied)),
244 };
245 let commit = result.map_err(reset_error)?;
246
247 if commit
248 .present_value
249 .is_some_and(|value| !valid_life_safety_state(value))
250 || commit
251 .silenced
252 .is_some_and(|value| !valid_silenced_state(value))
253 {
254 return Err(commit_value_error());
255 }
256
257 if let Some(value) = commit.present_value {
258 self.present_value = value.to_raw();
259 }
260 if let Some(value) = commit.silenced {
261 self.silenced = value.to_raw();
262 }
263 self.operation_expected = LifeSafetyOperation::NONE.to_raw();
264 Ok(LifeSafetyOperationEffect::Applied)
265 }
266}