bacnet_encoding/constructed/color_command.rs
1//! Codecs for the colour values of Addendum 135-2020ca (Clause 21):
2//! `BACnetxyColor`, a Color object's Present_Value, Tracking_Value and
3//! Default_Color, and `BACnetColorCommand`, the Color_Command of a Color or
4//! Color Temperature object.
5//!
6//! An xy colour is two application-tagged REALs, x then y, with nothing
7//! around them. The command is a SEQUENCE with no frame of its own: the
8//! operation under primitive context tag 0, then any of five optional fields
9//! in ascending tag order. The target colour sits between an opening and a
10//! closing tag 1; the target colour temperature, fade time, ramp rate and
11//! step increment are Unsigneds under tags 2 to 5.
12
13use bacnet_types::constructed::{BACnetColorCommand, BACnetXyColor};
14use bacnet_types::enums::ColorOperation;
15use bacnet_types::error::{Error, RangeError};
16use bytes::BytesMut;
17
18use super::members::unsigned_field;
19use super::tagged::{
20 decode_app_fixed, decode_optional_ctx, expect_closing, expect_end, expect_opening,
21 next_is_opening,
22};
23use crate::primitives;
24use crate::tags::{self, app_tag};
25
26/// The production name the command's decode errors carry.
27const WHAT: &str = "color command";
28
29/// The production name an xy colour's decode errors carry.
30const XY: &str = "xy color";
31
32/// Encode one `BACnetxyColor`: x, then y, each an application-tagged REAL.
33pub fn encode_xy_color(buf: &mut BytesMut, value: &BACnetXyColor) {
34 primitives::encode_app_real(buf, value.x);
35 primitives::encode_app_real(buf, value.y);
36}
37
38/// Read one application-tagged REAL at `offset` for `what`.
39fn app_real(data: &[u8], offset: usize, what: &'static str) -> Result<(f32, usize), Error> {
40 let (octets, end) = decode_app_fixed(data, offset, app_tag::REAL, 4, what)?;
41 Ok((primitives::decode_real(octets)?, end))
42}
43
44/// Decode one `BACnetxyColor` at `offset`: two application-tagged REALs.
45///
46/// Returns the colour and the offset just past y. Any REAL decodes, NaN and
47/// values outside 0.0 to 1.0 included; the receiver checks the range.
48/// Contents that run past the end of `data` are [`Error::BufferTooShort`];
49/// a missing coordinate, another tag, or a REAL that isn't four octets long
50/// is [`Error::Decoding`].
51pub fn decode_xy_color(data: &[u8], offset: usize) -> Result<(BACnetXyColor, usize), Error> {
52 let (x, offset) = app_real(data, offset, XY)?;
53 let (y, offset) = app_real(data, offset, XY)?;
54 Ok((BACnetXyColor { x, y }, offset))
55}
56
57/// Encode one unframed `BACnetColorCommand` SEQUENCE: the operation, then
58/// each field that is present.
59pub fn encode_color_command(buf: &mut BytesMut, value: &BACnetColorCommand) {
60 primitives::encode_ctx_enumerated(buf, 0, value.operation.to_raw());
61 if let Some(color) = &value.target_color {
62 tags::encode_opening_tag(buf, 1);
63 encode_xy_color(buf, color);
64 tags::encode_closing_tag(buf, 1);
65 }
66 let unsigneds = [
67 value.target_color_temperature,
68 value.fade_time,
69 value.ramp_rate,
70 value.step_increment,
71 ];
72 for (tag, field) in (2..).zip(unsigneds) {
73 if let Some(field) = field {
74 primitives::encode_ctx_unsigned(buf, tag, field.into());
75 }
76 }
77}
78
79/// A decoded command, the offset just past its last field, and the first
80/// field too wide for its type, if any.
81pub(super) type Fields = (BACnetColorCommand, usize, Option<&'static str>);
82
83/// Read the target colour framed in tag 1 at `offset`, if one opens there.
84fn target_color(data: &[u8], offset: usize) -> Result<(Option<BACnetXyColor>, usize), Error> {
85 if !next_is_opening(data, offset, 1)? {
86 return Ok((None, offset));
87 }
88 let content = expect_opening(data, offset, 1, WHAT)?;
89 let (color, end) = decode_xy_color(data, content)?;
90 let end = expect_closing(data, end, 1, WHAT)?;
91 Ok((Some(color), end))
92}
93
94/// Read the fields of one unframed `BACnetColorCommand` at `offset`,
95/// stopping at the first tag that isn't the next optional field. A field
96/// too wide for its type is returned rather than refused, so the caller can
97/// check the rest of its input first.
98pub(super) fn decode_fields(data: &[u8], offset: usize) -> Result<Fields, Error> {
99 let mut oversized = None;
100 let (operation, offset) = unsigned_field::<u32>(
101 data,
102 offset,
103 0,
104 WHAT,
105 "color command operation [0] exceeds 32 bits",
106 &mut oversized,
107 )?;
108 let (target_color, offset) = target_color(data, offset)?;
109 let mut unsigned = |offset: usize, tag: u8, too_wide: &'static str| {
110 decode_optional_ctx(data, offset, tag, WHAT, |data, at, tag, _| {
111 unsigned_field::<u32>(data, at, tag, WHAT, too_wide, &mut oversized)
112 })
113 };
114 let (target_color_temperature, offset) =
115 unsigned(offset, 2, "color command target-color-temperature [2] exceeds 32 bits")?;
116 let (fade_time, offset) = unsigned(offset, 3, "color command fade-time [3] exceeds 32 bits")?;
117 let (ramp_rate, offset) = unsigned(offset, 4, "color command ramp-rate [4] exceeds 32 bits")?;
118 let (step_increment, offset) = unsigned(offset, 5, "color command step-increment [5] exceeds 32 bits")?;
119 let command = BACnetColorCommand {
120 operation: ColorOperation::from_raw(operation),
121 target_color,
122 target_color_temperature,
123 fade_time,
124 ramp_rate,
125 step_increment,
126 };
127 Ok((command, offset, oversized))
128}
129
130/// The [`Error::Range`] for a field too wide for its type, named by its
131/// full label.
132fn too_wide(field: &'static str) -> Error {
133 RangeError::new(field).into()
134}
135
136/// Decode one unframed `BACnetColorCommand` SEQUENCE at `offset`.
137///
138/// Returns the command and the offset just past its last field. Decoding
139/// stops at the first tag that isn't the next optional field, so a caller
140/// that expects exactly one command must check that the returned offset
141/// reaches the end of its data, or the closing tag of its frame. A caller
142/// whose whole input must be one command uses [`decode_color_command_value`]
143/// instead, which checks that before it reports a field too wide for its
144/// type.
145///
146/// Field values aren't range-checked here; the receiver does that. Failures
147/// come in three kinds:
148///
149/// - contents that run past the end of `data`: [`Error::BufferTooShort`];
150/// - any other malformed field (a missing operation, an Unsigned with no
151/// contents octets, or one of more than four that opens with a zero octet,
152/// or a target colour that isn't two four-octet application REALs closed
153/// by tag 1, a frame cut off at a tag boundary included): [`Error::Decoding`];
154/// - a well-formed command whose operation or an Unsigned field needs more
155/// than 32 bits: [`Error::Range`], naming the first such field. A
156/// malformed field anywhere takes precedence.
157///
158/// Up to four contents octets, an Unsigned or ENUMERATED may open with zero
159/// octets; [`encode_color_command`] always writes the shortest form.
160pub fn decode_color_command(
161 data: &[u8],
162 offset: usize,
163) -> Result<(BACnetColorCommand, usize), Error> {
164 let (command, end, oversized) = decode_fields(data, offset)?;
165 match oversized {
166 Some(field) => Err(too_wide(field)),
167 None => Ok((command, end)),
168 }
169}
170
171/// Decode a `BACnetColorCommand` that fills `data`, as a WriteProperty of
172/// Color_Command carries it.
173///
174/// Fails as [`decode_color_command`] does, and with [`Error::Decoding`] when
175/// octets follow the last field. A field too wide for its type is
176/// [`Error::Range`] only when the rest of `data` is one well-formed
177/// command, so a broken input is always reported as broken.
178pub fn decode_color_command_value(data: &[u8]) -> Result<BACnetColorCommand, Error> {
179 let (command, end, oversized) = decode_fields(data, 0)?;
180 expect_end(data, end, end, WHAT)?;
181 match oversized {
182 Some(field) => Err(too_wide(field)),
183 None => Ok(command),
184 }
185}