mod output;
mod poll;
mod poll_reply;
mod timecode;
use crate::{Error, Result};
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
pub use self::output::{Output, PaddedData};
pub use self::poll::Poll;
pub use self::poll_reply::PollReply;
pub use self::timecode::{FrameType, Timecode};
#[derive(Debug)]
pub enum ArtCommand {
Poll(Poll),
PollReply(Box<PollReply>),
DiagData,
Command,
Output(Output),
Nzs,
Sync,
Address,
Input,
TodRequest,
TodData,
TodControl,
Rdm,
RdmSub,
VideoSetup,
VideoPalette,
VideoData,
MacMaster,
MacSlave,
FirmwareMaster,
FirmwareReply,
FileTnMaster,
FileFnMaster,
FileFnReply,
OpIpProg,
OpIpProgReply,
OpMedia,
OpMediaPatch,
OpMediaControl,
OpMediaControlReply,
OpTimeCode(Timecode),
OpTimeSync,
OpTrigger,
OpDirectory,
OpDirectoryReply,
}
pub const ARTNET_HEADER: &[u8; 8] = b"Art-Net\0";
pub const ARTNET_PROTOCOL_VERSION: [u8; 2] = [0, 14];
impl ArtCommand {
pub fn write_to_buffer(self) -> Result<Vec<u8>> {
let mut result = Vec::new();
let (opcode, data) = self.get_opcode()?;
result.extend_from_slice(ARTNET_HEADER);
result
.write_u16::<LittleEndian>(opcode)
.map_err(Error::CursorEof)?;
result.extend_from_slice(&data);
Ok(result)
}
pub fn from_buffer(buffer: &[u8]) -> Result<ArtCommand> {
const MIN_BUFFER_LENGTH: usize = 14;
if buffer.len() < MIN_BUFFER_LENGTH {
return Err(Error::MessageTooShort {
message: buffer.to_vec(),
min_len: MIN_BUFFER_LENGTH,
});
}
if !buffer.starts_with(ARTNET_HEADER) {
return Err(Error::InvalidArtnetHeader(buffer.to_vec()));
}
let opcode = LittleEndian::read_u16(&buffer[8..10]);
let remaining = &buffer[10..];
let command = ArtCommand::opcode_to_enum(opcode, remaining)?;
Ok(command)
}
fn opcode_to_enum(code: u16, data: &[u8]) -> Result<ArtCommand> {
Ok(match code {
0x2000 => ArtCommand::Poll(
Poll::from(data).map_err(|e| Error::OpcodeError("Poll", Box::new(e)))?,
),
0x2100 => ArtCommand::PollReply(Box::new(
PollReply::from(data).map_err(|e| Error::OpcodeError("PollReply", Box::new(e)))?,
)),
0x2300 => ArtCommand::DiagData,
0x2400 => ArtCommand::Command,
0x5000 => ArtCommand::Output(
Output::from(data).map_err(|e| Error::OpcodeError("Output", Box::new(e)))?,
),
0x5100 => ArtCommand::Nzs,
0x5200 => ArtCommand::Sync,
0x6000 => ArtCommand::Address,
0x7000 => ArtCommand::Input,
0x8000 => ArtCommand::TodRequest,
0x8100 => ArtCommand::TodData,
0x8200 => ArtCommand::TodControl,
0x8300 => ArtCommand::Rdm,
0x8400 => ArtCommand::RdmSub,
0xA010 => ArtCommand::VideoSetup,
0xA020 => ArtCommand::VideoPalette,
0xA040 => ArtCommand::VideoData,
0xF000 => ArtCommand::MacMaster,
0xF100 => ArtCommand::MacSlave,
0xF200 => ArtCommand::FirmwareMaster,
0xF300 => ArtCommand::FirmwareReply,
0xF400 => ArtCommand::FileTnMaster,
0xF500 => ArtCommand::FileFnMaster,
0xF600 => ArtCommand::FileFnReply,
0xF800 => ArtCommand::OpIpProg,
0xF900 => ArtCommand::OpIpProgReply,
0x9000 => ArtCommand::OpMedia,
0x9100 => ArtCommand::OpMediaPatch,
0x9200 => ArtCommand::OpMediaControl,
0x9300 => ArtCommand::OpMediaControlReply,
0x9700 => ArtCommand::OpTimeCode(
Timecode::from(data).map_err(|e| Error::OpcodeError("Timecode", Box::new(e)))?,
),
0x9800 => ArtCommand::OpTimeSync,
0x9900 => ArtCommand::OpTrigger,
0x9A00 => ArtCommand::OpDirectory,
0x9B00 => ArtCommand::OpDirectoryReply,
_ => return Err(Error::UnknownOpcode(code)),
})
}
fn get_opcode(&self) -> Result<(u16, Vec<u8>)> {
Ok(match self {
ArtCommand::Poll(poll) => (0x2000, poll.to_bytes()?),
ArtCommand::PollReply(reply) => (0x2100, reply.to_bytes()?),
ArtCommand::DiagData => (0x2300, Vec::new()),
ArtCommand::Command => (0x2400, Vec::new()),
ArtCommand::Output(output) => (0x5000, output.to_bytes()?),
ArtCommand::Nzs => (0x5100, Vec::new()),
ArtCommand::Sync => (0x5200, Vec::new()),
ArtCommand::Address => (0x6000, Vec::new()),
ArtCommand::Input => (0x7000, Vec::new()),
ArtCommand::TodRequest => (0x8000, Vec::new()),
ArtCommand::TodData => (0x8100, Vec::new()),
ArtCommand::TodControl => (0x8200, Vec::new()),
ArtCommand::Rdm => (0x8300, Vec::new()),
ArtCommand::RdmSub => (0x8400, Vec::new()),
ArtCommand::VideoSetup => (0xA010, Vec::new()),
ArtCommand::VideoPalette => (0xA020, Vec::new()),
ArtCommand::VideoData => (0xA040, Vec::new()),
ArtCommand::MacMaster => (0xF000, Vec::new()),
ArtCommand::MacSlave => (0xF100, Vec::new()),
ArtCommand::FirmwareMaster => (0xF200, Vec::new()),
ArtCommand::FirmwareReply => (0xF300, Vec::new()),
ArtCommand::FileTnMaster => (0xF400, Vec::new()),
ArtCommand::FileFnMaster => (0xF500, Vec::new()),
ArtCommand::FileFnReply => (0xF600, Vec::new()),
ArtCommand::OpIpProg => (0xF800, Vec::new()),
ArtCommand::OpIpProgReply => (0xF900, Vec::new()),
ArtCommand::OpMedia => (0x9000, Vec::new()),
ArtCommand::OpMediaPatch => (0x9100, Vec::new()),
ArtCommand::OpMediaControl => (0x9200, Vec::new()),
ArtCommand::OpMediaControlReply => (0x9300, Vec::new()),
ArtCommand::OpTimeCode(timecode) => (0x9700, timecode.to_bytes()?),
ArtCommand::OpTimeSync => (0x9800, Vec::new()),
ArtCommand::OpTrigger => (0x9900, Vec::new()),
ArtCommand::OpDirectory => (0x9A00, Vec::new()),
ArtCommand::OpDirectoryReply => (0x9B00, Vec::new()),
})
}
}