ether_dream/dac/
mod.rs

1//! A simple abstraction around a single Ether Dream DAC.
2
3pub mod stream;
4
5pub use self::stream::Stream;
6use crate::protocol::{self, command, Command as CommandTrait, ReadFromBytes, WriteToBytes};
7use byteorder;
8use std::error::Error;
9use std::{fmt, io, ops};
10
11/// A DAC along with its broadcasted MAC address.
12///
13/// This type can be used as though it is a `Dac` in many places as it implements
14/// `Deref<Target = Dac>`
15#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
16pub struct Addressed {
17    /// The unique MAC address associated with the DAC.
18    ///
19    /// This may be used to distinguish between multiple DACs broadcasting on a network.
20    pub mac_address: MacAddress,
21    /// The state of the DAC itself.
22    pub dac: Dac,
23}
24
25/// A simple abstraction around a single Ether Dream DAC.
26///
27/// This type monitors the multiple state machines described within the protocol and provides
28/// access to information about the Ether Dream hardware and software implementations.
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
30pub struct Dac {
31    /// This is undocumented in the official protocol but seems to represent a version number for
32    /// the hardware in use by the DAC.
33    pub hw_revision: u16,
34    /// This is undocumented in the official protocol but seems to represent the version of the
35    /// protocol implementation. As of writing this, this is hardcoded as `2` in the original
36    /// source.
37    pub sw_revision: u16,
38    /// The maximum number of points that the DAC may buffer at once.
39    pub buffer_capacity: u16,
40    /// The maximum rate at which the DAC may process buffered points.
41    pub max_point_rate: u32,
42    /// A more rust-esque version of the `ether_dream::protocol::DacState`.
43    ///
44    /// The DAC sends its status with each `DacBroadcast` and `DacResponse`.
45    pub status: Status,
46}
47
48/// The fixed-size array used to represent the MAC address of a DAC.
49///
50/// This may be used to distinguish between multiple DACs broadcasting on a network.
51///
52/// This type implements `std::fmt::Display` which can be used to produce the commonly used
53/// "human-readable" hex-value string representation of the address (e.g. "2A:FE:54:67:8B:D4").
54#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
55pub struct MacAddress(pub [u8; 6]);
56
57/// A more rust-esque version of the `ether_dream::protocol::DacState`.
58///
59/// The DAC sends its status with each `DacBroadcast` and `DacResponse`.
60#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
61pub struct Status {
62    /// This remains undocumented in the protocol.
63    ///
64    /// The original implementation source simply sets this to `0`.
65    pub protocol: u8,
66    /// The current state of the DAC's "light engine" state machine.
67    pub light_engine: LightEngine,
68    /// The current state of the DAC's "playback" state machine.
69    pub playback: Playback,
70    /// The currently-selected data source.
71    pub data_source: DataSource,
72    /// If the light engine is `Ready` no flags will be set.
73    pub light_engine_flags: LightEngineFlags,
74    /// These flags may be non-zero during normal operation.
75    pub playback_flags: PlaybackFlags,
76    /// The number of points currently buffered within the DAC.
77    pub buffer_fullness: u16,
78    /// If in the `Prepared` or `Playing` playback states, this is the number of points per
79    /// second for which the DAC is configured.
80    ///
81    /// If in the `Idle` playback state, this will be `0`.
82    pub point_rate: u32,
83    /// If in the `Playing` playback state, this is the number of points that the DAC has actually
84    /// emitted since it started playing.
85    ///
86    /// If in the `Prepared` or `Idle` playback states, this will be `0`.
87    pub point_count: u32,
88}
89
90/// The light engine state machine - the first of the three primary state machines described within
91/// the protocol.
92#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
93pub enum LightEngine {
94    Ready,
95    /// In the case where the DAC is also used for thermal control of laser apparatus, this is the
96    /// state that is entered after power-up.
97    Warmup,
98    /// Lasers are off but thermal control is still active.
99    Cooldown,
100    /// An emergency stop has been triggered, either by an E-stop input on the DAC, an E-stop
101    /// command over the network, or a fault such as over-temperature.
102    EmergencyStop,
103}
104
105/// The DAC has one playback system, which buffers data and sends it to the analog output hardware
106/// at its current point rate. At any given time, the playback system is connected to a source.
107/// Usually the source is the network streamer, which uses this protocol. However, other sources
108/// exist, such as a built-in abstract generator and file playback from SD card. The playback
109/// system is in one of the following states.
110#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
111pub enum Playback {
112    /// The default state:
113    ///
114    /// - No points may be added to the buffer.
115    /// - No output is generated.
116    /// - All analog outputs are at 0v.
117    /// - The shutter is controlled by the data source.
118    Idle,
119    /// The buffer will accept points.
120    ///
121    /// The output is the same as the `Idle` state
122    Prepared,
123    /// Points are being sent to the output.
124    Playing,
125}
126
127/// The data source in use by a DAC.
128#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
129pub enum DataSource {
130    /// Network streaming (the protocol implemented in this library).
131    NetworkStreaming,
132    /// ILDA playback from an SD card.
133    IldaPlayback(IldaPlaybackFlags),
134    /// The DAC's internal abstract generator.
135    InternalAbstractGenerator(InternalAbstractGeneratorFlags),
136}
137
138bitflags! {
139    /// If the light engine is `Ready`, this will be `0`.
140    pub struct LightEngineFlags: u16 {
141        const EMERGENCY_STOP_PACKET_OR_INVALID_COMMAND = 0b00000001;
142        const EMERGENCY_STOP_PROJECTOR_INPUT = 0b00000010;
143        const EMERGENCY_STOP_PROJECTOR_INPUT_ACTIVE = 0b00000100;
144        const EMERGENCY_STOP_OVER_TEMPERATURE = 0b00001000;
145        const EMERGENCY_STOP_OVER_TEMPERATURE_ACTIVE = 0b00010000;
146        const EMERGENCY_STOP_LOST_ETHERNET_LINK = 0b00100000;
147    }
148}
149
150bitflags! {
151    /// If the light engine is `Ready`, this will be `0`.
152    pub struct PlaybackFlags: u16 {
153        /// Describes the state of the shutter.
154        const SHUTTER_OPEN = 0b00000001;
155        /// Set if the last stream ended with underflow rather than a `Stop`.
156        const UNDERFLOWED = 0b00000010;
157        /// Set if the last stream ended because the E-Stop state was entered.
158        ///
159        /// This is reset to zero by the `Prepare` command.
160        const EMERGENCY_STOP = 0b00000100;
161    }
162}
163
164bitflags! {
165    /// If the data source is ILDA playback via SD, the following flags are used.
166    pub struct IldaPlaybackFlags: u16 {
167        const PLAYING = 0b0;
168        const REPEAT = 0b1;
169    }
170}
171
172bitflags! {
173    /// If the data source is the internal abstract generator, the following flags are used.
174    pub struct InternalAbstractGeneratorFlags: u16 {
175        const PLAYING = 0;
176    }
177}
178
179bitflags! {
180    /// The set of flags used to represent the **control** field of a **DacPoint**.
181    pub struct PointControl: u16 {
182        /// Indicates to the DAC to read a new point rate out of the point rate buffer and set it
183        /// as the current playback rate.
184        ///
185        /// If the buffer is empty, the rate is not changed.
186        const CHANGE_RATE = 0b10000000_00000000;
187    }
188}
189
190/// A command whose kind is determined at runtime.
191///
192/// This is particularly useful when **Read**ing a command from bytes or from a TCP stream. The
193/// **ReadFromBytes** implementation will automatically determine the kind by reading the first
194/// byte.
195#[derive(Clone, Debug, PartialEq, Eq, Hash)]
196pub enum Command<'a> {
197    PrepareStream(command::PrepareStream),
198    Begin(command::Begin),
199    PointRate(command::PointRate),
200    Data(command::Data<'a>),
201    Stop(command::Stop),
202    EmergencyStop(command::EmergencyStop),
203    ClearEmergencyStop(command::ClearEmergencyStop),
204    Ping(command::Ping),
205}
206
207/// An error describing a failure to convert a `protocol::DacStatus` to a `Status`.
208#[derive(Debug)]
209pub enum ProtocolError {
210    UnknownLightEngineState,
211    UnknownPlaybackState,
212    UnknownDataSource,
213}
214
215impl Addressed {
216    /// Create an `Addressed` DAC from a received `DacBroadcast`.
217    pub fn from_broadcast(dac_broadcast: &protocol::DacBroadcast) -> Result<Self, ProtocolError> {
218        let protocol::DacBroadcast {
219            mac_address,
220            hw_revision,
221            sw_revision,
222            buffer_capacity,
223            max_point_rate,
224            dac_status,
225        } = *dac_broadcast;
226        let mac_address = MacAddress(mac_address);
227        let status = Status::from_protocol(&dac_status)?;
228        let dac = Dac {
229            hw_revision,
230            sw_revision,
231            buffer_capacity,
232            max_point_rate,
233            status,
234        };
235        let addressed = Addressed { mac_address, dac };
236        Ok(addressed)
237    }
238}
239
240impl Dac {
241    /// Update the inner status given a new protocol representation.
242    pub fn update_status(&mut self, status: &protocol::DacStatus) -> Result<(), ProtocolError> {
243        self.status.update(status)
244    }
245}
246
247impl Status {
248    /// Create a `Status` from the lower-level protocol representation.
249    pub fn from_protocol(status: &protocol::DacStatus) -> Result<Self, ProtocolError> {
250        let protocol = status.protocol;
251        let light_engine = LightEngine::from_protocol(status.light_engine_state)
252            .ok_or(ProtocolError::UnknownLightEngineState)?;
253        let playback = Playback::from_protocol(status.playback_state)
254            .ok_or(ProtocolError::UnknownPlaybackState)?;
255        let data_source = DataSource::from_protocol(status.source, status.source_flags)
256            .ok_or(ProtocolError::UnknownDataSource)?;
257        let light_engine_flags = LightEngineFlags::from_bits_truncate(status.light_engine_flags);
258        let playback_flags = PlaybackFlags::from_bits_truncate(status.playback_flags);
259        let buffer_fullness = status.buffer_fullness;
260        let point_rate = status.point_rate;
261        let point_count = status.point_count;
262        Ok(Status {
263            protocol,
264            light_engine,
265            playback,
266            data_source,
267            light_engine_flags,
268            playback_flags,
269            buffer_fullness,
270            point_rate,
271            point_count,
272        })
273    }
274
275    /// Update the `Status` from the lower-level protocol representation.
276    pub fn update(&mut self, status: &protocol::DacStatus) -> Result<(), ProtocolError> {
277        self.protocol = status.protocol;
278        self.light_engine = LightEngine::from_protocol(status.light_engine_state)
279            .ok_or(ProtocolError::UnknownLightEngineState)?;
280        self.playback = Playback::from_protocol(status.playback_state)
281            .ok_or(ProtocolError::UnknownPlaybackState)?;
282        self.data_source = DataSource::from_protocol(status.source, status.source_flags)
283            .ok_or(ProtocolError::UnknownDataSource)?;
284        self.light_engine_flags = LightEngineFlags::from_bits_truncate(status.light_engine_flags);
285        self.playback_flags = PlaybackFlags::from_bits_truncate(status.playback_flags);
286        self.buffer_fullness = status.buffer_fullness;
287        self.point_rate = status.point_rate;
288        self.point_count = status.point_count;
289        Ok(())
290    }
291
292    /// Convert the `Status` to its lower-level protocol representation.
293    pub fn to_protocol(&self) -> protocol::DacStatus {
294        let protocol = self.protocol;
295        let light_engine_state = self.light_engine.to_protocol();
296        let playback_state = self.playback.to_protocol();
297        let (source, source_flags) = self.data_source.to_protocol();
298        let light_engine_flags = self.light_engine_flags.bits();
299        let playback_flags = self.playback_flags.bits();
300        let buffer_fullness = self.buffer_fullness;
301        let point_rate = self.point_rate;
302        let point_count = self.point_count;
303        protocol::DacStatus {
304            protocol,
305            light_engine_state,
306            playback_state,
307            source,
308            light_engine_flags,
309            playback_flags,
310            source_flags,
311            buffer_fullness,
312            point_rate,
313            point_count,
314        }
315    }
316}
317
318impl LightEngine {
319    /// Create a `LightEngine` enum from the lower-level protocol representation.
320    ///
321    /// Returns `None` if the given `state` byte is not known.
322    pub fn from_protocol(state: u8) -> Option<Self> {
323        let light_engine = match state {
324            protocol::DacStatus::LIGHT_ENGINE_READY => LightEngine::Ready,
325            protocol::DacStatus::LIGHT_ENGINE_WARMUP => LightEngine::Warmup,
326            protocol::DacStatus::LIGHT_ENGINE_COOLDOWN => LightEngine::Cooldown,
327            protocol::DacStatus::LIGHT_ENGINE_EMERGENCY_STOP => LightEngine::EmergencyStop,
328            _ => return None,
329        };
330        Some(light_engine)
331    }
332
333    /// Convert the `LightEngine` enum to its lower-level protocol representation.
334    pub fn to_protocol(&self) -> u8 {
335        match *self {
336            LightEngine::Ready => protocol::DacStatus::LIGHT_ENGINE_READY,
337            LightEngine::Warmup => protocol::DacStatus::LIGHT_ENGINE_WARMUP,
338            LightEngine::Cooldown => protocol::DacStatus::LIGHT_ENGINE_COOLDOWN,
339            LightEngine::EmergencyStop => protocol::DacStatus::LIGHT_ENGINE_EMERGENCY_STOP,
340        }
341    }
342}
343
344impl Playback {
345    /// Create a `Playback` enum from the lower-level protocol representation.
346    ///
347    /// Returns `None` if the given `state` byte is not known.
348    pub fn from_protocol(state: u8) -> Option<Self> {
349        let playback = match state {
350            protocol::DacStatus::PLAYBACK_IDLE => Playback::Idle,
351            protocol::DacStatus::PLAYBACK_PREPARED => Playback::Prepared,
352            protocol::DacStatus::PLAYBACK_PLAYING => Playback::Playing,
353            _ => return None,
354        };
355        Some(playback)
356    }
357
358    /// Convert the `LightEngine` enum to its lower-level protocol representation.
359    pub fn to_protocol(&self) -> u8 {
360        match *self {
361            Playback::Idle => protocol::DacStatus::PLAYBACK_IDLE,
362            Playback::Prepared => protocol::DacStatus::PLAYBACK_PREPARED,
363            Playback::Playing => protocol::DacStatus::PLAYBACK_PLAYING,
364        }
365    }
366}
367
368impl DataSource {
369    /// Create a `DataSource` enum from the lower-level protocol representation.
370    ///
371    /// Returns `None` if the given `source` byte is not known.
372    pub fn from_protocol(source: u8, flags: u16) -> Option<Self> {
373        match source {
374            protocol::DacStatus::SOURCE_NETWORK_STREAMING => Some(DataSource::NetworkStreaming),
375            protocol::DacStatus::SOURCE_ILDA_PLAYBACK_SD => {
376                Some(IldaPlaybackFlags::from_bits_truncate(flags)).map(DataSource::IldaPlayback)
377            }
378            protocol::DacStatus::SOURCE_INTERNAL_ABSTRACT_GENERATOR => {
379                Some(InternalAbstractGeneratorFlags::from_bits_truncate(flags))
380                    .map(DataSource::InternalAbstractGenerator)
381            }
382            _ => None,
383        }
384    }
385
386    /// Convert the `LightEngine` enum to its lower-level protocol representation.
387    ///
388    /// Returns the `source` and the `source_flags` fields respectively.
389    pub fn to_protocol(&self) -> (u8, u16) {
390        match *self {
391            DataSource::NetworkStreaming => (protocol::DacStatus::SOURCE_NETWORK_STREAMING, 0),
392            DataSource::IldaPlayback(ref flags) => {
393                (protocol::DacStatus::SOURCE_ILDA_PLAYBACK_SD, flags.bits())
394            }
395            DataSource::InternalAbstractGenerator(ref flags) => (
396                protocol::DacStatus::SOURCE_INTERNAL_ABSTRACT_GENERATOR,
397                flags.bits(),
398            ),
399        }
400    }
401}
402
403impl ops::Deref for Addressed {
404    type Target = Dac;
405    fn deref(&self) -> &Self::Target {
406        &self.dac
407    }
408}
409
410impl ops::DerefMut for Addressed {
411    fn deref_mut(&mut self) -> &mut Self::Target {
412        &mut self.dac
413    }
414}
415
416impl From<[u8; 6]> for MacAddress {
417    fn from(bytes: [u8; 6]) -> Self {
418        MacAddress(bytes)
419    }
420}
421
422impl Into<[u8; 6]> for MacAddress {
423    fn into(self) -> [u8; 6] {
424        self.0
425    }
426}
427
428impl ops::Deref for MacAddress {
429    type Target = [u8; 6];
430    fn deref(&self) -> &Self::Target {
431        &self.0
432    }
433}
434
435impl fmt::Display for MacAddress {
436    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437        let a = &self.0;
438        write!(
439            f,
440            "{:X}:{:X}:{:X}:{:X}:{:X}:{:X}",
441            a[0], a[1], a[2], a[3], a[4], a[5]
442        )
443    }
444}
445
446impl ReadFromBytes for Command<'static> {
447    fn read_from_bytes<R: byteorder::ReadBytesExt>(mut reader: R) -> io::Result<Self> {
448        let command = reader.read_u8()?;
449        let kind = match command {
450            command::PrepareStream::START_BYTE => command::PrepareStream.into(),
451            command::Begin::START_BYTE => command::Begin::read_fields(reader)?.into(),
452            command::PointRate::START_BYTE => command::PointRate::read_fields(reader)?.into(),
453            command::Data::START_BYTE => command::Data::read_fields(reader)?.into(),
454            command::Stop::START_BYTE => command::Stop.into(),
455            command::EmergencyStop::START_BYTE => command::EmergencyStop.into(),
456            command::EmergencyStopAlt::START_BYTE => command::EmergencyStop.into(),
457            command::ClearEmergencyStop::START_BYTE => command::ClearEmergencyStop.into(),
458            command::Ping::START_BYTE => command::Ping.into(),
459            unknown => {
460                let err_msg = format!("invalid command byte \"{}\"", unknown);
461                return Err(io::Error::new(io::ErrorKind::InvalidData, err_msg));
462            }
463        };
464        Ok(kind)
465    }
466}
467
468impl<'a> WriteToBytes for Command<'a> {
469    fn write_to_bytes<W: byteorder::WriteBytesExt>(&self, writer: W) -> io::Result<()> {
470        match *self {
471            Command::PrepareStream(ref cmd) => cmd.write_to_bytes(writer),
472            Command::Begin(ref cmd) => cmd.write_to_bytes(writer),
473            Command::PointRate(ref cmd) => cmd.write_to_bytes(writer),
474            Command::Data(ref cmd) => cmd.write_to_bytes(writer),
475            Command::Stop(ref cmd) => cmd.write_to_bytes(writer),
476            Command::EmergencyStop(ref cmd) => cmd.write_to_bytes(writer),
477            Command::ClearEmergencyStop(ref cmd) => cmd.write_to_bytes(writer),
478            Command::Ping(ref cmd) => cmd.write_to_bytes(writer),
479        }
480    }
481}
482
483impl<'a> From<command::PrepareStream> for Command<'a> {
484    fn from(command: command::PrepareStream) -> Self {
485        Command::PrepareStream(command)
486    }
487}
488
489impl<'a> From<command::Begin> for Command<'a> {
490    fn from(command: command::Begin) -> Self {
491        Command::Begin(command)
492    }
493}
494
495impl<'a> From<command::PointRate> for Command<'a> {
496    fn from(command: command::PointRate) -> Self {
497        Command::PointRate(command)
498    }
499}
500
501impl<'a> From<command::Data<'a>> for Command<'a> {
502    fn from(command: command::Data<'a>) -> Self {
503        Command::Data(command)
504    }
505}
506
507impl<'a> From<command::Stop> for Command<'a> {
508    fn from(command: command::Stop) -> Self {
509        Command::Stop(command)
510    }
511}
512
513impl<'a> From<command::EmergencyStop> for Command<'a> {
514    fn from(command: command::EmergencyStop) -> Self {
515        Command::EmergencyStop(command)
516    }
517}
518
519impl<'a> From<command::EmergencyStopAlt> for Command<'a> {
520    fn from(_: command::EmergencyStopAlt) -> Self {
521        Command::EmergencyStop(command::EmergencyStop)
522    }
523}
524
525impl<'a> From<command::ClearEmergencyStop> for Command<'a> {
526    fn from(command: command::ClearEmergencyStop) -> Self {
527        Command::ClearEmergencyStop(command)
528    }
529}
530
531impl<'a> From<command::Ping> for Command<'a> {
532    fn from(command: command::Ping) -> Self {
533        Command::Ping(command)
534    }
535}
536
537impl fmt::Display for ProtocolError {
538    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
539        let s = match *self {
540            ProtocolError::UnknownLightEngineState => "unknown light engine state",
541            ProtocolError::UnknownPlaybackState => "unknown playback state",
542            ProtocolError::UnknownDataSource => "unknown data source",
543        };
544        write!(f, "{}", s)
545    }
546}
547
548impl Error for ProtocolError {}