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::constructed_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 of the constructed alternatives, a lighting command in context tag
312/// `[0]` or Addendum 135-2020ca's xy colour in `[1]` and colour command in
313/// `[2]` (#1474). Octets that open one of those tags but don't hold one are
314/// INVALID_DATA_ENCODING.
315fn check_channel_value(value: &PropertyValue) -> Result<(), Error> {
316    match value {
317        value if value.is_primitive() => Ok(()),
318        PropertyValue::ApplicationData(octets)
319            if matches!(octets.first(), Some(0x0E | 0x1E | 0x2E)) =>
320        {
321            if constructed_channel_value(octets).is_some() {
322                Ok(())
323            } else {
324                Err(common::invalid_data_encoding_error())
325            }
326        }
327        _ => Err(common::invalid_data_type_error()),
328    }
329}
330
331impl BACnetObject for ChannelObject {
332    fn object_identifier(&self) -> ObjectIdentifier {
333        self.oid
334    }
335
336    fn object_name(&self) -> &str {
337        &self.name
338    }
339
340    fn read_property(
341        &self,
342        property: PropertyIdentifier,
343        array_index: Option<u32>,
344    ) -> Result<PropertyValue, Error> {
345        if let Some(result) = read_identity_properties!(self, property, array_index) {
346            return result;
347        }
348        match property {
349            PropertyIdentifier::OBJECT_TYPE => {
350                Ok(PropertyValue::Enumerated(ObjectType::CHANNEL.to_raw()))
351            }
352            PropertyIdentifier::PRESENT_VALUE => Ok(self.present_value.clone()),
353            PropertyIdentifier::LAST_PRIORITY => {
354                Ok(PropertyValue::Unsigned(self.last_priority.into()))
355            }
356            PropertyIdentifier::WRITE_STATUS => {
357                Ok(PropertyValue::Enumerated(self.write_status.to_raw()))
358            }
359            // FAULT follows Reliability; with no intrinsic reporting and
360            // nothing overriding the value, IN_ALARM and OVERRIDDEN stay
361            // clear (Clause 12.53.8).
362            PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
363                StatusFlags::empty(),
364                self.served_reliability(),
365                self.out_of_service,
366                EventState::NORMAL,
367            )),
368            PropertyIdentifier::RELIABILITY => Ok(PropertyValue::Enumerated(
369                self.served_reliability().to_raw(),
370            )),
371            PropertyIdentifier::OUT_OF_SERVICE => Ok(PropertyValue::Boolean(self.out_of_service)),
372            PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => common::read_array(
373                crate::device_reference::reference_elements(&self.members),
374                array_index,
375            ),
376            PropertyIdentifier::EXECUTION_DELAY => common::read_array(
377                self.execution_delay
378                    .iter()
379                    .map(|delay| PropertyValue::Unsigned((*delay).into()))
380                    .collect(),
381                array_index,
382            ),
383            PropertyIdentifier::ALLOW_GROUP_DELAY_INHIBIT => {
384                Ok(PropertyValue::Boolean(self.allow_group_delay_inhibit))
385            }
386            PropertyIdentifier::CHANNEL_NUMBER => {
387                Ok(PropertyValue::Unsigned(self.channel_number.into()))
388            }
389            PropertyIdentifier::CONTROL_GROUPS => common::read_array(
390                self.control_groups
391                    .iter()
392                    .map(|group| PropertyValue::Unsigned((*group).into()))
393                    .collect(),
394                array_index,
395            ),
396            _ => Err(common::unknown_property_error()),
397        }
398    }
399
400    fn write_property(
401        &mut self,
402        property: PropertyIdentifier,
403        array_index: Option<u32>,
404        value: PropertyValue,
405        priority: Option<u8>,
406    ) -> Result<(), Error> {
407        if array_index.is_some() && !self.is_array_property(property) {
408            return Err(common::property_is_not_an_array_error());
409        }
410        if let Some(result) = common::write_object_name(&mut self.name, property, &value) {
411            return result;
412        }
413        if let Some(result) = common::write_description(&mut self.description, property, &value) {
414            return result;
415        }
416        match property {
417            PropertyIdentifier::PRESENT_VALUE => self.write_present_value(value, priority),
418            PropertyIdentifier::OUT_OF_SERVICE => self.write_out_of_service(value),
419            PropertyIdentifier::RELIABILITY => self.write_reliability(value),
420            PropertyIdentifier::LIST_OF_OBJECT_PROPERTY_REFERENCES => {
421                self.write_members(array_index, value)
422            }
423            PropertyIdentifier::EXECUTION_DELAY => self.write_execution_delay(array_index, value),
424            PropertyIdentifier::ALLOW_GROUP_DELAY_INHIBIT => {
425                let PropertyValue::Boolean(allow) = value else {
426                    return Err(common::invalid_data_type_error());
427                };
428                self.allow_group_delay_inhibit = allow;
429                Ok(())
430            }
431            PropertyIdentifier::CHANNEL_NUMBER => {
432                let PropertyValue::Unsigned(raw) = value else {
433                    return Err(common::invalid_data_type_error());
434                };
435                self.channel_number =
436                    u16::try_from(raw).map_err(|_| common::value_out_of_range_error())?;
437                Ok(())
438            }
439            PropertyIdentifier::CONTROL_GROUPS => self.write_control_groups(array_index, value),
440            _ => Err(common::unhandled_write_error(
441                self.property_metadata().as_ref(),
442                property,
443                array_index,
444            )),
445        }
446    }
447
448    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
449        Cow::Borrowed(metadata::CHANNEL_PROPERTY_METADATA)
450    }
451
452    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
453        property_list_from_metadata(metadata::CHANNEL_PROPERTY_METADATA)
454    }
455
456    fn take_command_run_internal(&mut self) -> Option<CommandRun> {
457        self.pending_run.take()
458    }
459
460    fn command_generation_internal(&self) -> Option<u64> {
461        Some(self.generation)
462    }
463
464    fn remember_member_datatype_internal(
465        &mut self,
466        slot: usize,
467        reference: &BACnetDeviceObjectPropertyReference,
468        datatype: Option<MemberDatatype>,
469    ) {
470        if self.members.get(slot) == Some(reference) {
471            self.learned[slot] = datatype;
472        }
473    }
474
475    fn complete_command_run_internal(
476        &mut self,
477        generation: u64,
478        outcome: Result<(), WriteFailure>,
479    ) -> bool {
480        if generation != self.generation || self.write_status != WriteStatus::IN_PROGRESS {
481            return false;
482        }
483        (self.write_status, self.reliability) = match outcome {
484            Ok(()) => (WriteStatus::SUCCESSFUL, Reliability::NO_FAULT_DETECTED),
485            Err(failure) => (WriteStatus::FAILED, failure.reliability()),
486        };
487        true
488    }
489}
490
491#[cfg(test)]
492mod tests;
493
494#[cfg(test)]
495mod array_tests;
496
497#[cfg(test)]
498mod coercion_tests;
499
500#[cfg(test)]
501mod reliability_tests;
502
503#[cfg(test)]
504mod learned_tests;