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}