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#[typetag::serde(tag = "type")]
15pub trait DmxPort: fmt::Display {
16 fn available_ports() -> anyhow::Result<PortListing>
18 where
19 Self: Sized;
20
21 fn open(&mut self) -> Result<(), OpenError>;
25
26 fn close(&mut self);
28
29 fn write(&mut self, frame: &[u8]) -> Result<(), WriteError>;
33}
34
35type PortListing = Vec<Box<dyn DmxPort>>;
37
38pub 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
48pub 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
76fn 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}