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