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