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::{
15 life_safety_error, valid_life_safety_state, LifeSafetyPointObject, LifeSafetyZoneObject,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct LifeSafetyPointResetContext {
24 pub object_identifier: ObjectIdentifier,
26 pub operation: LifeSafetyOperation,
28 pub present_value: LifeSafetyState,
30 pub tracking_value: LifeSafetyState,
32 pub silenced: SilencedState,
34 pub operation_expected: LifeSafetyOperation,
36}
37
38#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
43pub struct LifeSafetyPointResetCommit {
44 pub present_value: Option<LifeSafetyState>,
46 pub tracking_value: Option<LifeSafetyState>,
50 pub silenced: Option<SilencedState>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct LifeSafetyZoneResetContext {
60 pub object_identifier: ObjectIdentifier,
62 pub operation: LifeSafetyOperation,
64 pub present_value: LifeSafetyState,
66 pub tracking_value: LifeSafetyState,
68 pub silenced: SilencedState,
70 pub operation_expected: LifeSafetyOperation,
72}
73
74#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
79pub struct LifeSafetyZoneResetCommit {
80 pub present_value: Option<LifeSafetyState>,
82 pub tracking_value: Option<LifeSafetyState>,
86 pub silenced: Option<SilencedState>,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum LifeSafetyResetError {
95 UnsupportedVariant,
97 InvalidOperationInThisState,
99 ServiceRequestDenied,
101}
102
103pub type LifeSafetyPointResetExecutor = Arc<
111 dyn Fn(&LifeSafetyPointResetContext) -> Result<LifeSafetyPointResetCommit, LifeSafetyResetError>
112 + Send
113 + Sync,
114>;
115
116pub type LifeSafetyZoneResetExecutor = Arc<
124 dyn Fn(&LifeSafetyZoneResetContext) -> Result<LifeSafetyZoneResetCommit, LifeSafetyResetError>
125 + Send
126 + Sync,
127>;
128
129pub(super) fn is_reset_operation(operation: LifeSafetyOperation) -> bool {
130 matches!(
131 operation,
132 LifeSafetyOperation::RESET
133 | LifeSafetyOperation::RESET_ALARM
134 | LifeSafetyOperation::RESET_FAULT
135 )
136}
137
138fn reset_error(error: LifeSafetyResetError) -> Error {
139 match error {
140 LifeSafetyResetError::UnsupportedVariant => {
141 life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE)
142 }
143 LifeSafetyResetError::InvalidOperationInThisState => {
144 life_safety_error(ErrorCode::INVALID_OPERATION_IN_THIS_STATE)
145 }
146 LifeSafetyResetError::ServiceRequestDenied => Error::Protocol {
147 class: ErrorClass::SERVICES.to_raw() as u32,
148 code: ErrorCode::SERVICE_REQUEST_DENIED.to_raw() as u32,
149 },
150 }
151}
152
153fn optional_functionality_error() -> Error {
154 life_safety_error(ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED)
155}
156
157fn invalid_operation_error() -> Error {
158 life_safety_error(ErrorCode::INVALID_OPERATION_IN_THIS_STATE)
159}
160
161fn commit_value_error() -> Error {
162 life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE)
163}
164
165fn valid_silenced_state(state: SilencedState) -> bool {
166 SilencedState::ALL_NAMED
167 .iter()
168 .any(|&(_, named)| named == state)
169}
170
171impl LifeSafetyPointObject {
172 pub(super) fn apply_reset_operation(
173 &mut self,
174 operation: LifeSafetyOperation,
175 ) -> Result<LifeSafetyOperationEffect, Error> {
176 if !is_reset_operation(operation) || self.operation_expected != operation {
177 return Err(invalid_operation_error());
178 }
179 let executor = self
180 .reset_executor
181 .clone()
182 .ok_or_else(optional_functionality_error)?;
183 let context = LifeSafetyPointResetContext {
184 object_identifier: self.oid,
185 operation,
186 present_value: self.present_value,
187 tracking_value: self.tracking_value,
188 silenced: self.silenced,
189 operation_expected: self.operation_expected,
190 };
191 let result = match catch_unwind(AssertUnwindSafe(|| executor(&context))) {
192 Ok(result) => result,
193 Err(_) => return Err(reset_error(LifeSafetyResetError::ServiceRequestDenied)),
194 };
195 let commit = result.map_err(reset_error)?;
196
197 if commit
198 .present_value
199 .is_some_and(|value| !valid_life_safety_state(value))
200 || commit
201 .tracking_value
202 .is_some_and(|value| !valid_life_safety_state(value))
203 || commit
204 .silenced
205 .is_some_and(|value| !valid_silenced_state(value))
206 {
207 return Err(commit_value_error());
208 }
209
210 if let Some(value) = commit.present_value {
211 self.present_value = value;
212 }
213 if let Some(value) = commit.tracking_value {
214 self.simulation().track(value);
215 }
216 if let Some(value) = commit.silenced {
217 self.silenced = value;
218 }
219 self.operation_expected = LifeSafetyOperation::NONE;
220 Ok(LifeSafetyOperationEffect::Applied)
221 }
222}
223
224impl LifeSafetyZoneObject {
225 pub(super) fn apply_reset_operation(
226 &mut self,
227 operation: LifeSafetyOperation,
228 ) -> Result<LifeSafetyOperationEffect, Error> {
229 if !is_reset_operation(operation) || self.operation_expected != operation {
230 return Err(invalid_operation_error());
231 }
232 let executor = self
233 .reset_executor
234 .clone()
235 .ok_or_else(optional_functionality_error)?;
236 let context = LifeSafetyZoneResetContext {
237 object_identifier: self.oid,
238 operation,
239 present_value: self.present_value,
240 tracking_value: self.tracking_value,
241 silenced: self.silenced,
242 operation_expected: self.operation_expected,
243 };
244 let result = match catch_unwind(AssertUnwindSafe(|| executor(&context))) {
245 Ok(result) => result,
246 Err(_) => return Err(reset_error(LifeSafetyResetError::ServiceRequestDenied)),
247 };
248 let commit = result.map_err(reset_error)?;
249
250 if commit
251 .present_value
252 .is_some_and(|value| !valid_life_safety_state(value))
253 || commit
254 .tracking_value
255 .is_some_and(|value| !valid_life_safety_state(value))
256 || commit
257 .silenced
258 .is_some_and(|value| !valid_silenced_state(value))
259 {
260 return Err(commit_value_error());
261 }
262
263 if let Some(value) = commit.present_value {
264 self.present_value = value;
265 }
266 if let Some(value) = commit.tracking_value {
267 self.simulation().track(value);
268 }
269 if let Some(value) = commit.silenced {
270 self.silenced = value;
271 }
272 self.operation_expected = LifeSafetyOperation::NONE;
273 Ok(LifeSafetyOperationEffect::Applied)
274 }
275}