Skip to main content

bacnet_objects/
channel.rs

1//! Channel object (type 53) per ASHRAE 135-2020 Clause 12.53.
2//!
3//! A Channel forwards one value to a set of object properties and keeps no
4//! state of its own besides that value: no priority array, no algorithm. A
5//! Present_Value write is passed on to every member of
6//! List_Of_Object_Property_References at the priority it came with.
7//!
8//! The object owns the bookkeeping: which values Present_Value takes (a
9//! BACnetChannelValue), Last_Priority, Write_Status and the busy interlock
10//! while a distribution runs. The member writes are the bundled server's,
11//! made the way a Command object's are: it takes the queued [`CommandRun`]
12//! under the guard that committed the Present_Value write, then, once that
13//! guard is released, coerces the value to each member's datatype
14//! ([`coerce_channel_value`]) and writes it through the local write path,
15//! honouring each member's Execution_Delay. The outcome comes back through
16//! [`BACnetObject::complete_command_run_internal`].
17//!
18//! A member may name another Device (Clause 12.53.11): the server writes it
19//! there as a confirmed WriteProperty, from its device bindings, coerced to
20//! the datatype a ReadProperty there returned. The object keeps that datatype
21//! for each member ([`BACnetObject::remember_member_datatype_internal`]) until
22//! the member is written again, or the server forgets it (#1342).
23//!
24//! Reliability reports how the last distribution ended (Clause 12.53.9). A
25//! SUCCESSFUL one sets NO_FAULT_DETECTED; a FAILED one sets the kind of its
26//! first failure ([`WriteFailure::reliability`]): CONFIGURATION_ERROR,
27//! PROCESS_ERROR or COMMUNICATION_FAILURE. It keeps its value while a
28//! distribution is in progress, and Status_Flags shows FAULT whenever it isn't
29//! NO_FAULT_DETECTED. Out of service, Reliability is decoupled from the
30//! distributions (Clause 12.53.10): it holds what it read when the object
31//! went out of service, a client may write any Reliability value to it, and
32//! back in service it reads the last distribution's verdict again.
33//!
34//! Channel_Number and Control_Groups are what the bundled server's WriteGroup
35//! execution matches on (Clause 15.11). Allow_Group_Delay_Inhibit is served
36//! for it too: the object only stores the flag, and the server reads it to
37//! decide whether a WriteGroup asking for no delays gets them.
38
39use std::borrow::Cow;
40use std::sync::Arc;
41
42use bacnet_encoding::constructed::is_lighting_command_channel_value;
43use bacnet_types::constructed::BACnetDeviceObjectPropertyReference;
44use bacnet_types::enums::{
45    ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier, Reliability, WriteStatus,
46};
47use bacnet_types::error::Error;
48use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
49
50use crate::command::{next_generation, CommandRun, RunPlan, WriteFailure};
51use crate::common::{self, read_identity_properties};
52use crate::property_metadata::{property_list_from_metadata, PropertyMetadata};
53use crate::traits::BACnetObject;
54
55mod arrays;
56mod coercion;
57mod metadata;
58
59pub use arrays::{MAX_CHANNEL_MEMBERS, MAX_CONTROL_GROUPS};
60pub use coercion::{coerce_channel_value, CoercionFailure, MemberDatatype};
61
62/// The priority Last_Priority holds when a write carried none, and its
63/// initial value (Clause 12.53.6).
64const DEFAULT_PRIORITY: u8 = 16;
65
66/// One member a distribution writes.
67#[doc(hidden)]
68#[derive(Debug, Clone, PartialEq)]
69pub struct ChannelMember {
70    /// The property written; never an empty reference. One naming a Device
71    /// other than this one is written in that device.
72    pub reference: BACnetDeviceObjectPropertyReference,
73    /// Milliseconds after the distribution starts before this member is
74    /// written: its Execution_Delay element.
75    pub delay_ms: u32,
76    /// The member's zero-based slot in List_Of_Object_Property_References.
77    pub slot: usize,
78    /// For a member in another device, the datatype an earlier distribution
79    /// learned for it, if any (#1342).
80    pub learned: Option<MemberDatatype>,
81}
82
83/// A Channel's Present_Value write, for the server to pass on.
84#[doc(hidden)]
85#[derive(Debug, Clone, PartialEq)]
86pub struct ChannelDistribution {
87    /// The value written, before any coercion.
88    pub value: PropertyValue,
89    /// The priority the write carried, which each member write carries too.
90    pub priority: Option<u8>,
91    /// The members to write, in list order.
92    pub members: Vec<ChannelMember>,
93}
94
95/// BACnet Channel object: forwards each Present_Value write to a list of
96/// member properties.
97pub struct ChannelObject {
98    oid: ObjectIdentifier,
99    name: String,
100    description: String,
101    present_value: PropertyValue,
102    last_priority: u8,
103    write_status: WriteStatus,
104    /// The verdict of the last distribution to end, or of a Present_Value
105    /// write that had nothing to distribute.
106    reliability: Reliability,
107    /// What Reliability reads while out of service: the value it read when
108    /// the object went out of service, or one a client wrote since. `None`
109    /// in service.
110    simulated_reliability: Option<Reliability>,
111    out_of_service: bool,
112    members: Vec<BACnetDeviceObjectPropertyReference>,
113    /// The datatype the server learned for each member in another device,
114    /// slot by slot with `members`; a slot whose member is replaced forgets
115    /// it (#1342).
116    learned: Vec<Option<MemberDatatype>>,
117    execution_delay: Vec<u32>,
118    channel_number: u16,
119    control_groups: Vec<u32>,
120    allow_group_delay_inhibit: bool,
121    /// A fresh `next_generation` value at every Present_Value write that
122    /// starts a distribution, unique across the process.
123    generation: u64,
124    pending_run: Option<CommandRun>,
125}
126
127impl ChannelObject {
128    /// Create a Channel with no members, in control group 0 alone (no
129    /// assignment), Present_Value NULL, Write_Status IDLE, Reliability
130    /// NO_FAULT_DETECTED and Allow_Group_Delay_Inhibit FALSE.
131    pub fn new(instance: u32, name: impl Into<String>, channel_number: u16) -> Result<Self, Error> {
132        let oid = ObjectIdentifier::new(ObjectType::CHANNEL, instance)?;
133        Ok(Self {
134            oid,
135            name: name.into(),
136            description: String::new(),
137            present_value: PropertyValue::Null,
138            last_priority: DEFAULT_PRIORITY,
139            write_status: WriteStatus::IDLE,
140            reliability: Reliability::NO_FAULT_DETECTED,
141            simulated_reliability: None,
142            out_of_service: false,
143            members: Vec::new(),
144            learned: Vec::new(),
145            execution_delay: Vec::new(),
146            channel_number,
147            control_groups: vec![0],
148            allow_group_delay_inhibit: false,
149            generation: next_generation(),
150            pending_run: None,
151        })
152    }
153
154    /// Replace List_Of_Object_Property_References.
155    ///
156    /// Execution_Delay follows the new size, keeping its leading delays and
157    /// adding zeros. A member may name another Device, which the server then
158    /// writes over the network. Refused, leaving the list as it was, with
159    /// PROPERTY / VALUE_OUT_OF_RANGE for a member whose device identifier
160    /// isn't a Device object (#1285), and with RESOURCES /
161    /// NO_SPACE_TO_WRITE_PROPERTY past [`MAX_CHANNEL_MEMBERS`].
162    pub fn set_members(
163        &mut self,
164        members: Vec<BACnetDeviceObjectPropertyReference>,
165    ) -> Result<(), Error> {
166        if members.len() > MAX_CHANNEL_MEMBERS {
167            return Err(common::protocol_error(
168                ErrorClass::RESOURCES,
169                ErrorCode::NO_SPACE_TO_WRITE_PROPERTY,
170            ));
171        }
172        crate::device_reference::check_device_members(&members)?;
173        self.execution_delay.resize(members.len(), 0);
174        self.learned = vec![None; members.len()];
175        self.members = members;
176        Ok(())
177    }
178
179    /// Replace Execution_Delay, one delay in milliseconds per member.
180    ///
181    /// Refused with PROPERTY / VALUE_OUT_OF_RANGE, leaving the delays as they
182    /// were, unless there's exactly one per member, so set the members first.
183    pub fn set_execution_delay(&mut self, delays: Vec<u32>) -> Result<(), Error> {
184        if delays.len() != self.members.len() {
185            return Err(common::value_out_of_range_error());
186        }
187        self.execution_delay = delays;
188        Ok(())
189    }
190
191    /// Replace Control_Groups.
192    ///
193    /// Refused, leaving the groups as they were, with PROPERTY /
194    /// VALUE_OUT_OF_RANGE for an empty list (the array holds at least one
195    /// entry, zero meaning no assignment) and with RESOURCES /
196    /// NO_SPACE_TO_WRITE_PROPERTY past [`MAX_CONTROL_GROUPS`].
197    pub fn set_control_groups(&mut self, groups: Vec<u32>) -> Result<(), Error> {
198        arrays::check_group_count(groups.len())?;
199        self.control_groups = groups;
200        Ok(())
201    }
202
203    /// Set Allow_Group_Delay_Inhibit: whether a WriteGroup that asks for no
204    /// execution delays skips this Channel's Execution_Delay (Clause
205    /// 12.53.13). WriteProperty and WritePropertyMultiple keep the delays
206    /// either way.
207    pub fn set_allow_group_delay_inhibit(&mut self, allow: bool) {
208        self.allow_group_delay_inhibit = allow;
209    }
210
211    /// Take a Present_Value write (Clauses 12.53.5 to 12.53.7).
212    fn write_present_value(
213        &mut self,
214        value: PropertyValue,
215        priority: Option<u8>,
216    ) -> Result<(), Error> {
217        if self.write_status == WriteStatus::IN_PROGRESS {
218            return Err(common::protocol_error(ErrorClass::OBJECT, ErrorCode::BUSY));
219        }
220        check_channel_value(&value)?;
221        let last_priority = match priority {
222            None => DEFAULT_PRIORITY,
223            Some(priority @ 1..=16) => priority,
224            Some(_) => return Err(common::value_out_of_range_error()),
225        };
226        self.present_value = value;
227        self.last_priority = last_priority;
228        // Out of service the value stays here (Clause 12.53.10).
229        if self.out_of_service {
230            return Ok(());
231        }
232        if self.members.is_empty() {
233            self.write_status = WriteStatus::IDLE;
234            self.reliability = Reliability::NO_FAULT_DETECTED;
235            return Ok(());
236        }
237        self.generation = next_generation();
238        let members: Vec<ChannelMember> = self
239            .members
240            .iter()
241            .zip(&self.execution_delay)
242            .zip(&self.learned)
243            .enumerate()
244            .filter(|(_, ((member, _), _))| !arrays::is_empty(member))
245            .map(|(slot, ((member, delay), learned))| ChannelMember {
246                reference: member.clone(),
247                delay_ms: *delay,
248                slot,
249                learned: *learned,
250            })
251            .collect();
252        if members.is_empty() {
253            // Every member is an empty reference: nothing to write, nothing
254            // to fail.
255            self.write_status = WriteStatus::SUCCESSFUL;
256            self.reliability = Reliability::NO_FAULT_DETECTED;
257            return Ok(());
258        }
259        self.write_status = WriteStatus::IN_PROGRESS;
260        self.pending_run = Some(CommandRun {
261            source: self.oid,
262            generation: self.generation,
263            plan: RunPlan::Channel(ChannelDistribution {
264                value: self.present_value.clone(),
265                priority,
266                members,
267            }),
268            chain: Arc::from([]),
269        });
270        Ok(())
271    }
272
273    /// What Reliability reads: a client's simulated value while out of
274    /// service, otherwise the last distribution's verdict.
275    fn served_reliability(&self) -> Reliability {
276        self.simulated_reliability.unwrap_or(self.reliability)
277    }
278
279    fn write_out_of_service(&mut self, value: PropertyValue) -> Result<(), Error> {
280        let PropertyValue::Boolean(value) = value else {
281            return Err(common::invalid_data_type_error());
282        };
283        if value != self.out_of_service {
284            // Out of service a client simulates Reliability, starting from
285            // what it reads now; back in service the verdict shows again.
286            self.simulated_reliability = value.then_some(self.reliability);
287        }
288        self.out_of_service = value;
289        Ok(())
290    }
291
292    /// A client's simulated Reliability, taken only while out of service
293    /// (Clause 12.53.10).
294    fn write_reliability(&mut self, value: PropertyValue) -> Result<(), Error> {
295        if !self.out_of_service {
296            return Err(common::write_access_denied_error());
297        }
298        let PropertyValue::Enumerated(raw) = value else {
299            return Err(common::invalid_data_type_error());
300        };
301        let reliability = Reliability::from_raw(raw);
302        if !common::is_reliability_value_valid(reliability) {
303            return Err(common::value_out_of_range_error());
304        }
305        self.simulated_reliability = Some(reliability);
306        Ok(())
307    }
308}
309
310/// Refuse a Present_Value that isn't a BACnetChannelValue: a primitive, or
311/// one context-\[0\] lighting command.
312fn check_channel_value(value: &PropertyValue) -> Result<(), Error> {
313    match value {
314        value if value.is_primitive() => Ok(()),
315        PropertyValue::ApplicationData(octets) if octets.first() == Some(&0x0E) => {
316            if is_lighting_command_channel_value(octets) {
317                Ok(())
318            } else {
319                Err(common::invalid_data_encoding_error())
320            }
321        }
322        _ => Err(common::invalid_data_type_error()),
323    }
324}
325
326impl BACnetObject for ChannelObject {
327    fn object_identifier(&self) -> ObjectIdentifier {
328        self.oid
329    }
330
331    fn object_name(&self) -> &str {
332        &self.name
333    }
334
335    fn read_property(
336        &self,
337        property: PropertyIdentifier,
338        array_index: Option<u32>,
339    ) -> Result<PropertyValue, Error> {
340        if let Some(result) = read_identity_properties!(self, property, array_index) {
341            return result;
342        }
343        match property {
344            PropertyIdentifier::OBJECT_TYPE => {
345                Ok(PropertyValue::Enumerated(ObjectType::CHANNEL.to_raw()))
346            }
347            PropertyIdentifier::PRESENT_VALUE => Ok(self.present_value.clone()),
348            PropertyIdentifier::LAST_PRIORITY => {
349                Ok(PropertyValue::Unsigned(self.last_priority.into()))
350            }
351            PropertyIdentifier::WRITE_STATUS => {
352                Ok(PropertyValue::Enumerated(self.write_status.to_raw()))
353            }
354            // FAULT follows Reliability; with no intrinsic reporting and
355            // nothing overriding the value, IN_ALARM and OVERRIDDEN stay
356            // clear (Clause 12.53.8).
357            PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
358                StatusFlags::empty(),
359                self.served_reliability(),
360                self.out_of_service,
361                EventState::NORMAL,
362            )),
363            PropertyIdentifier::RELIABILITY => Ok(PropertyValue::Enumerated(
364                self.served_reliability().to_raw(),
365            )),
366            PropertyIdentifier::OUT_OF_SERVICE => Ok(PropertyValue::Boolean(self.out_of_service)),
367            PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => common::read_array(
368                crate::device_reference::reference_elements(&self.members),
369                array_index,
370            ),
371            PropertyIdentifier::EXECUTION_DELAY => common::read_array(
372                self.execution_delay
373                    .iter()
374                    .map(|delay| PropertyValue::Unsigned((*delay).into()))
375                    .collect(),
376                array_index,
377            ),
378            PropertyIdentifier::ALLOW_GROUP_DELAY_INHIBIT => {
379                Ok(PropertyValue::Boolean(self.allow_group_delay_inhibit))
380            }
381            PropertyIdentifier::CHANNEL_NUMBER => {
382                Ok(PropertyValue::Unsigned(self.channel_number.into()))
383            }
384            PropertyIdentifier::CONTROL_GROUPS => common::read_array(
385                self.control_groups
386                    .iter()
387                    .map(|group| PropertyValue::Unsigned((*group).into()))
388                    .collect(),
389                array_index,
390            ),
391            _ => Err(common::unknown_property_error()),
392        }
393    }
394
395    fn write_property(
396        &mut self,
397        property: PropertyIdentifier,
398        array_index: Option<u32>,
399        value: PropertyValue,
400        priority: Option<u8>,
401    ) -> Result<(), Error> {
402        if array_index.is_some() && !self.is_array_property(property) {
403            return Err(common::property_is_not_an_array_error());
404        }
405        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
406            return result;
407        }
408        if let Some(result) = common::write_description(&mut self.description, property, &value) {
409            return result;
410        }
411        match property {
412            PropertyIdentifier::PRESENT_VALUE => self.write_present_value(value, priority),
413            PropertyIdentifier::OUT_OF_SERVICE => self.write_out_of_service(value),
414            PropertyIdentifier::RELIABILITY => self.write_reliability(value),
415            PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => {
416                self.write_members(array_index, value)
417            }
418            PropertyIdentifier::EXECUTION_DELAY => self.write_execution_delay(array_index, value),
419            PropertyIdentifier::ALLOW_GROUP_DELAY_INHIBIT => {
420                let PropertyValue::Boolean(allow) = value else {
421                    return Err(common::invalid_data_type_error());
422                };
423                self.allow_group_delay_inhibit = allow;
424                Ok(())
425            }
426            PropertyIdentifier::CHANNEL_NUMBER => {
427                let PropertyValue::Unsigned(raw) = value else {
428                    return Err(common::invalid_data_type_error());
429                };
430                self.channel_number =
431                    u16::try_from(raw).map_err(|_| common::value_out_of_range_error())?;
432                Ok(())
433            }
434            PropertyIdentifier::CONTROL_GROUPS => self.write_control_groups(array_index, value),
435            _ => Err(common::unhandled_write_error(
436                self.property_metadata().as_ref(),
437                property,
438                array_index,
439            )),
440        }
441    }
442
443    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
444        Cow::Borrowed(metadata::CHANNEL_PROPERTY_METADATA)
445    }
446
447    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
448        property_list_from_metadata(metadata::CHANNEL_PROPERTY_METADATA)
449    }
450
451    fn take_command_run_internal(&mut self) -> Option<CommandRun> {
452        self.pending_run.take()
453    }
454
455    fn command_generation_internal(&self) -> Option<u64> {
456        Some(self.generation)
457    }
458
459    fn remember_member_datatype_internal(
460        &mut self,
461        slot: usize,
462        reference: &BACnetDeviceObjectPropertyReference,
463        datatype: Option<MemberDatatype>,
464    ) {
465        if self.members.get(slot) == Some(reference) {
466            self.learned[slot] = datatype;
467        }
468    }
469
470    fn complete_command_run_internal(
471        &mut self,
472        generation: u64,
473        outcome: Result<(), WriteFailure>,
474    ) -> bool {
475        if generation != self.generation || self.write_status != WriteStatus::IN_PROGRESS {
476            return false;
477        }
478        (self.write_status, self.reliability) = match outcome {
479            Ok(()) => (WriteStatus::SUCCESSFUL, Reliability::NO_FAULT_DETECTED),
480            Err(failure) => (WriteStatus::FAILED, failure.reliability()),
481        };
482        true
483    }
484}
485
486#[cfg(test)]
487mod tests;
488
489#[cfg(test)]
490mod array_tests;
491
492#[cfg(test)]
493mod coercion_tests;
494
495#[cfg(test)]
496mod reliability_tests;
497
498#[cfg(test)]
499mod learned_tests;