#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
#[cfg(feature = "shared_radio")]
mod shared_radio;
#[cfg(feature = "shared_radio")]
pub use crate::shared_radio::{SharedCrazyradio, WeakSharedCrazyradio};
use core::time::Duration;
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
type Result<T> = std::result::Result<T, Error>;
fn find_crazyradio(
nth: Option<usize>,
serial: Option<&str>,
) -> Result<rusb::Device<rusb::GlobalContext>> {
let mut n = 0;
for device in rusb::devices()?.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == 0x1915 && device_desc.product_id() == 0x7777 {
let handle = device.open()?;
if (nth == None || nth == Some(n))
&& (serial == None || serial == Some(&get_serial(&device_desc, &handle)?))
{
return Ok(device);
}
n += 1;
}
}
Err(Error::NotFound)
}
fn get_serial<T: rusb::UsbContext>(
device_desc: &rusb::DeviceDescriptor,
handle: &rusb::DeviceHandle<T>,
) -> Result<String> {
let languages = handle.read_languages(Duration::from_secs(1))?;
if !languages.is_empty() {
let serial =
handle.read_serial_number_string(languages[0], device_desc, Duration::from_secs(1))?;
Ok(serial)
} else {
Err(Error::NotFound)
}
}
fn list_crazyradio_serials() -> Result<Vec<String>> {
let mut serials = vec![];
for device in rusb::devices()?.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == 0x1915 && device_desc.product_id() == 0x7777 {
let handle: rusb::DeviceHandle<rusb::GlobalContext> = device.open()?;
let languages = handle.read_languages(Duration::from_secs(1))?;
if !languages.is_empty() {
let serial = handle.read_serial_number_string(
languages[0],
&device_desc,
Duration::from_secs(1),
)?;
serials.push(serial);
}
}
}
Ok(serials)
}
enum UsbCommand {
SetRadioChannel = 0x01,
SetRadioAddress = 0x02,
SetDataRate = 0x03,
SetRadioPower = 0x04,
SetRadioArd = 0x05,
SetRadioArc = 0x06,
AckEnable = 0x10,
SetContCarrier = 0x20,
SetInlineMode = 0x23,
SetPacketLossSimulation = 0x30,
LaunchBootloader = 0xff,
}
pub struct Crazyradio {
device_desciptor: rusb::DeviceDescriptor,
device_handle: rusb::DeviceHandle<rusb::GlobalContext>,
cache_settings: bool,
inline_mode: bool,
channel: Channel,
address: [u8; 5],
datarate: Datarate,
ack_enable: bool,
}
impl Crazyradio {
pub fn open_first() -> Result<Self> {
Crazyradio::open_nth(0)
}
pub fn open_nth(nth: usize) -> Result<Self> {
Self::open_generic(Some(nth), None)
}
pub fn open_by_serial(serial: &str) -> Result<Self> {
Self::open_generic(None, Some(serial))
}
fn open_generic(nth: Option<usize>, serial: Option<&str>) -> Result<Self> {
let device = find_crazyradio(nth, serial)?;
let device_desciptor = device.device_descriptor()?;
let device_handle = device.open()?;
device_handle.claim_interface(0)?;
let version = device_desciptor.device_version();
let version = version.major() as f64
+ (version.minor() as f64 / 10.0)
+ (version.sub_minor() as f64 / 100.0);
if version < 0.5 {
return Err(Error::DongleVersionNotSupported);
}
let mut cr = Crazyradio {
device_desciptor,
device_handle,
cache_settings: true,
inline_mode: false,
channel: Channel::from_number(2).unwrap(),
address: [0xe7; 5],
datarate: Datarate::Dr2M,
ack_enable: true,
};
cr.reset()?;
Ok(cr)
}
pub fn list_serials() -> Result<Vec<String>> {
list_crazyradio_serials()
}
pub fn serial(&self) -> Result<String> {
get_serial(&self.device_desciptor, &self.device_handle)
}
pub fn reset(&mut self) -> Result<()> {
let prev_cache_settings = self.cache_settings;
self.cache_settings = false;
_ = self.set_inline_mode(true);
self.set_datarate(Datarate::Dr2M)?;
self.set_channel(Channel::from_number(2).unwrap())?;
self.set_cont_carrier(false)?;
self.set_address(&[0xe7, 0xe7, 0xe7, 0xe7, 0xe7])?;
self.set_power(Power::P0dBm)?;
self.set_arc(3)?;
self.set_ard_bytes(32)?;
self.set_ack_enable(true)?;
self.cache_settings = prev_cache_settings;
Ok(())
}
pub fn set_cache_settings(&mut self, cache_settings: bool) {
self.cache_settings = cache_settings;
}
pub fn set_channel(&mut self, channel: Channel) -> Result<()> {
if !self.inline_mode && (!self.cache_settings || self.channel != channel) {
self.device_handle.write_control(
0x40,
UsbCommand::SetRadioChannel as u8,
channel.0 as u16,
0,
&[],
Duration::from_secs(1),
)?;
}
self.channel = channel;
Ok(())
}
pub fn set_datarate(&mut self, datarate: Datarate) -> Result<()> {
if !self.inline_mode && (!self.cache_settings || self.datarate != datarate) {
self.device_handle.write_control(
0x40,
UsbCommand::SetDataRate as u8,
datarate as u16,
0,
&[],
Duration::from_secs(1),
)?;
}
self.datarate = datarate;
Ok(())
}
pub fn set_address(&mut self, address: &[u8; 5]) -> Result<()> {
if !self.inline_mode && (!self.cache_settings || self.address != *address) {
self.device_handle.write_control(
0x40,
UsbCommand::SetRadioAddress as u8,
0,
0,
address,
Duration::from_secs(1),
)?;
}
if self.cache_settings || self.inline_mode {
self.address.copy_from_slice(address);
}
Ok(())
}
pub fn set_power(&mut self, power: Power) -> Result<()> {
self.device_handle.write_control(
0x40,
UsbCommand::SetRadioPower as u8,
power as u16,
0,
&[],
Duration::from_secs(1),
)?;
Ok(())
}
pub fn set_ard_time(&mut self, delay: Duration) -> Result<()> {
if delay <= Duration::from_millis(4000) {
let ard = (delay.as_millis() as u16 / 250) - 1;
self.device_handle.write_control(
0x40,
UsbCommand::SetRadioArd as u8,
ard,
0,
&[],
Duration::from_secs(1),
)?;
Ok(())
} else {
Err(Error::InvalidArgument)
}
}
pub fn set_ard_bytes(&mut self, nbytes: u8) -> Result<()> {
if nbytes <= 32 {
self.device_handle.write_control(
0x40,
UsbCommand::SetRadioArd as u8,
0x80 | nbytes as u16,
0,
&[],
Duration::from_secs(1),
)?;
Ok(())
} else {
Err(Error::InvalidArgument)
}
}
pub fn set_arc(&mut self, arc: usize) -> Result<()> {
if arc <= 15 {
self.device_handle.write_control(
0x40,
UsbCommand::SetRadioArc as u8,
arc as u16,
0,
&[],
Duration::from_secs(1),
)?;
Ok(())
} else {
Err(Error::InvalidArgument)
}
}
pub fn set_ack_enable(&mut self, ack_enable: bool) -> Result<()> {
if !self.inline_mode && ack_enable != self.ack_enable {
self.device_handle.write_control(
0x40,
UsbCommand::AckEnable as u8,
ack_enable as u16,
0,
&[],
Duration::from_secs(1),
)?;
}
self.ack_enable = ack_enable;
Ok(())
}
pub fn scan_channels(
&mut self,
start: Channel,
stop: Channel,
packet: &[u8],
) -> Result<Vec<Channel>> {
let mut ack_data = [0u8; 32];
let mut result: Vec<Channel> = vec![];
for ch in start.0..stop.0 + 1 {
let channel = Channel::from_number(ch).unwrap();
self.set_channel(channel)?;
let ack = self.send_packet(packet, &mut ack_data)?;
if ack.received {
result.push(channel);
}
}
Ok(result)
}
pub fn launch_bootloader(self) -> Result<()> {
self.device_handle.write_control(
0x40,
UsbCommand::LaunchBootloader as u8,
0,
0,
&[],
Duration::from_secs(1),
)?;
Ok(())
}
pub fn set_cont_carrier(&mut self, enable: bool) -> Result<()> {
self.device_handle.write_control(
0x40,
UsbCommand::SetContCarrier as u8,
enable as u16,
0,
&[],
Duration::from_secs(1),
)?;
Ok(())
}
pub fn set_inline_mode(&mut self, inline_mode_enable: bool) -> Result<()> {
let setting = inline_mode_enable.then_some(1).unwrap_or(0);
self.device_handle.write_control(
0x40,
UsbCommand::SetInlineMode as u8,
setting,
0,
&[],
Duration::from_secs(1),
)?;
self.inline_mode = inline_mode_enable;
Ok(())
}
pub fn set_packet_loss_simulation(
&mut self,
packet_loss_percent: u8,
ack_loss_percent: u8,
) -> Result<()> {
if self.device_desciptor.device_version() < rusb::Version::from_bcd(0x0500) {
return Err(Error::DongleVersionNotSupported);
}
if packet_loss_percent <= 100 && ack_loss_percent <= 100 {
let data = [packet_loss_percent, ack_loss_percent];
self.device_handle.write_control(
0x40,
UsbCommand::SetPacketLossSimulation as u8,
0,
0,
&data,
Duration::from_secs(1),
)?;
Ok(())
} else {
Err(Error::InvalidArgument)
}
}
pub fn send_packet(&mut self, data: &[u8], ack_data: &mut [u8]) -> Result<Ack> {
if self.inline_mode {
self.send_inline(data, Some(ack_data))
} else {
self.device_handle
.write_bulk(0x01, data, Duration::from_secs(1))?;
let mut received_data = [0u8; 33];
let received =
self.device_handle
.read_bulk(0x81, &mut received_data, Duration::from_secs(1))?;
if ack_data.len() <= 32 {
ack_data.copy_from_slice(&received_data[1..ack_data.len() + 1]);
} else {
ack_data
.split_at_mut(32)
.0
.copy_from_slice(&received_data[1..33]);
}
Ok(Ack {
received: received_data[0] & 0x01 != 0,
power_detector: received_data[0] & 0x02 != 0,
retry: ((received_data[0] & 0xf0) >> 4) as usize,
length: received - 1,
})
}
}
pub fn send_packet_no_ack(&mut self, data: &[u8]) -> Result<()> {
if self.inline_mode {
self.send_inline(data, None)?;
} else {
self.device_handle
.write_bulk(0x01, data, Duration::from_secs(1))?;
}
Ok(())
}
fn send_inline(&mut self, data: &[u8], ack_data: Option<&mut [u8]>) -> Result<Ack> {
const OUT_HEADER_LENGTH: usize = 8;
const IN_HEADER_LENGTH: usize = 2;
const OUT_FIELD2_ACK_ENABLE: u8 = 0x10;
const IN_HEADER_ACK_RECEIVED: u8 = 0x01;
const IN_HEADER_POWER_DETECTOR: u8 = 0x02;
const _IN_HEADER_INVALID_SETTING: u8 = 0x04;
const IN_HEADER_RETRY_MASK: u8 = 0xf0;
const IN_HEADER_RETRY_SHIFT: u8 = 4;
let mut command = vec![];
command.push((OUT_HEADER_LENGTH + data.len()) as u8);
let mut field2 = self.datarate as u8;
if self.ack_enable {
field2 |= OUT_FIELD2_ACK_ENABLE;
}
command.push(field2);
command.push(self.channel.into());
command.extend_from_slice(&self.address);
command.extend_from_slice(&data);
let mut answer = [0u8; 64];
self.device_handle
.write_bulk(0x01, &command, Duration::from_secs(1))?;
let answer_size = self.device_handle
.read_bulk(0x81, &mut answer, Duration::from_secs(1))?;
if (answer_size < IN_HEADER_LENGTH) || ((answer[0] as usize) != answer_size) {
return Err(Error::UsbProtocolError("Inline header from radio malformed, try to update your radio".to_string()));
}
let payload_length = (answer[0] as usize) - 2;
if let Some(ack_data) = ack_data {
ack_data[0..payload_length]
.copy_from_slice(&answer[IN_HEADER_LENGTH..(IN_HEADER_LENGTH + payload_length)]);
}
Ok(Ack {
received: answer[1] & IN_HEADER_ACK_RECEIVED != 0,
power_detector: answer[1] & IN_HEADER_POWER_DETECTOR != 0,
retry: ((answer[1] & IN_HEADER_RETRY_MASK) >> IN_HEADER_RETRY_SHIFT) as usize,
length: payload_length,
})
}
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
impl Crazyradio {
pub async fn open_first_async() -> Result<Self> {
let (tx, rx) = flume::bounded(0);
std::thread::spawn(move || tx.send(Self::open_first()));
rx.recv_async().await.unwrap()
}
pub async fn open_nth_async(nth: usize) -> Result<Self> {
let (tx, rx) = flume::bounded(0);
std::thread::spawn(move || tx.send(Self::open_nth(nth)));
rx.recv_async().await.unwrap()
}
pub async fn open_by_serial_async(serial: &str) -> Result<Self> {
let serial = serial.to_owned();
let (tx, rx) = flume::bounded(0);
std::thread::spawn(move || tx.send(Self::open_by_serial(&serial)));
rx.recv_async().await.unwrap()
}
pub async fn list_serials_async() -> Result<Vec<String>> {
let (tx, rx) = flume::bounded(0);
std::thread::spawn(move || tx.send(Self::list_serials()));
rx.recv_async().await.unwrap()
}
}
#[derive(thiserror::Error, Debug, Clone)]
pub enum Error {
#[error("Usb Error: {0:?}")]
UsbError(rusb::Error),
#[error("Crazyradio not found")]
NotFound,
#[error("Invalid arguments")]
InvalidArgument,
#[error("Crazyradio version not supported")]
DongleVersionNotSupported,
#[error("USB protocol error ({0})")]
UsbProtocolError(String),
}
impl From<rusb::Error> for Error {
fn from(usb_error: rusb::Error) -> Self {
Error::UsbError(usb_error)
}
}
#[derive(Debug, Copy, Clone)]
pub struct Ack {
pub received: bool,
pub power_detector: bool,
pub retry: usize,
pub length: usize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde_support", derive(Serialize))]
pub struct Channel(u8);
#[cfg(feature = "serde_support")]
impl<'de> Deserialize<'de> for Channel {
fn deserialize<D>(deserializer: D) -> std::result::Result<Channel, D::Error>
where
D: serde::Deserializer<'de>,
{
let ch_number: u8 = Deserialize::deserialize(deserializer)?;
let channel = Channel::from_number(ch_number)
.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
Ok(channel)
}
}
impl Channel {
pub fn from_number(channel: u8) -> Result<Self> {
if channel < 126 {
Ok(Channel(channel))
} else {
Err(Error::InvalidArgument)
}
}
}
impl From<Channel> for u8 {
fn from(val: Channel) -> Self {
val.0
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum Datarate {
Dr250K = 0,
Dr1M = 1,
Dr2M = 2,
}
pub enum Power {
Pm18dBm = 0,
Pm12dBm = 1,
Pm6dBm = 2,
P0dBm = 3,
}
#[cfg(test)]
mod tests {
#[cfg(feature = "serde_support")]
use serde_json;
#[cfg(feature = "serde_support")]
use super::Channel;
#[test]
#[cfg(feature = "serde_support")]
fn test_that_deserializing_a_correct_channel_works() {
let test_str = "42";
let result: Result<Channel, serde_json::Error> = serde_json::from_str(test_str);
assert!(matches!(result, Ok(Channel(42))));
}
#[test]
#[cfg(feature = "serde_support")]
fn test_that_deserializing_an_incorrect_channel_works() {
let test_str = "126";
let result: Result<Channel, serde_json::Error> = serde_json::from_str(test_str);
assert!(matches!(result, Err(_)));
}
#[test]
#[cfg(feature = "serde_support")]
fn test_that_serialize_channel_works() {
let test_channel = Channel(42);
let result = serde_json::to_string(&test_channel);
assert!(matches!(result, Ok(str) if str == "42"));
}
}