use std::ffi::CString;
use std::fs::{File, OpenOptions};
use std::prelude::v1::*;
use std::string::String;
use std::time::SystemTime;
use libc::{self};
#[cfg(not(feature = "defmt"))]
use log::{debug, info};
#[cfg(feature = "defmt")]
use defmt::{debug, info};
use clap::Parser;
use embedded_hal::delay::DelayNs;
use humantime::Duration as HumanDuration;
use byteorder::{ByteOrder, NetworkEndian};
use pcap_file::{pcap::PcapHeader, DataLink, PcapWriter};
use rolling_stats::Stats;
use crate::*;
use crate::{
blocking::{BlockingError, BlockingOptions, BlockingReceive, BlockingTransmit},
Power, Receive, ReceiveInfo, Rssi, Transmit,
};
#[derive(Clone, Parser, PartialEq, Debug)]
pub enum Operation {
#[clap(name = "tx")]
Transmit(TransmitOptions),
#[clap(name = "rx")]
Receive(ReceiveOptions),
#[clap(name = "rssi")]
Rssi(RssiOptions),
#[clap(name = "echo")]
Echo(EchoOptions),
#[clap(name = "ping-pong")]
LinkTest(PingPongOptions),
}
pub fn do_operation<T, I, E>(radio: &mut T, operation: Operation) -> Result<(), BlockingError<E>>
where
T: Transmit<Error = E>
+ Power<Error = E>
+ Receive<Info = I, Error = E>
+ Rssi<Error = E>
+ Power<Error = E>
+ DelayNs,
I: ReceiveInfo + Default + std::fmt::Debug,
E: std::fmt::Debug,
{
let mut buff = [0u8; 1024];
match operation {
Operation::Transmit(options) => do_transmit(radio, options)?,
Operation::Receive(options) => do_receive(radio, &mut buff, options).map(|_| ())?,
Operation::Echo(options) => do_echo(radio, &mut buff, options).map(|_| ())?,
Operation::Rssi(options) => do_rssi(radio, options).map(|_| ())?,
Operation::LinkTest(options) => do_ping_pong(radio, options).map(|_| ())?,
}
Ok(())
}
#[derive(Clone, Parser, PartialEq, Debug)]
pub struct TransmitOptions {
#[clap(long)]
pub data: Vec<u8>,
#[clap(long)]
pub power: Option<i8>,
#[clap(long)]
pub period: Option<HumanDuration>,
#[clap(flatten)]
pub blocking_options: BlockingOptions,
}
pub fn do_transmit<T, E>(radio: &mut T, options: TransmitOptions) -> Result<(), BlockingError<E>>
where
T: Transmit<Error = E> + Power<Error = E> + DelayNs,
E: core::fmt::Debug,
{
if let Some(p) = options.power {
radio.set_power(p)?;
}
loop {
radio.do_transmit(&options.data, options.blocking_options.clone())?;
match &options.period {
Some(p) => radio.delay_us(p.as_micros() as u32),
None => break,
}
}
Ok(())
}
#[derive(Clone, Parser, PartialEq, Debug)]
pub struct ReceiveOptions {
#[clap(long = "continuous")]
pub continuous: bool,
#[clap(flatten)]
pub pcap_options: PcapOptions,
#[clap(flatten)]
pub blocking_options: BlockingOptions,
}
#[derive(Clone, Parser, PartialEq, Debug)]
pub struct PcapOptions {
#[clap(long, group = "1")]
pub pcap_file: Option<String>,
#[clap(long, group = "1")]
pub pcap_pipe: Option<String>,
}
impl PcapOptions {
pub fn open(&self) -> Result<Option<PcapWriter<File>>, std::io::Error> {
let pcap_file = match (&self.pcap_file, &self.pcap_pipe) {
(Some(file), None) => {
let f = File::create(file)?;
Some(f)
}
#[cfg(target_family = "unix")]
(None, Some(pipe)) => {
let _ = std::fs::remove_file(pipe);
let n = CString::new(pipe.as_str()).unwrap();
let status = unsafe { libc::mkfifo(n.as_ptr(), 0o644) };
if status != 0 {
panic!("Error creating fifo: {}", status);
}
let f = OpenOptions::new()
.write(true)
.open(pipe)
.expect("Error opening PCAP pipe");
Some(f)
}
(None, None) => None,
_ => unimplemented!(),
};
#[cfg(any(feature = "log", feature = "defmt"))]
info!("pcap pipe open, awaiting connection");
let pcap_writer = match pcap_file {
None => None,
Some(f) => {
let mut h = PcapHeader::default();
h.datalink = DataLink::IEEE802_15_4;
let w = PcapWriter::with_header(h, f).expect("Error writing to PCAP file");
Some(w)
}
};
Ok(pcap_writer)
}
}
pub fn do_receive<T, I, E>(
radio: &mut T,
mut buff: &mut [u8],
options: ReceiveOptions,
) -> Result<usize, E>
where
T: Receive<Info = I, Error = E> + DelayNs,
I: std::fmt::Debug,
E: std::fmt::Debug,
{
let mut pcap_writer = options
.pcap_options
.open()
.expect("Error opening pcap file / pipe");
radio.start_receive()?;
loop {
if radio.check_receive(true)? {
let (n, i) = radio.get_received(&mut buff)?;
match std::str::from_utf8(&buff[0..n as usize]) {
Ok(s) => info!("Received: '{}' info: {:?}", s, i),
#[cfg(not(feature = "defmt"))]
Err(_) => info!("Received: '{:02x?}' info: {:?}", &buff[0..n as usize], i),
#[cfg(feature = "defmt")]
Err(_) => info!("Received: '{:?}' info: {:?}", &buff[0..n as usize], i),
}
if let Some(p) = &mut pcap_writer {
let t = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
p.write(
t.as_secs() as u32,
t.as_nanos() as u32 % 1_000_000,
&buff[0..n],
n as u32,
)
.expect("Error writing pcap file");
}
if !options.continuous {
return Ok(n);
}
radio.start_receive()?;
}
radio.delay_us(options.blocking_options.poll_interval.as_micros() as u32);
}
}
#[derive(Clone, Parser, PartialEq, Debug)]
pub struct RssiOptions {
#[clap(long = "period", default_value = "1s")]
pub period: HumanDuration,
#[clap(long = "continuous")]
pub continuous: bool,
}
pub fn do_rssi<T, I, E>(radio: &mut T, options: RssiOptions) -> Result<(), E>
where
T: Receive<Info = I, Error = E> + Rssi<Error = E> + DelayNs,
I: std::fmt::Debug,
E: std::fmt::Debug,
{
radio.start_receive()?;
loop {
let rssi = radio.poll_rssi()?;
info!("rssi: {}", rssi);
radio.check_receive(true)?;
radio.delay_us(options.period.as_micros() as u32);
if !options.continuous {
break;
}
}
Ok(())
}
#[derive(Clone, Parser, PartialEq, Debug)]
pub struct EchoOptions {
#[clap(long = "continuous")]
pub continuous: bool,
#[clap(long = "power")]
pub power: Option<i8>,
#[clap(long = "delay", default_value = "100ms")]
pub delay: HumanDuration,
#[clap(long = "append-info")]
pub append_info: bool,
#[clap(flatten)]
pub blocking_options: BlockingOptions,
}
pub fn do_echo<T, I, E>(
radio: &mut T,
mut buff: &mut [u8],
options: EchoOptions,
) -> Result<usize, BlockingError<E>>
where
T: Receive<Info = I, Error = E> + Transmit<Error = E> + Power<Error = E> + DelayNs,
I: ReceiveInfo + std::fmt::Debug,
E: std::fmt::Debug,
{
if let Some(p) = options.power {
radio.set_power(p)?;
}
radio.start_receive()?;
loop {
if radio.check_receive(true)? {
let (mut n, i) = radio.get_received(&mut buff)?;
match std::str::from_utf8(&buff[0..n as usize]) {
Ok(s) => info!("Received: '{}' info: {:?}", s, i),
#[cfg(not(feature = "defmt"))]
Err(_) => info!("Received: '{:02x?}' info: {:?}", &buff[0..n as usize], i),
#[cfg(feature = "defmt")]
Err(_) => info!("Received: '{:?}' info: {:?}", &buff[0..n as usize], i),
}
if options.append_info {
NetworkEndian::write_i16(&mut buff[n..], i.rssi());
n += 2;
}
radio.delay_us(options.delay.as_micros() as u32);
radio.do_transmit(&buff[..n], options.blocking_options.clone())?;
if !options.continuous {
return Ok(n);
}
}
radio.delay_us(options.blocking_options.poll_interval.as_micros() as u32);
}
}
#[derive(Clone, Parser, PartialEq, Debug)]
pub struct PingPongOptions {
#[clap(long, default_value = "100")]
pub rounds: u32,
#[clap(long)]
pub power: Option<i8>,
#[clap(long, default_value = "100ms")]
pub delay: HumanDuration,
#[clap(long)]
pub parse_info: bool,
#[clap(flatten)]
pub blocking_options: BlockingOptions,
}
pub struct LinkTestInfo {
pub sent: u32,
pub received: u32,
pub local_rssi: Stats<f32>,
pub remote_rssi: Stats<f32>,
}
pub fn do_ping_pong<T, I, E>(
radio: &mut T,
options: PingPongOptions,
) -> Result<LinkTestInfo, BlockingError<E>>
where
T: Receive<Info = I, Error = E> + Transmit<Error = E> + Power<Error = E> + DelayNs,
I: ReceiveInfo,
E: std::fmt::Debug,
{
let mut link_info = LinkTestInfo {
sent: options.rounds,
received: 0,
local_rssi: Stats::new(),
remote_rssi: Stats::new(),
};
let mut buff = [0u8; 32];
if let Some(p) = options.power {
radio.set_power(p)?;
}
for i in 0..options.rounds {
NetworkEndian::write_u32(&mut buff[0..], i as u32);
let n = 4;
debug!("Sending message {}", i);
radio.do_transmit(&buff[0..n], options.blocking_options.clone())?;
let (n, info) = match radio.do_receive(&mut buff, options.blocking_options.clone()) {
Ok(r) => r,
Err(BlockingError::Timeout) => {
debug!("Timeout awaiting response {}", i);
continue;
}
Err(e) => return Err(e),
};
let receive_index = NetworkEndian::read_u32(&buff[0..n]);
if receive_index != i {
debug!("Invalid receive index");
continue;
}
let remote_rssi = match options.parse_info {
true => Some(NetworkEndian::read_i16(&buff[4..n])),
false => None,
};
debug!(
"Received response {} with local rssi: {} and remote rssi: {:?}",
receive_index,
info.rssi(),
remote_rssi
);
link_info.received += 1;
link_info.local_rssi.update(info.rssi() as f32);
if let Some(rssi) = remote_rssi {
link_info.remote_rssi.update(rssi as f32);
}
radio.delay_us(options.delay.as_micros() as u32);
}
Ok(link_info)
}