Skip to main content

dmx_rdm_ftdi/
lib.rs

1//! Library for using ftdi uarts, connected to rs485 transceivers with the [dmx-rdm-rs](https://crates.io/crates/dmx-rdm)
2//! library. Most usb rs485 cables using a ftdi chipset will work. There are even once
3//! that already have the XLR-connector attached to them.
4//!
5//! <div class="warning">This driver won't work with Enttec OpenDMX or Enttec DMX Pro devices.
6//! Refer to the readme for more details.</div>
7
8use dmx_rdm::consts::{DMX_BAUD, INTER_SLOT_TIME_MILLIS};
9use dmx_rdm::dmx_uart_driver::{
10    DmxRecvUartDriver, DmxRespUartDriver, DmxUartDriver, DmxUartDriverError,
11};
12use libftd2xx::{BitsPerWord, FtStatus, Ftdi, FtdiCommon, Parity, StopBits};
13use std::time::{Duration, SystemTime};
14
15pub struct FtdiDriverConfig {
16    /// In order to comply with the standard this value has to be set to 2ms.
17    /// This is extremely cpu intensive. Most of the time lower rates will suffice but be careful.
18    pub latency_timer: Duration,
19}
20
21impl Default for FtdiDriverConfig {
22    fn default() -> Self {
23        Self {
24            // this puts a lot of work on the kernel but complies with the standard
25            latency_timer: Duration::from_millis(2),
26        }
27    }
28}
29
30pub struct FtdiDriver {
31    serial_port: Ftdi,
32    latency_timer_us: u32,
33}
34
35impl FtdiDriver {
36    pub fn new(mut serial_port: Ftdi, config: FtdiDriverConfig) -> Result<Self, FtStatus> {
37        serial_port.set_baud_rate(DMX_BAUD)?;
38        serial_port.set_data_characteristics(BitsPerWord::Bits8, StopBits::Bits2, Parity::No)?;
39        serial_port.set_flow_control_none()?;
40        serial_port.set_timeouts(
41            Duration::from_millis(INTER_SLOT_TIME_MILLIS as u64),
42            Duration::from_secs(1),
43        )?;
44        serial_port.set_latency_timer(config.latency_timer)?;
45
46        Ok(Self {
47            serial_port,
48            latency_timer_us: config.latency_timer.as_micros() as u32,
49        })
50    }
51
52    fn begin_package(&mut self) -> Result<(), FtStatus> {
53        while self.serial_port.status()?.ammount_in_tx_queue != 0 {}
54
55        spin_sleep::sleep(Duration::from_millis(50));
56
57        self.serial_port.set_break_on()?;
58        self.serial_port.set_break_off()?;
59        // no sleeps since clearing the break already takes long enough 😉
60
61        Ok(())
62    }
63
64    fn check_timeout(&self, requested_timeout_us: u32) -> u32 {
65        // Bypassing the timer check, because we are expecting to be in the middle of a package.
66        if requested_timeout_us == 0 {
67            return 0;
68        }
69
70        if requested_timeout_us < self.latency_timer_us {
71            #[cfg(feature = "log")]
72            log::warn!("Requested timeout ({}µs) is shorter then latency timer ({}µs). This will cause timing issues. Using latency timer to prevent errors.", requested_timeout_us, self.latency_timer_us);
73
74            return self.latency_timer_us;
75        }
76
77        requested_timeout_us
78    }
79}
80
81impl DmxUartDriver for FtdiDriver {
82    type DriverError = FtStatus;
83}
84
85impl DmxRespUartDriver for FtdiDriver {
86    fn write_frames(
87        &mut self,
88        buffer: &[u8],
89    ) -> Result<usize, DmxUartDriverError<Self::DriverError>> {
90        self.begin_package()?;
91        self.write_frames_no_break(buffer)
92    }
93
94    fn write_frames_no_break(
95        &mut self,
96        buffer: &[u8],
97    ) -> Result<usize, DmxUartDriverError<Self::DriverError>> {
98        Ok(self.serial_port.write(buffer)?)
99    }
100}
101
102impl DmxRecvUartDriver for FtdiDriver {
103    fn read_frames(
104        &mut self,
105        buffer: &mut [u8],
106        timeout_us: u32,
107    ) -> Result<usize, DmxUartDriverError<Self::DriverError>> {
108        // for some bizarre reason a break shows up as a single 0x00 byte
109        let start_time = SystemTime::now();
110        let mut break_byte = [0xFFu8; 1];
111
112        let actual_timeout = self.check_timeout(timeout_us);
113
114        while start_time.elapsed().unwrap().as_micros() < actual_timeout as u128 {
115            let bytes_read = self.serial_port.read(&mut break_byte)?;
116            if bytes_read != 0 && break_byte[0] == 0 {
117                return self.read_frames_no_break(buffer, 1);
118            }
119        }
120
121        Err(DmxUartDriverError::TimeoutError)
122    }
123
124    fn read_frames_no_break(
125        &mut self,
126        buffer: &mut [u8],
127        timeout_us: u32,
128    ) -> Result<usize, DmxUartDriverError<Self::DriverError>> {
129        let buffer_size = buffer.len();
130        let mut head = 0;
131
132        let actual_timeout_us = self.check_timeout(timeout_us);
133
134        let mut slot_start = SystemTime::now();
135        while head < buffer_size {
136            let bytes_read = self.serial_port.read(&mut buffer[head..])?;
137            head += bytes_read;
138
139            if head == 0 {
140                if slot_start.elapsed().unwrap().as_micros() < actual_timeout_us as u128 {
141                    continue;
142                }
143
144                return Err(DmxUartDriverError::TimeoutError);
145            }
146
147            if bytes_read > 0 {
148                slot_start = SystemTime::now();
149            } else if slot_start.elapsed().unwrap().as_millis() >= INTER_SLOT_TIME_MILLIS as u128 {
150                break;
151            }
152        }
153
154        Ok(head)
155    }
156}