Skip to main content

bacnet_encoding/constructed/
lighting_command.rs

1//! Clause 21 codec for `BACnetLightingCommand`: a Lighting Output's
2//! Lighting_Command (Clause 12.54), and the one constructed alternative of a
3//! BACnetChannelValue, where it sits inside an opening and closing tag 0 (see
4//! `channel_value.rs`).
5//!
6//! The SEQUENCE has no frame of its own. The operation comes first under
7//! primitive context tag 0, then any of five optional fields in ascending tag
8//! order: REALs under tags 1 to 3 (target level, ramp rate, step increment)
9//! and Unsigneds under tags 4 and 5 (fade time, priority).
10
11use bacnet_types::constructed::BACnetLightingCommand;
12use bacnet_types::enums::LightingOperation;
13use bacnet_types::error::Error;
14use bytes::BytesMut;
15
16use super::members::unsigned_field;
17use super::tagged::{decode_ctx_real, decode_optional_ctx, expect_end};
18use crate::primitives;
19
20/// The production name the decode errors carry.
21const WHAT: &str = "lighting command";
22
23/// Encode one unframed `BACnetLightingCommand` SEQUENCE: the operation, then
24/// each field that is present.
25pub fn encode_lighting_command(buf: &mut BytesMut, value: &BACnetLightingCommand) {
26    primitives::encode_ctx_enumerated(buf, 0, value.operation.to_raw());
27    let levels = [value.target_level, value.ramp_rate, value.step_increment];
28    for (tag, level) in (1..).zip(levels) {
29        if let Some(level) = level {
30            primitives::encode_ctx_real(buf, tag, level);
31        }
32    }
33    if let Some(fade_time) = value.fade_time {
34        primitives::encode_ctx_unsigned(buf, 4, fade_time.into());
35    }
36    if let Some(priority) = value.priority {
37        primitives::encode_ctx_unsigned(buf, 5, priority.into());
38    }
39}
40
41/// A decoded command, the offset just past its last field, and the first
42/// field too wide for its type, if any.
43pub(super) type Fields = (BACnetLightingCommand, usize, Option<&'static str>);
44
45/// Read the fields of one unframed `BACnetLightingCommand` at `offset`,
46/// stopping at the first tag that isn't the next optional field. A field
47/// too wide for its type is returned rather than refused, so the caller can
48/// check the rest of its input first.
49pub(super) fn decode_fields(data: &[u8], offset: usize) -> Result<Fields, Error> {
50    let mut oversized = None;
51    let (operation, offset) = unsigned_field::<u32>(
52        data,
53        offset,
54        0,
55        WHAT,
56        "operation [0] exceeds 32 bits",
57        &mut oversized,
58    )?;
59    let (target_level, offset) = decode_optional_ctx(data, offset, 1, WHAT, decode_ctx_real)?;
60    let (ramp_rate, offset) = decode_optional_ctx(data, offset, 2, WHAT, decode_ctx_real)?;
61    let (step_increment, offset) = decode_optional_ctx(data, offset, 3, WHAT, decode_ctx_real)?;
62    let (fade_time, offset) = decode_optional_ctx(data, offset, 4, WHAT, |data, at, tag, _| {
63        unsigned_field::<u32>(
64            data,
65            at,
66            tag,
67            WHAT,
68            "fade-time [4] exceeds 32 bits",
69            &mut oversized,
70        )
71    })?;
72    let (priority, offset) = decode_optional_ctx(data, offset, 5, WHAT, |data, at, tag, _| {
73        unsigned_field::<u8>(
74            data,
75            at,
76            tag,
77            WHAT,
78            "priority [5] exceeds an Unsigned8",
79            &mut oversized,
80        )
81    })?;
82    let command = BACnetLightingCommand {
83        operation: LightingOperation::from_raw(operation),
84        target_level,
85        ramp_rate,
86        step_increment,
87        fade_time,
88        priority,
89    };
90    Ok((command, offset, oversized))
91}
92
93/// The [`Error::OutOfRange`] for a field too wide for its type.
94fn too_wide(field: &'static str) -> Error {
95    Error::OutOfRange(format!("{WHAT} {field}"))
96}
97
98/// Decode one unframed `BACnetLightingCommand` SEQUENCE at `offset`.
99///
100/// Returns the command and the offset just past its last field. Decoding
101/// stops at the first tag that isn't the next optional field, so a caller
102/// that expects exactly one command must check that the returned offset
103/// reaches the end of its data, or the closing tag of its frame. A caller
104/// whose whole input must be one command uses
105/// [`decode_lighting_command_value`] instead, which checks that before it
106/// reports a field too wide for its type.
107///
108/// Field values aren't range-checked here; the receiver does that. Failures
109/// come in three kinds:
110///
111/// - contents that run past the end of `data`: [`Error::BufferTooShort`];
112/// - any other malformed field (a missing operation, a REAL not four octets
113///   long, an Unsigned with no contents octets, or one of more than four
114///   that opens with a zero octet): [`Error::Decoding`];
115/// - a well-formed command whose operation or fade time needs more than 32
116///   bits, or whose priority needs more than 8: [`Error::OutOfRange`], naming
117///   the first such field. A malformed field anywhere takes precedence.
118///
119/// Up to four contents octets, an Unsigned or ENUMERATED may open with zero
120/// octets; [`encode_lighting_command`] always writes the shortest form.
121pub fn decode_lighting_command(
122    data: &[u8],
123    offset: usize,
124) -> Result<(BACnetLightingCommand, usize), Error> {
125    let (command, end, oversized) = decode_fields(data, offset)?;
126    match oversized {
127        Some(field) => Err(too_wide(field)),
128        None => Ok((command, end)),
129    }
130}
131
132/// Decode a `BACnetLightingCommand` that fills `data`, as a WriteProperty of
133/// Lighting_Command carries it.
134///
135/// Fails as [`decode_lighting_command`] does, and with [`Error::Decoding`]
136/// when octets follow the last field. A field too wide for its type is
137/// [`Error::OutOfRange`] only when the rest of `data` is one well-formed
138/// command, so a broken input is always reported as broken.
139pub fn decode_lighting_command_value(data: &[u8]) -> Result<BACnetLightingCommand, Error> {
140    let (command, end, oversized) = decode_fields(data, 0)?;
141    expect_end(data, end, end, WHAT)?;
142    match oversized {
143        Some(field) => Err(too_wide(field)),
144        None => Ok(command),
145    }
146}