open_dmx/
dmx_serial.rs

1
2#[cfg(feature = "thread_priority")]
3use thread_priority;
4
5use crate::thread::*;
6use crate::check_valid_channel;
7use crate::error::{DMXDisconnectionError, DMXChannelValidityError};
8use crate::DMX_CHANNELS;
9
10use serialport::SerialPort;
11
12use std::time;
13use std::io::Write;
14use std::thread;
15use std::sync::mpsc;
16
17// Sleep duration between sending the break and the data
18const TIME_BREAK_TO_DATA: time::Duration = time::Duration::new(0, 136_000);
19
20/// A [DMX-Interface] which writes to the [SerialPort] independently from the main thread.
21/// 
22/// [DMX-Interface]: DMXSerial
23/// 
24/// It uses the RS-485 standard *(aka. Open DMX)* to send **DMX data** over a [SerialPort]. 
25/// 
26/// [SerialPort]: serialport::SerialPort
27///
28#[derive(Debug)]
29pub struct DMXSerial {
30    
31    name: String,
32    // Array of DMX-Values which are written to the Serial-Port
33    channels: ArcRwLock<[u8; DMX_CHANNELS]>,
34    // Connection to the Agent-Thread, if this is dropped the Agent-Thread will stop
35    agent: AgentCommunication::<()>,
36
37    // Mode
38    is_sync: ArcRwLock<bool>,
39
40    min_time_break_to_break: ArcRwLock<time::Duration>,
41
42}
43
44impl DMXSerial {
45    /// Opens a new [DMX-Interface] on the given [`path`]. Returns an [DMXError] if the port could not be opened.
46    /// 
47    /// The [`path`] should look something like this:
48    /// 
49    /// - **Windows**: `COM3`
50    /// - **Linux**: `/dev/ttyUSB0`
51    /// 
52    /// [DMX-Interface]: DMXSerial
53    /// [`path`]: std::str
54    /// 
55    /// <br>
56    /// 
57    ///  The interface can be set to **synchronous** or **asynchronous** mode *(default)*. 
58    /// 
59    /// In **synchronous** mode, no `data` will be sent to the [SerialPort] unti [`DMXSerial::update()`] is called.
60    /// If updates are not sent regularly in **synchronous** mode, DMX-Devices might not react to the changes.
61    /// 
62    /// In **asynchronous** mode, the `data` will be polled automatically to the [SerialPort].
63    /// 
64    /// 
65    /// [`set functions`]: DMXSerial::set_channel
66    /// [SerialPort]: serialport::SerialPort
67    /// 
68    /// # Example
69    /// 
70    /// Basic usage:
71    /// 
72    /// ```
73    /// use open_dmx::DMXSerial;
74    /// 
75    /// fn main() {
76    ///    let mut dmx = DMXSerial::open("COM3").unwrap();
77    ///   dmx.set_channels([255; 512]);
78    ///   dmx.set_channel(1, 0).unwrap();
79    /// }
80    /// ```
81    /// 
82    pub fn open(port: &str) -> Result<DMXSerial, serialport::Error> {
83
84        let (handler, agent_rx) = mpsc::sync_channel(0);
85        let (agent_tx, handler_rec) = mpsc::channel();
86
87        // channel default created here!
88        let dmx = DMXSerial {
89            name: port.to_string(),
90            channels: ArcRwLock::new([0; DMX_CHANNELS]),
91            agent: AgentCommunication::new(agent_tx, agent_rx),
92            is_sync: ArcRwLock::new(false),
93            min_time_break_to_break: ArcRwLock::new(time::Duration::from_micros(22_700))};
94
95        let mut agent = DMXSerialAgent::open(&port, dmx.min_time_break_to_break.read_only())?;
96        let channel_view = dmx.channels.read_only();
97        let is_sync_view = dmx.is_sync.read_only();
98        let _ = thread::spawn(move || {
99                #[cfg(feature = "thread_priority")]
100                thread_priority::set_current_thread_priority(thread_priority::ThreadPriority::Max).unwrap_or_else(|e| {
101                    eprintln!("Failed to set thread priority: \"{:?}\". Continuing anyways...", e)
102                });
103                loop {
104                    // This can be unwrapped since the values can't be dropped while the thread is running
105                    if is_sync_view.read().unwrap().clone() {
106                        if handler_rec.recv().is_err() {
107                            // If the channel is dropped by the other side, the thread will stop
108                            break;
109                        }
110                    }
111
112                    let channels = channel_view.read().unwrap().clone();
113
114                    // If an error occurs, the thread will stop
115                    if let Err(_) = agent.send_dmx_packet(channels) {
116                        break;
117                    }
118
119                    //If the channel is dropped by the other side, the thread will stop
120                    if let Err(mpsc::TrySendError::Disconnected(_)) = handler.try_send(()) {
121                        break;
122                    }
123                }
124        });
125        Ok(dmx)
126    }
127
128    /// Does the same as [`DMXSerial::open`] but sets the [DMXSerial] to **sync mode**.
129    /// 
130    /// # Example
131    /// 
132    /// Basic strobe effect:
133    /// 
134    /// ```
135    /// use open_dmx::DMXSerial;
136    /// fn main() {
137    ///     let mut dmx = DMXSerial::open_sync("COM3").unwrap();
138    ///     //strobe
139    ///     loop {
140    ///         dmx.set_channels([255; 512]);
141    ///         dmx.update(); //returns once the data is sent
142    ///         dmx.set_channels([0; 512]);
143    ///         dmx.update();
144    ///     }
145    /// }
146    pub fn open_sync(port: &str) -> Result<DMXSerial, serialport::Error> {
147        let mut dmx = DMXSerial::open(port)?;
148        dmx.set_sync();
149        Ok(dmx)
150    }
151
152    /// Reopens the [DMXSerial] on the same [`path`].
153    /// 
154    /// It keeps the current [`channel`] values.
155    ///
156    /// [`path`]: std::str
157    /// [`channel`]: usize
158    ///
159    pub fn reopen(&mut self) -> Result<(), serialport::Error> {
160        let channels = self.get_channels();
161        let new_dmx = DMXSerial::open(&self.name)?;
162        *self = new_dmx;
163        self.set_channels(channels);
164        Ok(())
165    }
166    /// Gets the name of the Path on which the [DMXSerial] is opened.
167    /// 
168    ///  # Example
169    /// 
170    /// Basic usage:
171    /// 
172    /// ```
173    /// # use open_dmx::DMXSerial;
174    /// # fn main() {
175    /// let mut dmx = DMXSerial::open("COM3").unwrap();
176    /// assert_eq!(dmx.name(), "COM3");
177    /// # }
178    /// ```
179    ///     
180    pub fn name(&self) -> &str {
181        &self.name
182    }
183
184    /// Sets the specified [`channel`] to the given [`value`].
185    /// 
186    /// [`channel`]: usize
187    /// [`value`]: u8
188    /// 
189    /// # Example
190    /// 
191    /// Basic usage:
192    /// 
193    /// ```
194    /// # use open_dmx::DMXSerial;
195    /// # fn main() {
196    /// # let mut dmx = DMXSerial::open("COM3").unwrap();
197    /// dmx.set_channel(1, 255); //sets the first channel to 255
198    /// # }
199    /// ```
200    /// 
201    pub fn set_channel(&mut self, channel: usize, value: u8) -> Result<(), DMXChannelValidityError> {
202        check_valid_channel(channel)?;
203        // RwLock can be unwrapped here
204        let mut channels = self.channels.write().unwrap();
205        channels[channel - 1] = value;
206        Ok(())
207    }
208
209    /// Sets all channels to the given [`value`] via a array of size [`DMX_CHANNELS`].
210    /// 
211    /// [`value`]: u8
212    /// 
213    /// # Example
214    /// 
215    /// Checkerboard effect:
216    /// 
217    /// ```
218    /// # use open_dmx::{DMXSerial, DMX_CHANNELS};
219    /// # fn main() {
220    ///    let mut dmx = DMXSerial::open("COM3").unwrap();
221    ///    let mut channels = [0; DMX_CHANNELS];
222    ///    channels.iter_mut().enumerate().for_each(|(i, value)| *value = if i % 2 == 0 { 255 } else { 0 });
223    ///    dmx.set_channels(channels);
224    ///  # }
225    /// ```
226    /// 
227    pub fn set_channels(&mut self, channels: [u8; DMX_CHANNELS]) {
228        // RwLock can be unwrapped here
229        *self.channels.write().unwrap() = channels;
230    }
231
232    /// Tries to get the [`value`] of the specified [`channel`].
233    /// 
234    /// [`channel`]: usize
235    /// [`value`]: u8
236    /// 
237    /// Returns [`DMXError::NotValid`] if the given [`channel`] is not in the range of [`DMX_CHANNELS`].
238    /// 
239    /// # Example
240    /// 
241    /// Basic usage:
242    /// 
243    /// ```
244    /// # use open_dmx::DMXSerial;
245    /// # fn main() {
246    /// # let mut dmx = DMXSerial::open("COM3").unwrap();
247    /// dmx.set_channel(1, 255).unwrap();
248    /// assert_eq!(dmx.get_channel(1).unwrap(), 255);
249    /// # }
250    /// ```
251    /// 
252    pub fn get_channel(&self, channel: usize) -> Result<u8, DMXChannelValidityError> {
253        check_valid_channel(channel)?;
254        // RwLock can be unwrapped here
255        let channels = self.channels.read().unwrap();
256        Ok(channels[channel - 1])
257    }
258
259    /// Returns the [`value`] of all channels via a array of size [`DMX_CHANNELS`].
260    /// 
261    /// [`value`]: u8
262    /// 
263    /// # Example
264    /// 
265    /// Basic usage:
266    /// 
267    /// ```
268    /// # use open_dmx::{DMXSerial, DMX_CHANNELS};
269    /// # fn main() {
270    /// # let mut dmx = DMXSerial::open("COM3").unwrap();
271    /// dmx.set_channels([255; DMX_CHANNELS]).unwrap();
272    /// assert_eq!(dmx.get_channels(), [255; DMX_CHANNELS]);
273    /// # }
274    /// 
275    pub fn get_channels(&self) -> [u8; DMX_CHANNELS] {
276        // RwLock can be unwrapped here
277        self.channels.read().unwrap().clone()
278    }
279
280    /// Resets all channels to `0`.
281    ///     
282    /// # Example
283    /// 
284    /// Basic usage:
285    /// 
286    /// ```
287    /// # use open_dmx::{DMXSerial, DMX_CHANNELS};
288    /// # fn main() {
289    /// # let mut dmx = DMXSerial::open("COM3").unwrap();
290    /// dmx.set_channels([255; DMX_CHANNELS]).unwrap();
291    /// assert_eq!(dmx.get_channels(), [255; DMX_CHANNELS]);
292    /// dmx.reset_channels();
293    /// assert_eq!(dmx.get_channels(), [0; DMX_CHANNELS]);
294    /// # }
295    /// ```
296    /// 
297    pub fn reset_channels(&mut self) {
298        // RwLock can be unwrapped here
299        self.channels.write().unwrap().fill(0);
300    }
301
302    fn wait_for_update(&self) -> Result<(), DMXDisconnectionError> {
303        self.agent.rx.recv().map_err(|_| DMXDisconnectionError)?;
304        Ok(())
305    }
306    
307    /// Updates the DMX data.
308    /// 
309    /// Returns after the data has been sent.
310    /// 
311    /// Works both in **sync** and **async** mode.
312    /// 
313    /// # Example
314    /// 
315    /// [Basic Usage]
316    /// 
317    /// [Basic Usage]: #example-1
318    /// 
319    pub fn update(&mut self) -> Result<(), DMXDisconnectionError> {
320        self.update_async()?;
321        self.wait_for_update().map_err(|_| DMXDisconnectionError)?;
322        Ok(())
323    }
324
325    /// Updates the DMX data but returns immediately.
326    /// 
327    /// Useless in **async** mode.
328    /// 
329    pub fn update_async(&self) -> Result<(), DMXDisconnectionError> {
330        self.agent.tx.send(()).map_err(|_| DMXDisconnectionError)?;
331        Ok(())
332    }
333
334    /// Sets the DMX mode to **sync**.
335    /// 
336    pub fn set_sync(&mut self) {
337        // RwLock can be unwrapped here
338        *self.is_sync.write().unwrap() = true;
339    }
340
341    /// Sets the DMX mode to **async**.
342    ///     
343    pub fn set_async(&mut self) {
344        // RwLock can be unwrapped here
345        *self.is_sync.write().unwrap() = false;
346    }
347
348    /// Returns `true` if the DMX mode is **sync**.
349    ///     
350    pub fn is_sync(&self) -> bool {
351        // RwLock can be unwrapped here
352        self.is_sync.read().unwrap().clone()
353    }
354
355    /// Returns `true` if the DMX mode is **async**.
356    /// 
357    pub fn is_async(&self) -> bool {
358        !self.is_sync()
359    }
360
361    /// Sets the minimum [`Duration`] between two **DMX packets**.
362    /// 
363    /// [`Duration`]: time::Duration
364    /// 
365    /// # Default
366    /// 
367    /// - 22.7 ms
368    /// 
369    /// <br>
370    /// 
371    /// Some devices may require a longer time between two **packets**.
372    /// 
373    /// See the [DMX512-Standard] for timing.
374    /// 
375    /// [DMX512-Standard]: https://www.erwinrol.com/page/articles/dmx512/
376    pub fn set_packet_time(&mut self, time: time::Duration) {
377        // RwLock can be unwrapped here
378        self.min_time_break_to_break.write().unwrap().clone_from(&time);
379    }
380
381    /// Returns the minimum [`Duration`] between two **DMX packets**.
382    /// 
383    /// [`Duration`]: time::Duration
384    /// 
385    pub fn get_packet_time(&self) -> time::Duration {
386        // RwLock can be unwrapped here
387        self.min_time_break_to_break.read().unwrap().clone()
388    }
389
390    /// Checks if the [`DMXSerial`] device is still connected.
391    ///
392    /// # Example
393    /// 
394    /// Basic usage:
395    /// 
396    /// ```
397    /// # use open_dmx::DMXSerial;
398    /// # fn main() {
399    /// # let mut dmx = DMXSerial::open("COM3").unwrap();
400    /// assert!(dmx.check_agent().is_ok()); // If not, the device got disconnected
401    /// # }
402    pub fn check_agent(&self) -> Result<(), DMXDisconnectionError> {
403        if let Err(mpsc::TryRecvError::Disconnected) = self.agent.rx.try_recv() {
404            return Err(DMXDisconnectionError);
405        }
406        Ok(())
407    }
408}
409
410#[derive(Debug)]
411struct AgentCommunication<T> {
412    pub tx: mpsc::Sender<T>,
413    pub rx: mpsc::Receiver<T>,
414}
415
416impl<T> AgentCommunication<T> {
417    pub fn new(tx: mpsc::Sender<T>, rx: mpsc::Receiver<T>) -> AgentCommunication<T> {
418        AgentCommunication {
419            tx,
420            rx,
421        }
422    }
423}
424
425struct DMXSerialAgent {
426    port: Box<dyn SerialPort>,
427    min_b2b: ReadOnly<time::Duration>,
428}
429
430impl DMXSerialAgent {
431
432    pub fn open (port: &str, min_b2b: ReadOnly<time::Duration>) -> Result<DMXSerialAgent, serialport::Error> {
433        let port = serialport::new(port, 250000)
434        .data_bits(serialport::DataBits::Eight)
435        .stop_bits(serialport::StopBits::Two)
436        .parity(serialport::Parity::None)
437        .flow_control(serialport::FlowControl::None)
438        .open()?;
439        let dmx = DMXSerialAgent {
440            port,
441            min_b2b,
442        };
443        Ok(dmx)
444    }
445
446    fn send_data(&mut self, data: &[u8]) -> serialport::Result<()> {
447        self.port.write(data)?;
448        Ok(())
449    }
450    
451    pub fn send_dmx_packet(&mut self, channels: [u8; DMX_CHANNELS]) -> serialport::Result<()> {
452        let start = time::Instant::now();
453        self.port.set_break()?;
454        thread::sleep(TIME_BREAK_TO_DATA);
455        self.port.clear_break()?;
456        let mut prefixed_data = [0; 513];// 1 start byte + 512 channels
457        prefixed_data[1..].copy_from_slice(&channels);
458        self.send_data(&prefixed_data)?;
459
460        thread::sleep(self.min_b2b.read().unwrap().saturating_sub(start.elapsed()));
461
462        Ok(())
463    }
464}