tiny_artnet/
lib.rs

1#![no_std]
2extern crate tiny_artnet_bytes_no_atomic as bytes;
3
4mod poll_reply;
5pub use poll_reply::PollReply;
6
7use core::ops::RangeInclusive;
8
9use bytes::BufMut;
10use nom::{
11    bytes::complete::tag,
12    number::complete as number,
13    number::complete::{be_u16, le_u16},
14    sequence::tuple,
15    IResult,
16};
17
18const ID: &'static [u8] = b"Art-Net\0";
19pub const PORT: u16 = 0x1936;
20
21const DEFAULT_4_BYTES: &'static [u8; 4] = &[0; 4];
22const DEFAULT_6_BYTES: &'static [u8; 6] = &[0; 6];
23
24#[derive(Debug)]
25pub enum Art<'a> {
26    Poll(Poll),
27    // PollReply(PollReply),
28    Command(Command<'a>),
29    Dmx(Dmx<'a>),
30    Sync,
31}
32
33#[derive(Debug)]
34pub enum Error<'a> {
35    UnsupportedProtocolVersion(u16),
36    UnsupportedOpCode(u16),
37    ParserError(nom::Err<nom::error::Error<&'a [u8]>>),
38}
39
40impl<'a> From<nom::Err<nom::error::Error<&'a [u8]>>> for Error<'a> {
41    fn from(err: nom::Err<nom::error::Error<&'a [u8]>>) -> Self {
42        Error::ParserError(err)
43    }
44}
45
46pub fn from_slice<'a>(s: &'a [u8]) -> Result<Art<'a>, Error<'a>> {
47    // ID
48    let (s, _) = tag(ID)(s)?;
49
50    let (s, op_code) = le_u16(s)?;
51    let (s, protocol_version): (&'a [u8], u16) = be_u16(s)?;
52
53    if protocol_version > 14 {
54        return Err(Error::UnsupportedProtocolVersion(protocol_version));
55    }
56
57    let message = match op_code {
58        0x2000 => Art::Poll(parse_poll(s)?),
59        // poll_reply::OP_POLL_REPLY => Art::PollReply(poll_reply::from_str(s)?),
60        0x2400 => Art::Command(parse_command(s)?),
61        0x5000 => Art::Dmx(parse_dmx(s)?),
62        0x5200 => parse_sync(s).map(|_| Art::Sync)?,
63        _ => return Err(Error::UnsupportedOpCode(op_code)),
64    };
65
66    Ok(message)
67}
68
69/// (ESTAManLo, ESTAManHi)
70pub type ESTAManufacturerCode = (char, char);
71
72fn parse_esta_manufacturer_code<'a>(s: &'a [u8]) -> IResult<&'a [u8], ESTAManufacturerCode> {
73    let (s, (lo, hi)) = tuple((number::u8, number::u8))(s)?;
74    Ok((s, (lo as char, hi as char)))
75}
76
77pub fn put_esta_manufacturer_code<B: BufMut>(
78    buf: &mut B,
79    manufacturer_code: &ESTAManufacturerCode,
80) {
81    buf.put_u8(manufacturer_code.0 as u8);
82    buf.put_u8(manufacturer_code.1 as u8);
83}
84
85/// One of the 32,768 possible addresses to which a DMX frame can be
86/// directed. The Port-Address is a 15-bit number composed of Net+Sub-Net+Universe.
87///
88/// Bits:
89///     | 15 | 8-14 | 4-7    | 0-3      |
90///     | 0  | Net  | SubNet | Universe |
91#[derive(Debug)]
92pub struct PortAddress {
93    pub net: u8,
94    pub sub_net: u8,
95    pub universe: u8,
96}
97
98fn parse_port_address<'a>(s: &'a [u8]) -> IResult<&'a [u8], PortAddress> {
99    use nom::bits::complete as bits;
100
101    let (s, (sub_net, universe, _, net)): (&[u8], (u8, u8, u8, u8)) = nom::bits::bits(tuple((
102        // Low Byte (SubUni)
103        bits::take::<&[u8], u8, usize, nom::error::Error<(&[u8], usize)>>(4usize),
104        bits::take(4usize),
105        // High Byte (Net)
106        bits::take(1usize),
107        bits::take(7usize),
108    )))(s)?;
109
110    let port_address = PortAddress {
111        net,
112        sub_net,
113        universe,
114    };
115
116    Ok((s, port_address))
117}
118
119impl PortAddress {
120    /// Combines the Net, SubNet and Universe into a single usize index. Note this is not the same as the little endian u16 sent over the wire.
121    pub fn as_index(&self) -> usize {
122        (self.net as usize >> 14) + (self.sub_net as usize >> 7) + (self.universe as usize)
123    }
124}
125
126// Appends a Nul terminated ASCII string truncated (or padded) to N bytes
127fn put_padded_str<const N: usize, B: BufMut>(mut buf: B, input: &str) {
128    let mut padded_bytes = [0; N];
129
130    let bytes = input.as_bytes();
131    // Truncate to N minus 1 to leave 1 byte for the NUL character
132    let truncated_bytes = if bytes.len() > N - 1 {
133        &bytes[..N - 1]
134    } else {
135        &bytes[..]
136    };
137
138    // Put the truncated bytes into the padded buffer - this guarentees that the length is always N
139    (&mut padded_bytes[..]).put_slice(truncated_bytes);
140
141    buf.put_slice(&padded_bytes);
142}
143
144#[derive(Debug)]
145pub struct Poll {
146    pub flags: u8,
147    pub min_diagnostic_priority: u8,
148    pub target_port_addresses: RangeInclusive<u16>,
149}
150
151fn parse_poll<'a>(s: &'a [u8]) -> Result<Poll, Error<'a>> {
152    let (s, flags) = number::u8(s)?;
153    let (s, min_diagnostic_priority) = number::u8(s)?;
154
155    let target_port_addresses = if !s.is_empty() {
156        let (s, target_port_top): (&'a [u8], u16) = be_u16(s)?;
157        let (_s, target_port_bottom): (&'a [u8], u16) = be_u16(s)?;
158
159        target_port_top..=target_port_bottom
160    } else {
161        0..=u16::MAX
162    };
163
164    Ok(Poll {
165        flags,
166        min_diagnostic_priority,
167        target_port_addresses,
168    })
169}
170
171#[derive(Debug)]
172pub struct Command<'a> {
173    pub esta_manufacturer_code: ESTAManufacturerCode,
174    pub data: &'a [u8],
175}
176
177fn parse_command<'a>(s: &'a [u8]) -> Result<Command<'a>, Error<'a>> {
178    let (s, esta_manufacturer_code) = parse_esta_manufacturer_code(s)?;
179    let (s, length): (&'a [u8], u16) = le_u16(s)?;
180
181    let data = &s[..length as usize];
182
183    Ok(Command {
184        esta_manufacturer_code,
185        data,
186    })
187}
188
189#[derive(Debug)]
190pub struct Dmx<'a> {
191    /// The sequence number is used to ensure that
192    /// ArtDmx packets are used in the correct order.
193    /// When Art-Net is carried over a medium such as
194    /// the Internet, it is possible that ArtDmx packets
195    /// will reach the receiver out of order.
196    ///
197    /// This field is incremented in the range 0x01 to
198    /// 0xff to allow the receiving node to re-sequence
199    /// packets.
200    ///
201    /// The Sequence field is set to 0x00 to disable this
202    /// feature.
203    pub sequence: u8,
204    /// The physical input port from which DMX512
205    /// data was input. This field is used by the
206    /// receiving device to discriminate between
207    /// packets with identical Port-Address that have
208    /// been generated by different input ports and so
209    /// need to be merged.
210    pub physical: u8,
211    ///  one of the 32,768 possible addresses to which a DMX frame can be
212    /// directed. The Port-Address is a 15-bit number composed of Net+Sub-Net+Universe.
213    ///
214    /// Bits:
215    ///     | 15 | 8-14 | 4-7    | 0-3      |
216    ///     | 0  | Net  | SubNet | Universe |
217    pub port_address: PortAddress,
218    pub data: &'a [u8],
219}
220
221fn parse_dmx<'a>(s: &'a [u8]) -> Result<Dmx<'a>, Error<'a>> {
222    let (s, sequence) = number::u8(s)?;
223    let (s, physical) = number::u8(s)?;
224    let (s, port_address) = parse_port_address(s)?;
225
226    let (s, length): (&'a [u8], u16) = be_u16(s)?;
227
228    let data = &s[..length as usize];
229
230    Ok(Dmx {
231        sequence,
232        physical,
233        port_address,
234        data,
235    })
236}
237
238fn parse_sync<'a>(s: &'a [u8]) -> Result<(), Error<'a>> {
239    let (s, _aux1) = number::u8(s)?;
240    let (_s, _aux2) = number::u8(s)?;
241
242    Ok(())
243}