rust_dmx/
lib.rs

1use io::Write;
2use std::fmt;
3use std::io;
4use thiserror::Error;
5
6mod enttec;
7mod offline;
8
9pub use enttec::EnttecDmxPort;
10pub use offline::OfflineDmxPort;
11
12/// Trait for the general notion of a DMX port.
13/// This enables creation of an "offline" port to slot into place if an API requires an output.
14#[typetag::serde(tag = "type")]
15pub trait DmxPort: fmt::Display {
16    /// Return the available ports.  The ports will need to be opened before use.
17    fn available_ports() -> anyhow::Result<PortListing>
18    where
19        Self: Sized;
20
21    /// Open the port for writing.  Implementations should no-op if this is
22    /// called twice rather than returning an error.  Primarily used to re-open
23    /// a port that has be deserialized.
24    fn open(&mut self) -> Result<(), OpenError>;
25
26    /// Close the port.
27    fn close(&mut self);
28
29    /// Write a DMX frame out to the port.  If the frame is smaller than the minimum universe size,
30    /// it will be padded with zeros.  If the frame is larger than the maximum universe size, the
31    /// values beyond the max size will be ignored.
32    fn write(&mut self, frame: &[u8]) -> Result<(), WriteError>;
33}
34
35/// A listing of available ports.
36type PortListing = Vec<Box<dyn DmxPort>>;
37
38/// Gather up all of the providers and use them to get listings of all ports they have available.
39/// Return them as a vector of names plus opener functions.
40/// This function does not check whether or not any of the ports are in use already.
41pub fn available_ports() -> anyhow::Result<PortListing> {
42    let mut ports = Vec::new();
43    ports.extend(OfflineDmxPort::available_ports()?);
44    ports.extend(EnttecDmxPort::available_ports()?);
45    Ok(ports)
46}
47
48/// Prompt the user to select a port via the command prompt.
49pub fn select_port() -> anyhow::Result<Box<dyn DmxPort>> {
50    let mut ports = available_ports()?;
51    println!("Available DMX ports:");
52    for (i, port) in ports.iter().enumerate() {
53        println!("{}: {}", i, port);
54    }
55    let mut port = loop {
56        print!("Select a port: ");
57        io::stdout().flush()?;
58        let input = read_string()?;
59        let index = match input.trim().parse::<usize>() {
60            Ok(num) => num,
61            Err(e) => {
62                println!("{}; please enter an integer.", e);
63                continue;
64            }
65        };
66        if index >= ports.len() {
67            println!("Please enter a value less than {}.", ports.len());
68            continue;
69        }
70        break ports.swap_remove(index);
71    };
72    port.open()?;
73    Ok(port)
74}
75
76/// Read a line of input from stdin.
77fn read_string() -> Result<String, io::Error> {
78    let mut line = String::new();
79    io::stdin().read_line(&mut line)?;
80    Ok(line.trim().to_string())
81}
82
83#[derive(Error, Debug)]
84pub enum OpenError {
85    #[error("the DMX port is not connected")]
86    NotConnected,
87    #[error(transparent)]
88    Other(#[from] anyhow::Error),
89}
90
91#[derive(Error, Debug)]
92pub enum WriteError {
93    #[error("the DMX port is not connected")]
94    Disconnected,
95    #[error(transparent)]
96    Other(#[from] anyhow::Error),
97}