bacnet_objects/
elevator.rs1use crate::prelude::*;
8use alloc::borrow::Cow;
9use bacnet_types::constructed::BACnetLandingCallStatus;
10use bacnet_types::enums::{LiftCarDirection, LiftGroupMode, ObjectType, PropertyIdentifier};
11use bacnet_types::error::Error;
12use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
13use core::hash::Hash;
14
15use crate::collections::HashSet;
16
17use crate::common;
18use crate::traits::BACnetObject;
19
20mod car_state;
21mod door_values;
22mod doors;
23mod energy_meter;
24mod escalator;
25mod landing_calls;
26mod lift;
27mod membership;
28mod metadata;
29
30pub use escalator::EscalatorObject;
31pub use lift::LiftObject;
32
33fn named_or_proprietary<T: Copy + PartialEq>(named: &[(&str, T)], value: T, raw: u32) -> bool {
40 named.iter().any(|&(_, named)| named == value) || (1024..=65_535).contains(&raw)
41}
42
43fn direction_in_range(direction: &LiftCarDirection) -> bool {
47 named_or_proprietary(LiftCarDirection::ALL_NAMED, *direction, direction.to_raw())
48}
49
50fn checked<T>(value: T, in_range: fn(&T) -> bool) -> Result<T, Error> {
52 if in_range(&value) {
53 Ok(value)
54 } else {
55 Err(common::value_out_of_range_error())
56 }
57}
58
59fn decode_fault_signals<T: Copy + Eq + Hash>(
66 value: PropertyValue,
67 named: &[(&str, T)],
68 from_raw: fn(u32) -> T,
69) -> Result<Vec<T>, Error> {
70 let values = match value {
71 PropertyValue::Enumerated(v) => vec![PropertyValue::Enumerated(v)],
72 PropertyValue::List(values) => values,
73 _ => return Err(common::invalid_data_type_error()),
74 };
75 let mut faults = Vec::with_capacity(values.len());
76 let mut seen = HashSet::with_capacity(values.len());
77 for (index, value) in values.into_iter().enumerate() {
79 let PropertyValue::Enumerated(raw) = value else {
80 return Err(common::at_list_element(
81 common::invalid_data_type_error(),
82 index,
83 ));
84 };
85 let fault = from_raw(raw);
86 if !named_or_proprietary(named, fault, raw) || !seen.insert(fault) {
87 return Err(common::at_list_element(
88 common::value_out_of_range_error(),
89 index,
90 ));
91 }
92 faults.push(fault);
93 }
94 Ok(faults)
95}
96
97pub struct ElevatorGroupObject {
107 oid: ObjectIdentifier,
108 name: String,
109 description: String,
110 machine_room_id: ObjectIdentifier,
112 group_id: u8,
114 group_members: Vec<ObjectIdentifier>,
116 group_mode: LiftGroupMode,
118 landing_calls: Vec<BACnetLandingCallStatus>,
120 landing_call_control: BACnetLandingCallStatus,
122}
123
124impl ElevatorGroupObject {
125 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
127 let oid = ObjectIdentifier::new(ObjectType::ELEVATOR_GROUP, instance)?;
128 Ok(Self {
129 oid,
130 name: name.into(),
131 description: String::new(),
132 machine_room_id: ObjectIdentifier::new(
133 ObjectType::POSITIVE_INTEGER_VALUE,
134 ObjectIdentifier::MAX_INSTANCE,
135 )?,
136 group_id: 0,
137 group_members: Vec::new(),
138 group_mode: LiftGroupMode::UNKNOWN,
139 landing_calls: Vec::new(),
140 landing_call_control: landing_calls::initial_landing_call_control(),
141 })
142 }
143
144 pub fn machine_room_id(&self) -> ObjectIdentifier {
148 self.machine_room_id
149 }
150
151 pub fn set_machine_room_id(&mut self, oid: ObjectIdentifier) -> Result<(), Error> {
159 if oid.object_type() != ObjectType::POSITIVE_INTEGER_VALUE {
160 return Err(common::value_out_of_range_error());
161 }
162 self.machine_room_id = oid;
163 Ok(())
164 }
165
166 pub fn add_member(&mut self, oid: ObjectIdentifier) {
168 self.group_members.push(oid);
169 }
170
171 pub fn landing_call_control(&self) -> &BACnetLandingCallStatus {
174 &self.landing_call_control
175 }
176
177 pub fn landing_calls(&self) -> &[BACnetLandingCallStatus] {
179 &self.landing_calls
180 }
181
182 pub fn set_landing_calls(&mut self, calls: Vec<BACnetLandingCallStatus>) -> Result<(), Error> {
188 calls.iter().try_for_each(landing_calls::validate)?;
189 self.landing_calls = calls;
190 Ok(())
191 }
192}
193
194impl BACnetObject for ElevatorGroupObject {
195 fn object_identifier(&self) -> ObjectIdentifier {
196 self.oid
197 }
198
199 fn object_name(&self) -> &str {
200 &self.name
201 }
202
203 fn read_property(
204 &self,
205 property: PropertyIdentifier,
206 array_index: Option<u32>,
207 ) -> Result<PropertyValue, Error> {
208 match property {
211 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
212 Ok(PropertyValue::ObjectIdentifier(self.oid))
213 }
214 p if p == PropertyIdentifier::OBJECT_NAME => {
215 Ok(PropertyValue::CharacterString(self.name.clone()))
216 }
217 p if p == PropertyIdentifier::DESCRIPTION => {
218 Ok(PropertyValue::CharacterString(self.description.clone()))
219 }
220 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
221 ObjectType::ELEVATOR_GROUP.to_raw(),
222 )),
223 p if p == PropertyIdentifier::PROPERTY_LIST => {
224 common::read_property_list_property(&self.property_list(), array_index)
225 }
226 p if p == PropertyIdentifier::MACHINE_ROOM_ID => {
227 Ok(PropertyValue::ObjectIdentifier(self.machine_room_id))
228 }
229 p if p == PropertyIdentifier::GROUP_ID => {
230 Ok(PropertyValue::Unsigned(u64::from(self.group_id)))
231 }
232 p if p == PropertyIdentifier::GROUP_MEMBERS => common::read_array(
235 self.group_members
236 .iter()
237 .copied()
238 .map(PropertyValue::ObjectIdentifier)
239 .collect(),
240 array_index,
241 ),
242 p if p == PropertyIdentifier::GROUP_MODE => {
243 Ok(PropertyValue::Enumerated(self.group_mode.to_raw()))
244 }
245 p if p == PropertyIdentifier::LANDING_CALLS => Ok(PropertyValue::List(
246 self.landing_calls
247 .iter()
248 .map(landing_calls::encode)
249 .collect::<Result<Vec<_>, Error>>()?,
250 )),
251 p if p == PropertyIdentifier::LANDING_CALL_CONTROL => {
252 landing_calls::encode(&self.landing_call_control)
253 }
254 _ => Err(common::unknown_property_error()),
255 }
256 }
257
258 fn write_property(
259 &mut self,
260 property: PropertyIdentifier,
261 _array_index: Option<u32>,
262 value: PropertyValue,
263 _priority: Option<u8>,
264 ) -> Result<(), Error> {
265 if let Some(result) = common::write_description(&mut self.description, property, &value) {
266 return result;
267 }
268 match property {
269 p if p == PropertyIdentifier::GROUP_ID => {
270 if let PropertyValue::Unsigned(v) = value {
271 self.group_id =
273 u8::try_from(v).map_err(|_| common::value_out_of_range_error())?;
274 Ok(())
275 } else {
276 Err(common::invalid_data_type_error())
277 }
278 }
279 p if p == PropertyIdentifier::GROUP_MODE => {
280 if let PropertyValue::Enumerated(v) = value {
281 self.group_mode = LiftGroupMode::from_raw(v);
282 Ok(())
283 } else {
284 Err(common::invalid_data_type_error())
285 }
286 }
287 p if p == PropertyIdentifier::LANDING_CALL_CONTROL => {
288 self.landing_call_control = landing_calls::decode_write(value)?;
289 Ok(())
290 }
291 _ => Err(crate::common::unhandled_write_error(
292 self.property_metadata().as_ref(),
293 property,
294 _array_index,
295 )),
296 }
297 }
298
299 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
300 metadata::for_elevator_group_object(self)
301 }
302
303 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
304 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
305 }
306}
307
308#[cfg(test)]
313mod tests;