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