bacnet_objects/access_control/
zone.rs1use bacnet_types::enums::AccessZoneOccupancyState;
2
3use super::zone_occupancy::{adjusted, Occupancy};
4use super::zone_out_of_service::ZoneState;
5use super::*;
6use crate::event::state_reporting::{ChangeOfStateReporting, ListedStates};
7
8pub struct AccessZoneObject {
42 oid: ObjectIdentifier,
43 name: String,
44 description: String,
45 global_identifier: u64,
46 state: ZoneState,
49 device_state: Option<ZoneState>,
51 occupancy: Occupancy,
53 entry_points: Vec<BACnetDeviceObjectReference>,
54 exit_points: Vec<BACnetDeviceObjectReference>,
55 out_of_service: bool,
56 reporting: ChangeOfStateReporting,
58}
59
60impl AccessZoneObject {
61 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
63 let oid = ObjectIdentifier::new(ObjectType::ACCESS_ZONE, instance)?;
64 Ok(Self {
65 oid,
66 name: name.into(),
67 description: String::new(),
68 global_identifier: 0,
69 state: ZoneState {
70 occupancy_count: 0,
71 reliability: Reliability::NO_FAULT_DETECTED,
72 },
73 device_state: None,
74 occupancy: Occupancy::NEW,
75 entry_points: Vec::new(),
76 exit_points: Vec::new(),
77 out_of_service: false,
78 reporting: ChangeOfStateReporting::new(ALARM_STATES),
79 })
80 }
81
82 pub fn set_entry_points(
91 &mut self,
92 points: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
93 ) -> Result<(), Error> {
94 self.entry_points = references_to(ObjectType::ACCESS_POINT, points)?;
95 Ok(())
96 }
97
98 pub fn set_exit_points(
101 &mut self,
102 points: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
103 ) -> Result<(), Error> {
104 self.exit_points = references_to(ObjectType::ACCESS_POINT, points)?;
105 Ok(())
106 }
107
108 pub fn set_alarm_values(
117 &mut self,
118 states: impl IntoIterator<Item = AccessZoneOccupancyState>,
119 ) -> Result<(), Error> {
120 self.reporting
121 .set_alarm_values(states.into_iter().map(|state| state.to_raw()).collect())
122 }
123
124 pub fn set_occupancy_count(&mut self, count: u64) {
131 if self.occupancy.enabled {
132 self.device_state_mut().occupancy_count = count;
133 }
134 }
135
136 pub fn set_occupancy_count_enable(&mut self, enabled: bool) {
141 self.occupancy.enabled = enabled;
142 if !enabled {
143 self.occupancy.adjust_value = 0;
144 self.state.occupancy_count = 0;
145 if let Some(own) = &mut self.device_state {
146 own.occupancy_count = 0;
147 }
148 }
149 }
150
151 pub fn set_occupancy_limits(&mut self, lower: u64, upper: u64) -> Result<(), Error> {
156 self.occupancy.set_limits(lower, upper)
157 }
158
159 pub fn occupancy_state(&self) -> AccessZoneOccupancyState {
162 self.occupancy.state(self.state.occupancy_count)
163 }
164
165 fn watched_state(&self) -> u32 {
167 self.occupancy_state().to_raw()
168 }
169
170 fn served_reliability(&self) -> Reliability {
172 self.state.reliability
173 }
174
175 fn write_adjust_value(&mut self, value: &PropertyValue) -> Result<(), Error> {
180 let PropertyValue::Signed(adjust) = *value else {
181 return Err(common::invalid_data_type_error());
182 };
183 if !self.occupancy.enabled {
184 return Ok(());
185 }
186 self.occupancy.adjust_value = adjust;
187 if !self.out_of_service {
188 self.state.occupancy_count = adjusted(self.state.occupancy_count, adjust);
189 }
190 Ok(())
191 }
192
193 fn device_state_mut(&mut self) -> &mut ZoneState {
196 self.device_state.as_mut().unwrap_or(&mut self.state)
197 }
198}
199
200impl BACnetObject for AccessZoneObject {
201 fn object_identifier(&self) -> ObjectIdentifier {
202 self.oid
203 }
204
205 fn object_name(&self) -> &str {
206 &self.name
207 }
208
209 fn read_property(
210 &self,
211 property: PropertyIdentifier,
212 array_index: Option<u32>,
213 ) -> Result<PropertyValue, Error> {
214 if let Some(result) = crate::common::read_identity_properties!(self, property, array_index)
215 {
216 return result;
217 }
218 if let Some(result) = self.reporting.read(property, array_index) {
219 return result;
220 }
221 match property {
222 p if p == PropertyIdentifier::OBJECT_TYPE => {
223 Ok(PropertyValue::Enumerated(ObjectType::ACCESS_ZONE.to_raw()))
224 }
225 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
227 StatusFlags::empty(),
228 self.state.reliability,
229 self.out_of_service,
230 self.reporting.event_state(),
231 )),
232 p if p == PropertyIdentifier::OUT_OF_SERVICE => {
233 Ok(PropertyValue::Boolean(self.out_of_service))
234 }
235 p if p == PropertyIdentifier::RELIABILITY => {
236 Ok(PropertyValue::Enumerated(self.state.reliability.to_raw()))
237 }
238 p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
239 Ok(PropertyValue::Unsigned(self.global_identifier))
240 }
241 p if p == PropertyIdentifier::OCCUPANCY_COUNT => {
242 Ok(PropertyValue::Unsigned(self.state.occupancy_count))
243 }
244 p if p == PropertyIdentifier::OCCUPANCY_STATE => {
245 Ok(PropertyValue::Enumerated(self.occupancy_state().to_raw()))
246 }
247 p if p == PropertyIdentifier::OCCUPANCY_COUNT_ENABLE => {
248 Ok(PropertyValue::Boolean(self.occupancy.enabled))
249 }
250 p if p == PropertyIdentifier::ADJUST_VALUE => {
251 Ok(PropertyValue::Signed(self.occupancy.adjust_value))
252 }
253 p if p == PropertyIdentifier::OCCUPANCY_UPPER_LIMIT => {
254 Ok(PropertyValue::Unsigned(self.occupancy.upper_limit))
255 }
256 p if p == PropertyIdentifier::OCCUPANCY_LOWER_LIMIT => {
257 Ok(PropertyValue::Unsigned(self.occupancy.lower_limit))
258 }
259 p if p == PropertyIdentifier::ENTRY_POINTS => {
260 Ok(crate::device_reference::reference_list(&self.entry_points))
261 }
262 p if p == PropertyIdentifier::EXIT_POINTS => {
263 Ok(crate::device_reference::reference_list(&self.exit_points))
264 }
265 _ => Err(common::unknown_property_error()),
266 }
267 }
268
269 fn write_property(
270 &mut self,
271 property: PropertyIdentifier,
272 array_index: Option<u32>,
273 value: PropertyValue,
274 _priority: Option<u8>,
275 ) -> Result<(), Error> {
276 if let Some(result) = common::write_out_of_service_with_restore(
279 &mut self.out_of_service,
280 &mut self.state,
281 &mut self.device_state,
282 None,
283 property,
284 &value,
285 ) {
286 return result;
287 }
288 if let Some(result) = common::write_description(&mut self.description, property, &value) {
289 return result;
290 }
291 if let Some(result) = self.state.write(
292 self.out_of_service,
293 self.occupancy.enabled,
294 property,
295 &value,
296 ) {
297 return result;
298 }
299 if property == PropertyIdentifier::ADJUST_VALUE {
300 return self.write_adjust_value(&value);
301 }
302 if let Some(result) = self.reporting.write(property, array_index, &value) {
303 return result;
304 }
305 match property {
306 p if p == PropertyIdentifier::GLOBAL_IDENTIFIER => {
307 if let PropertyValue::Unsigned(v) = value {
308 self.global_identifier = v;
309 Ok(())
310 } else {
311 Err(common::invalid_data_type_error())
312 }
313 }
314 _ => Err(common::unhandled_write_error(
315 self.property_metadata().as_ref(),
316 property,
317 array_index,
318 )),
319 }
320 }
321
322 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
323 super::metadata_topology::for_access_zone_object(self)
324 }
325
326 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
327 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
328 }
329
330 crate::event::impl_change_of_state_reporting!(
331 reporting,
332 Self::watched_state,
333 Self::served_reliability
334 );
335
336 fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
343 if self.out_of_service {
344 return Err(common::write_access_denied_error());
345 }
346 self.state.reliability = checked_reliability(reliability)?;
347 Ok(())
348 }
349}
350
351const ALARM_STATES: ListedStates = ListedStates {
354 normal: AccessZoneOccupancyState::NORMAL.to_raw(),
355 in_range: occupancy_state_in_range,
356};
357
358fn occupancy_state_in_range(raw: u32) -> bool {
361 AccessZoneOccupancyState::ALL_NAMED
362 .iter()
363 .any(|&(_, named)| named.to_raw() == raw)
364 || (64..=65_535).contains(&raw)
365}
366
367