From c7c90a8ff37a2c7daf4adb5139661633a1bd2e3e Mon Sep 17 00:00:00 2001 From: John Goerzen Date: Sun, 20 Sep 2020 17:48:07 -0500 Subject: [PATCH] adding --- Cargo.lock | 1 + Cargo.toml | 1 + src/txpacket.rs | 108 +++++++++++++++++++ src/xb.rs | 278 ++++++------------------------------------------ 4 files changed, 142 insertions(+), 246 deletions(-) create mode 100644 src/txpacket.rs diff --git a/Cargo.lock b/Cargo.lock index 409a035..d2d9eb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -980,6 +980,7 @@ dependencies = [ name = "xbnet" version = "1.1.0" dependencies = [ + "bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "format_escape_default 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e8f23aa..4b7e437 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,4 @@ format_escape_default = "0.1.1" structopt = "0.3" tun-tap = "0.1.2" rustbee = "0.1.1" +bytes = "0.5" diff --git a/src/txpacket.rs b/src/txpacket.rs new file mode 100644 index 0000000..abfcc89 --- /dev/null +++ b/src/txpacket.rs @@ -0,0 +1,108 @@ +/*! XBee packet transmission */ +/* + Copyright (C) 2020 John Goerzen . + +*/ + +use bytes::*; + +/** XBee transmissions can give either a 64-bit or a 16-bit destination +address. This permits the user to select one. */ +#[derive(Eq, PartialEq, Debug)] +pub enum XBDestAddr { + /// A 16-bit destination address. When a 64-bit address is given, this is transmitted as 0xFFFE. + U16(u16), + + /// The 64-bit destination address. 0xFFFF for broadcast. + /// When a 16-bit destination is given, this will be transmitted as 0xFFFFFFFFFFFFFFFF. + U64(u64) +} + +/** Possible errors from serialization */ +#[derive(Eq, PartialEq, Debug)] +pub enum TXGenError { + /// The payload was an invalid length + InvalidLen +} + +/** A Digi 64-bit transmit request, frame type 0x10 */ +#[derive(Eq, PartialEq, Debug)] +pub struct XBTXRequest<'a> { + /// The frame ID, which will be returned in the subsequent response frame. + /// Set to 0 to disable a response for this transmission. + pub frame_id: u8, + + /// The destination address + pub dest_addr: XBDestAddr, + + /// The number of hops a broadcast transmission can traverse. When 0, the value if NH is used. + pub broadcast_radius: u8, + + /// Transmit options bitfield. When 0, uses the TO setting. + pub transmit_options: u8, + + /// The payload + pub payload: &'a [u8], +} + +impl XBTXRequest { + pub fn serialize(&self) -> Result { + if self.payload.is_empty() { + return Err(TXGenError::InvalidLen); + } + + // We generate the bits that are outside the length & checksum parts, then the + // inner parts, then combine them. + let mut fullframe = BytesMut::new(); + + fullframe.put_u8(0x7e); // Start delimeter + + let mut innerframe = BytesMut::new(); + // Frame type + innerframe.put_u8(0x10); + + innerframe.put_u8(self.frame_id); + match self.dest_addr { + XBDestAddr::U16(dest) => { + innerframe.put_u64(0xFFFFFFFFFFFFFFFFu64); + innerframe.put_u16(dest); + }, + XBDestAddr::U64(dest) => { + innerframe.put_u64(dest); + innerframe.put_u16(0xFFFEu16); + } + }; + + innerframe.put_u8(self.broadcast_radius); + innerframe.put_u8(self.transmit_options); + innerframe.put_slice(self.payload); + + // That's it for the inner frame. Now fill in the outer frame. + if let Some(lenu16) = u16::try_from(self.payload.len()) { + fullframe.put_u16(lenu16); + fullframe.put_slice(self.innerframe); + fullframe.put_u8(xbchecksum(self.innerframe)); + } else { + Err(TXGenError::InvalidLen) + } + } +} + +/// Calculate an XBee checksum over a slice +pub fn xbchecksum(data: &[u8]) -> u8 { + let sumu64 = data.into_iter().map(|x| u64::from(x)).sum(); + 0xffu8 - (sumu64 as u8) +} diff --git a/src/xb.rs b/src/xb.rs index ae7a964..e8fba8d 100644 --- a/src/xb.rs +++ b/src/xb.rs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019 John Goerzen (XB, crossbeam_channel::Receiver) { - let (readerlinestx, readerlinesrx) = crossbeam_channel::unbounded(); - let (txblockstx, txblocksrx) = crossbeam_channel::bounded(2); - let (readeroutput, readeroutputreader) = crossbeam_channel::unbounded(); + pub fn new(ser: XBSer) -> (XB, crossbeam_channel::Receiver) { + + debug!("Configuring radio"); + thread::sleep(Duration::from_msecs(1100)); + ser.swrite.lock().unwrap().write_all(b"+++")?; + ser.swrite.lock().unwrap().flush(); + + assert_response(ser.readln()?, "OK"); + + // Enter API mode + ser.writeln("ATAP 1")?; + assert_response(ser.readln()?, "OK"); + + // Standard API output mode + ser.writeln("ATAO 0")?; + assert_response(ser.readln()?, "OK"); + + // Get our own MAC address + ser.writeln("ATSH")?; + let serialhigh = ser.readln()?; + + ser.writeln("ATSL")?; + let seriallow = ser.readln()?; + + // Get maximum packet size + ser.writeln("ATNP")?; + let maxpacket = ser.readln()?; + + // Exit command mode + ser.writeln("ATCN")?; + assert_response(ser.readln()?, "OK"); let ser2 = ser.clone(); - thread::spawn(move || readerlinesthread(ser2, readerlinestx)); - (XB { readqual, ser, readeroutput, readerlinesrx, txblockstx, txblocksrx, maxpacketsize, pack, txdelay: None, txwait: Duration::from_millis(txwait), @@ -134,245 +159,6 @@ impl XB { extradata: vec![]}, readeroutputreader) } - /// Utility to read the response from initialization - fn initresp(&mut self) -> io::Result<()> { - let line = self.readerlinesrx.recv().unwrap(); - if line == "invalid_param" { - Err(mkerror("Bad response from radio during initialization")) - } else { - Ok(()) - } - } - - pub fn radiocfg(&mut self, initfile: Option) -> io::Result<()> { - // First, send it an invalid command. Then, consume everything it sends back - self.ser.writeln(String::from("INVALIDCOMMAND"))?; - - // Give it a chance to do its thing. - thread::sleep(Duration::from_secs(1)); - - // Consume all data. - while let Ok(_) = self.readerlinesrx.try_recv() { - } - - debug!("Configuring radio"); - let default = vec![ - "sys get ver", - "mac reset", - "mac pause", - "radio get mod", - "radio get freq", - "radio get pwr", - "radio get sf", - "radio get bw", - "radio get cr", - "radio get wdt", - "radio set pwr 20", - "radio set sf sf12", - "radio set bw 125", - "radio set cr 4/5", - "radio set wdt 60000"]; - - let initlines: Vec = if let Some(file) = initfile { - let f = fs::File::open(file)?; - let reader = BufReader::new(f); - reader.lines().map(|l| l.unwrap()).collect() - } else { - default.iter().map(|l| String::from(*l)).collect() - }; - - for line in initlines { - if line.len() > 0 { - self.ser.writeln(line)?; - self.initresp()?; - } - } - Ok(()) - } - - /// Utililty function to handle actual sending. Assumes radio is idle. - fn dosend(&mut self, data: Vec) -> io::Result<()> { - let mut tosend = vec![]; - tosend.append(&mut self.extradata); // drains self.extradata! - tosend.append(&mut data.clone()); - let mut data = tosend; // hide the original 'data' - - if data.len() > self.maxpacketsize { - self.extradata = data.split_off(self.maxpacketsize); - } - - while data.len() < self.maxpacketsize && self.extradata.is_empty() { - // Consider the next packet - maybe we can combine it with this one. - let r = self.txblocksrx.try_recv(); - match r { - Ok(mut next) => { - if self.pack { - // Try to fill up the frame. - data.append(&mut next); - if data.len() > self.maxpacketsize { - // Too much; put the extra into extradata. - self.extradata = data.split_off(self.maxpacketsize); - break; // for clarity only -- would exit the loop anyhow - } - } else { - // Only append the extra if it will fit entirely in the frame. - if data.len() + next.len() <= self.maxpacketsize { - data.append(&mut next); - } else { - self.extradata.append(&mut next); - break; // for clarity only -- would exit the loop anyhow - } - } - }, - Err(e) => { - if e.is_disconnected() { - // other threads crashed - r.unwrap(); - } - // Otherwise - nothing to do - break; - } - } - } - - let mut flag: u8 = 0; - - // Give receiver a change to process. - thread::sleep(self.txwait); - - if (!self.txblocksrx.is_empty()) || (!self.extradata.is_empty()) { - // If there will be more data to send.. - flag = 1; - - // See if we need to signal the other end's turn. - match (self.txslotend, self.txslot) { - (None, Some(txslot)) => self.txslotend = Some(Instant::now() + txslot), - (Some(txslotend), _) => - if Instant::now() > txslotend { - debug!("txslot exceeded; setting txdelay and sending flag 2"); - flag = 2; - self.txdelay = Some(Instant::now() + self.eotwait); - self.txslotend = None; - }, - _ => () - } - } else { - self.txslotend = None; - } - - // Now, send the mesage. - let txstr = format!("radio tx {}{}", hex::encode([flag]), hex::encode(data)); - - self.ser.writeln(txstr)?; - - // We get two responses from this.... though sometimes a lingering radio_err also. - let mut resp = self.readerlinesrx.recv().unwrap(); - if resp == String::from("radio_err") { - resp = self.readerlinesrx.recv().unwrap(); - } - assert_response(resp, String::from("ok"))?; - - // Second. - self.readerlinesrx.recv().unwrap(); // normally radio_tx_ok - - Ok(()) - } - - // Receive a message from the incoming radio channel and process it. - fn handlerx(&mut self, msg: String, readqual: bool) -> io::Result<()> { - if msg.starts_with("radio_rx ") { - if let Ok(mut decoded) = hex::decode(&msg.as_bytes()[10..]) { - trace!("DECODED: {}", format_escape_default(&decoded)); - let radioqual = if readqual { - self.ser.writeln(String::from("radio get snr"))?; - let snr = self.readerlinesrx.recv().unwrap(); - self.ser.writeln(String::from("radio get rssi"))?; - let rssi = self.readerlinesrx.recv().unwrap(); - Some((snr, rssi)) - } else { - None - }; - - let flag = decoded.remove(0); // Remove the flag from the vec - if flag == 1 { - // More data is coming - self.txdelay = Some(Instant::now() + self.eotwait); - } else { - self.txdelay = None; - } - debug!("handlerx: txdelay set to {:?}", self.txdelay); - - self.readeroutput.send(ReceivedFrames(decoded, radioqual)).unwrap(); - - if flag == 2 && self.txslot != None { - // Other end has more data, but it giving us a chance to transmit. - // Need to immediately send something. dosend() will pick up - // self.extradata or self.txblocksrx to fill up the frame if it can. - self.dosend(vec![])?; - } - } else { - return Err(mkerror("Error with hex decoding")); - } - } - - // Might get radio_err here. That's harmless. - Ok(()) - } - - // Whether or not a txdelay prevents transmit at this time. None if - // we are cleared to transmit; Some(Duration) gives the amount of time - // we'd have to wait otherwise. - fn txdelayrequired(&mut self) -> Option { - debug!("txdelayrequired: self.txdelay = {:?}", self.txdelay); - match self.txdelay { - None => None, - Some(delayend) => { - let now = Instant::now(); - if now >= delayend { - // We're past the delay. Clear it and return. - debug!("txdelayrequired: {:?} past the required delay {:?}", now, delayend); - self.txdelay = None; - None - } else { - // Indicate we're still blocked. - debug!("txdelayrequired: required delay {:?}", delayend - now); - Some(delayend - now) - } - } - } - } - - fn enterrxmode(&mut self) -> io::Result<()> { - // Enter read mode - - self.ser.writeln(String::from("radio rx 0"))?; - let mut response = self.readerlinesrx.recv().unwrap(); - - // For some reason, sometimes we get a radio_err here, then an OK. Ignore it. - if response == String::from("radio_err") { - response = self.readerlinesrx.recv().unwrap(); - } - assert_response(response, String::from("ok"))?; - Ok(()) - } - - fn rxstop(&mut self) -> io::Result<()> { - self.ser.writeln(String::from("radio rxstop"))?; - let checkresp = self.readerlinesrx.recv().unwrap(); - if checkresp.starts_with("radio_rx ") { - // We had a race. A packet was coming in. Decode and deal with it, - // then look for the 'ok' from rxstop. We can't try to read the quality in - // this scenario. - self.handlerx(checkresp, false)?; - self.readerlinesrx.recv().unwrap(); // used to pop this into checkresp, but no need now. - } - - // Now, checkresp should hold 'ok'. - // It might not be; I sometimes see radio_err here. it's OK too. - // assert_response(checkresp, String::from("ok"))?; - Ok(()) - } - pub fn mainloop(&mut self) -> io::Result<()> { loop { // First, check to see if we're allowed to transmit. If not, just