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}