cc1101_rust/
lib.rs

1//! This project provides an interface to the [CC1101 Linux Driver](https://github.com/28757B2/cc1101-driver) to allow receiving and transmitting packets from Rust.
2//!
3//! The CC1101 is a general purpose packet radio that operates in the Sub-GHz Industrial, Scientific and Medical (ISM) bands (315/433/868/915 MHz).
4//!
5//! The driver supports a subset of the CC1101 hardware's features and provides a high-level interface to the device that does not require setting of the individual hardware registers.
6//!
7//! * Frequencies - 300-348/387-464/778-928 MHz
8//! * Modulation - OOK/2FSK/4FSK/GFSK/MSK
9//! * Data Rate - 0.6 - 500 kBaud
10//! * RX Bandwidth - 58 - 812 kHz
11//! * Arbitrary packet length RX/TX
12//! * Sync word or carrier sense triggered RX
13//! * 16/32 bit configurable sync word
14
15pub mod config;
16mod ioctl;
17mod patable;
18
19use config::{RXConfig, Registers, RegistersType, TXConfig};
20use std::fs::{File, OpenOptions};
21use std::io::{Read, Write};
22
23// Driver version
24const VERSION: u32 = 4;
25
26/// Errors encountered during communication with the CC1101 driver
27#[derive(Debug)]
28pub enum DeviceError {
29    NoDevice,
30    FileHandleClone,
31    InvalidIOCTL,
32    VersionMismatch,
33    NoRXConfig,
34    Busy,
35    Copy,
36    InvalidConfig,
37    OutOfMemory,
38    BufferEmpty,
39    PacketSize,
40    Unknown,
41}
42
43/// Errors caused by device configuration
44#[derive(Debug)]
45pub enum ConfigError {
46    InvalidFrequency,
47    InvalidBandwidth,
48    InvalidCarrierSense,
49    InvalidTXPower,
50    InvalidBaudRate,
51    InvalidDeviation,
52    InvalidSyncWord,
53    InvalidMaxLNAGain,
54    InvalidMaxDVGAGain,
55    InvalidMagnTarget,
56}
57
58/// Generic type for errors thrown by the module
59#[derive(Debug)]
60pub enum CC1101Error {
61    Device(DeviceError),
62    Config(ConfigError),
63}
64
65/// CC1101 radio device
66///
67/// This struct provides a handle to a CC1101 device, presented by the [Linux Driver](https://github.com/28757B2/cc1101-driver) as a character device (e.g `/dev/cc1101.0.0`).
68/// It is used to configure the driver to receive and transmit packets.
69///
70/// # Receive
71///
72/// As all packet reception and interaction with the CC1101 hardware occurs within the kernel driver, receiving packets is an asynchronous process.
73///
74/// The driver begins RX when an IOCTL is sent to the character device with a receive configuration. Packets received by the radio are buffered within a FIFO
75/// until being read into userspace via a `read()` call. Packet reception stops when a reset IOCTL is sent.
76///
77/// Calling [`CC1101::new`] or [`CC1101::set_rx_config`] with an [`RXConfig`] causes the driver to begin packet reception.
78///
79/// [`CC1101::receive`] can then later be used to read out the contents of the driver packet receive FIFO.
80///
81/// [`CC1101::reset`] is used to stop packet reception by the driver.
82///
83/// # Transmit
84///
85/// Transmission of packets is a synchronous process.
86///
87/// TX occurs when an IOCTL is sent to the character device with a transmit configuration and a `write()` call made with the bytes to transmit.
88/// When a `write()` occurs, RX is paused, the device is reconfigured for TX and the provided packet is transmitted. Once TX completes, the receive config is restored and RX continues.
89/// The `write()` call blocks until completion of the transmission.
90///
91/// [`CC1101::transmit`] is used to transmit packets using a [`TXConfig`]. This call will block until TX is complete.
92///
93/// # Device Sharing
94///
95/// It is possible to share a CC1101 character device between multiple receiving and transmitting process.
96///
97/// For example, this allows a long-running receiving process to share a device with a process that occasionally transmits.
98///
99/// The receiving process periodically polls the character device to check for new packets. Another process can acquire the character device while the receving process is sleeping and request a transmit.
100/// This will cause the driver to reconfigure the hardware, transmit, then return to the receive configuration and continue listening for new packets.
101///
102///
103/// This behaviour is controlled by the `blocking` argument to [`CC1101::new`]. Specifying `false` will release the file handle to the character device after every [`CC1101::receive`] and [`CC1101::transmit`] call.
104/// This enables another process to aquire a handle to use the radio between events. The `open()` call on the character device will block until the radio becomes available again.
105///
106/// Specifying `true` will hold the file handle open while the [`CC1101`] struct is kept in scope. This prevents another process from using the device between events.
107///
108/// Note - sharing a device between two receiving processes will cause packet loss, as the driver's internal packet buffer is reset each time a new receive configuration is set.
109///
110pub struct CC1101 {
111    device: String,
112    handle: Option<File>,
113    rx_config: Option<RXConfig>,
114}
115
116impl CC1101 {
117    /// Create a new handle to a CC1101 device
118    ///
119    /// Providing an `rx_config` will configure the driver for RX with the provided configuration and begin packet reception. Received packets can be read using [`CC1101::receive`].
120    ///
121    /// `blocking` determines if the file handle to the device should be kept open. This prevents another process from using the radio (and reconfiguring it), but prevents sharing of the device with another process.
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// # use cc1101_rust::{CC1101, config::{RXConfig, Modulation}};
127    /// let rx_config = RXConfig::new(433.92, Modulation::OOK, 1.0, 64, None, None, None, None, None, None, None)?;
128    /// let cc1101 = CC1101::new("/dev/cc1101.0.0", Some(rx_config), false)?;
129    /// # Ok::<(), cc1101_rust::CC1101Error>(())
130    /// ```
131    pub fn new(
132        device: &str,
133        rx_config: Option<RXConfig>,
134        blocking: bool,
135    ) -> Result<CC1101, CC1101Error> {
136        let handle = Self::open(device)?;
137
138        if let Some(rx_config) = &rx_config {
139            Self::set_rx_config_on_device(&handle, &None, rx_config, blocking)?;
140        }
141
142        match blocking {
143            true => Ok(CC1101 {
144                device: device.to_string(),
145                handle: Some(handle),
146                rx_config,
147            }),
148            false => Ok(CC1101 {
149                device: device.to_string(),
150                handle: None,
151                rx_config,
152            }),
153        }
154    }
155
156    /// Get the current RSSI value from the radio
157    pub fn get_rssi(&self) -> Result<u8, CC1101Error> {
158        let handle = self.get_handle()?;
159        ioctl::get_rssi(&handle)
160    }
161
162    /// Get the maximum packet size configured in the driver
163    pub fn get_max_packet_size(&self) -> Result<u32, CC1101Error> {
164        let handle = self.get_handle()?;
165        ioctl::get_max_packet_size(&handle)
166    }
167
168    /// Receive packets from the radio
169    ///
170    /// This will read the content of the driver's received packet buffer if the driver is already in RX.
171    ///
172    /// If the driver is not in RX (i.e [`CC1101::reset`] has been called), calling this will configure the driver for RX and begin packet reception.
173    ///
174    /// Individual packets are a [`Vec<u8>`] of the size specified in the `packet_length` argument to [`RXConfig::new`].
175    ///
176    /// The return type is [`Vec<Vec<u8>>`], as multiple packets can be returned in one receive call. This will be empty if no packets have been received.
177    ///
178    /// # Example
179    ///
180    /// ```no_run
181    /// # use std::{thread, time};
182    /// # use cc1101_rust::{CC1101, config::{RXConfig, Modulation}};
183    /// let rx_config = RXConfig::new(433.92, Modulation::OOK, 1.0, 64, None, None, None, None, None, None, None)?;
184    /// let cc1101 = CC1101::new("/dev/cc1101.0.0", Some(rx_config), false)?;
185    ///
186    /// loop {
187    ///     let packets = cc1101.receive()?;
188    ///     for packet in packets {
189    ///         println!("Received - {:x?}", packet);
190    ///     }
191    ///     thread::sleep(time::Duration::from_millis(100));
192    /// }
193    /// # Ok::<(), cc1101_rust::CC1101Error>(())
194    /// ```
195    pub fn receive(&self) -> Result<Vec<Vec<u8>>, CC1101Error> {
196        if let Some(rx_config) = &self.rx_config {
197            let mut handle = self.get_handle()?;
198            Self::set_rx_config_on_device(
199                &handle,
200                &self.rx_config,
201                rx_config,
202                self.handle.is_some(),
203            )?;
204
205            let mut packets = vec![];
206            loop {
207                let mut packet = vec![0; rx_config.get_packet_length() as usize];
208                match handle.read(&mut packet) {
209                    Ok(_) => {
210                        packets.push(packet);
211                    }
212                    Err(e) => match e.raw_os_error() {
213                        Some(libc::ENOMSG) => break,
214                        Some(libc::EMSGSIZE) => {
215                            return Err(CC1101Error::Device(DeviceError::PacketSize))
216                        }
217                        Some(libc::EBUSY) => return Err(CC1101Error::Device(DeviceError::Busy)),
218                        Some(libc::EINVAL) => {
219                            return Err(CC1101Error::Device(DeviceError::InvalidConfig))
220                        }
221                        Some(libc::EFAULT) => return Err(CC1101Error::Device(DeviceError::Copy)),
222                        _ => {
223                            return Err(CC1101Error::Device(DeviceError::Unknown));
224                        }
225                    },
226                }
227            }
228
229            Ok(packets)
230        } else {
231            Err(CC1101Error::Device(DeviceError::NoRXConfig))
232        }
233    }
234
235    /// Transmit a packet via the radio using the provided configuration
236    ///
237    /// # Example
238    /// ```no_run
239    /// # use cc1101_rust::{CC1101, config::{TXConfig, Modulation}};
240    /// const PACKET: [u8; 11] = [0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f];       
241    ///
242    /// let tx_config = TXConfig::new(433.92, Modulation::OOK, 1.0, 0.1, None, None)?;
243    /// let cc1101 = CC1101::new("/dev/cc1101.0.0", None, false)?;
244    ///
245    /// cc1101.transmit(&tx_config, &PACKET)?;
246    /// # Ok::<(), cc1101_rust::CC1101Error>(())
247    /// ```
248    ///
249    pub fn transmit(&self, tx_config: &TXConfig, data: &[u8]) -> Result<(), CC1101Error> {
250        let mut handle = self.get_handle()?;
251
252        Self::set_tx_config_on_device(&handle, tx_config)?;
253
254        match handle.write(data) {
255            Ok(_) => Ok(()),
256            Err(e) => match e.raw_os_error() {
257                Some(libc::EINVAL) => Err(CC1101Error::Device(DeviceError::PacketSize)),
258                Some(libc::ENOMEM) => Err(CC1101Error::Device(DeviceError::OutOfMemory)),
259                Some(libc::EFAULT) => Err(CC1101Error::Device(DeviceError::Copy)),
260                _ => Err(CC1101Error::Device(DeviceError::Unknown)),
261            },
262        }
263    }
264
265    /// Open a file handle to the device
266    fn open(device: &str) -> Result<File, CC1101Error> {
267        let handle = match OpenOptions::new().read(true).write(true).open(device) {
268            Ok(file) => file,
269            Err(e) => match e.raw_os_error() {
270                Some(libc::EBUSY) => return Err(CC1101Error::Device(DeviceError::Busy)),
271                _ => return Err(CC1101Error::Device(DeviceError::Unknown)),
272            },
273        };
274
275        let version = ioctl::get_version(&handle)?;
276
277        if version != VERSION {
278            return Err(CC1101Error::Device(DeviceError::VersionMismatch));
279        }
280
281        Ok(handle)
282    }
283
284    /// Get a handle to the device.
285    ///
286    /// Either re-use the existing handle if in blocking mode, or create a new one.
287    fn get_handle(&self) -> Result<File, CC1101Error> {
288        if let Some(handle) = &self.handle {
289            match handle.try_clone() {
290                Ok(h) => Ok(h),
291                Err(_) => Err(CC1101Error::Device(DeviceError::FileHandleClone)),
292            }
293        } else {
294            Ok(Self::open(&self.device)?)
295        }
296    }
297
298    /// Issue a reset command to the device.
299    ///
300    /// This will clear the received packet buffer and stop receiving. Packet reception can be resumed by calling [`CC1101::receive`].
301    pub fn reset(&mut self) -> Result<(), CC1101Error> {
302        ioctl::reset(&self.get_handle()?)
303    }
304
305    fn set_tx_config_on_device(handle: &File, tx_config: &TXConfig) -> Result<(), CC1101Error> {
306        ioctl::set_tx_conf(handle, tx_config)
307    }
308
309    /// Set the receive configuration.
310    ///
311    /// This will configure the driver for RX with the provided configuration and begin packet reception. Received packets can be read using [`CC1101::receive`].
312    ///
313    pub fn set_rx_config(&mut self, rx_config: &RXConfig) -> Result<(), CC1101Error> {
314        Self::set_rx_config_on_device(
315            &self.get_handle()?,
316            &self.rx_config,
317            rx_config,
318            self.handle.is_some(),
319        )?;
320        self.rx_config = Some(rx_config.clone());
321        Ok(())
322    }
323
324    fn set_rx_config_on_device(
325        handle: &File,
326        old_config: &Option<RXConfig>,
327        new_config: &RXConfig,
328        blocking: bool,
329    ) -> Result<(), CC1101Error> {
330        // Does the new config match the saved config
331        let configs_match = match old_config {
332            Some(old_config) => old_config == new_config,
333            None => false,
334        };
335
336        if configs_match {
337            // In non-blocking mode, the RX config on the device may become of out sync with the saved config
338            if !blocking {
339                // Get the current config on the device
340                let current_device_config = ioctl::get_rx_conf(handle)?;
341
342                // Update the device if the config on the device and the saved config differ
343                if current_device_config != *new_config {
344                    ioctl::set_rx_conf(handle, new_config)?;
345                }
346            }
347        } else {
348            ioctl::set_rx_conf(handle, new_config)?;
349        }
350        Ok(())
351    }
352
353    /// Get the configured receive config
354    pub fn get_rx_config(&self) -> &Option<RXConfig> {
355        &self.rx_config
356    }
357
358    /// Get the transmit configuration currently set in the driver
359    pub fn get_device_tx_config(&mut self) -> Result<TXConfig, CC1101Error> {
360        ioctl::get_tx_conf(&self.get_handle()?)
361    }
362
363    /// Get the receive configuration currently set in the driver
364    ///
365    /// In non-blocking mode, this may differ from the value returned by [`CC1101::get_rx_config`] if another process has reconfigured the device.
366    pub fn get_device_rx_config(&mut self) -> Result<RXConfig, CC1101Error> {
367        ioctl::get_rx_conf(&self.get_handle()?)
368    }
369
370    /// Get the set of hardware registers for RX/TX currently configured in the driver, or currently configured on the CC1101
371    pub fn get_device_registers(
372        &self,
373        registers_type: RegistersType,
374    ) -> Result<Registers, CC1101Error> {
375        ioctl::get_registers(&self.get_handle()?, registers_type)
376    }
377}