Skip to main content

ydlidar_rust_driver/
types.rs

1use crate::cmd::TMiniPlusCmd;
2
3/// # Example
4///
5/// ```
6/// use ydlidar::types::{LidarCommands, TMiniPlus};
7///
8/// let start_cmd = TMiniPlus::start_scan_cmd();
9/// assert_eq!(start_cmd, &[0xA5, 0x60]);
10/// ```
11pub trait LidarCommands {
12    /// The header structure for this LIDAR model's data packets.
13    type Header;
14
15    /// Get the command to start scanning.
16    ///
17    /// Returns a reference to the command byte array.
18    fn start_scan_cmd() -> &'static [u8; 2];
19
20    /// Get the command to stop scanning.
21    ///
22    /// Returns a reference to the command byte array.
23    fn stop_scan_cmd() -> &'static [u8; 2];
24
25    /// Get the command to retrieve device information.
26    ///
27    /// Returns a reference to the command byte array.
28    fn get_info_cmd() -> &'static [u8; 2];
29}
30
31/// YDLidar T-mini Plus model implementation.
32///
33/// This struct represents the T-mini Plus LIDAR model and implements
34/// the necessary traits for device communication.
35#[derive(Debug, Clone, Copy)]
36pub struct TMiniPlus;
37
38impl LidarCommands for TMiniPlus {
39    type Header = TMiniHeader;
40
41    fn start_scan_cmd() -> &'static [u8; 2] {
42        TMiniPlusCmd::START_SCAN.as_cmd()
43    }
44
45    fn stop_scan_cmd() -> &'static [u8; 2] {
46        TMiniPlusCmd::STOP_SCAN.as_cmd()
47    }
48
49    fn get_info_cmd() -> &'static [u8; 2] {
50        TMiniPlusCmd::GET_INFO.as_cmd()
51    }
52}
53
54/// Data packet header for T-mini Plus.
55///
56/// # Fields
57///
58/// - `header`: Start marker (0x55AA)
59/// - `ct`: Control byte containing scan frequency and package type
60/// - `lsn`: Sample count in this packet
61/// - `fsa`: First sample angle (raw value)
62/// - `lsa`: Last sample angle (raw value)
63/// - `cs`: Checksum for data integrity
64#[derive(Debug, Clone, Copy)]
65#[repr(C)]
66pub struct TMiniHeader {
67    /// Start marker (0x55AA).
68    pub header: u16,
69    /// Control byte: scan frequency and package type.
70    pub ct: u8,
71    /// Sample count in this packet.
72    pub lsn: u8,
73    /// First sample angle (raw value).
74    pub fsa: u16,
75    /// Last sample angle (raw value).
76    pub lsa: u16,
77    /// Checksum for data integrity.
78    pub cs: u16,
79}
80
81impl TMiniHeader {
82    /// Create a header from raw bytes.
83    ///
84    /// # Arguments
85    ///
86    /// * `data` - Raw byte array containing header data
87    ///
88    /// # Returns
89    ///
90    /// A new `TMiniHeader` instance parsed from the bytes.
91    pub fn from_bytes(data: &[u8]) -> Option<Self> {
92        if data.len() < 10 {
93            return None;
94        }
95
96        Some(TMiniHeader {
97            header: u16::from_le_bytes([data[0], data[1]]),
98            ct: data[2],
99            lsn: data[3],
100            fsa: u16::from_le_bytes([data[4], data[5]]),
101            lsa: u16::from_le_bytes([data[6], data[7]]),
102            cs: u16::from_le_bytes([data[8], data[9]]),
103        })
104    }
105
106    /// Check if the header is valid.
107    ///
108    /// Verifies that the start marker is correct (0x55AA).
109    pub fn is_valid(&self) -> bool {
110        self.header == 0x55AA // Little endian: bytes are AA 55, value is 0x55AA
111    }
112
113    /// Get the scan frequency from the control byte.
114    ///
115    /// # Returns
116    ///
117    /// The scan frequency in Hz.
118    pub fn scan_frequency(&self) -> f32 {
119        match self.ct & 0x03 {
120            0 => 5.0,
121            1 => 8.0,
122            2 => 10.0,
123            _ => 0.0,
124        }
125    }
126}
127
128/// Each sample in a scan packet contains distance and intensity information.
129#[derive(Debug, Clone, Copy)]
130#[repr(C)]
131pub struct SampleNode {
132    /// Signal intensity (0-255).
133    pub intensity: u8,
134    /// Low byte of distance value.
135    pub distance_low: u8,
136    /// High byte of distance and flag bits.
137    pub distance_high_and_flag: u8,
138}
139
140impl SampleNode {
141    /// # Returns
142    ///
143    /// Distance in millimeters.
144    pub fn distance(&self) -> u16 {
145        // where s1=intensity, s2=distance_low, s3=distance_high_and_flag
146        ((self.distance_high_and_flag as u16) << 6) | ((self.distance_low as u16) >> 2)
147    }
148
149    /// # Returns
150    ///
151    /// True if the sample should be discarded.
152    pub fn is_invalid(&self) -> bool {
153        (self.distance_high_and_flag & 0x80) != 0
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_tmini_header_parsing() {
163        let data = [0x55, 0xAA, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
164        let header = TMiniHeader::from_bytes(&data).unwrap();
165        assert!(header.is_valid());
166        assert_eq!(header.lsn, 1);
167    }
168
169    #[test]
170    fn test_sample_node_distance() {
171        let sample = SampleNode {
172            intensity: 100,
173            distance_low: 0x34,
174            distance_high_and_flag: 0x12,
175        };
176        assert_eq!(sample.distance(), 0x1234);
177        assert!(!sample.is_invalid());
178    }
179}