diff --git a/.travis.yml b/.travis.yml index ebf5457..946ebd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ matrix: allow_failures: - rust: nightly fast_finish: true +before_script: + - rustup component add rustfmt-preview + - which rustfmt script: - cargo test --verbose --workspace - cargo test --release --verbose --workspace diff --git a/ublox/Cargo.toml b/ublox/Cargo.toml index fe60dd5..0460905 100644 --- a/ublox/Cargo.toml +++ b/ublox/Cargo.toml @@ -11,12 +11,12 @@ default = [] serial = ["serialport", "crc"] [dependencies] -serde = "1.0" -serde_derive = "1.0" -bincode = "1.2.1" chrono = "0.4" -ublox_derive = "0.0.0" +ublox_derive = "0.0.1" serialport = { version = "3.3.0", optional = true } crc = { version = "1.8.1", optional = true } -num-derive = "0.3.0" -num-traits = "0.2.11" +bitflags = "1.2.1" + +[dev-dependencies] +rand = "0.7.3" +cpu-time = "1.0.0" diff --git a/ublox/examples/serial.rs b/ublox/examples/serial.rs index ad0d7e1..6086bcb 100644 --- a/ublox/examples/serial.rs +++ b/ublox/examples/serial.rs @@ -6,24 +6,23 @@ mod serial { pub fn main() { let mut dev = Device::new("/dev/ttyUSB0").unwrap(); - - let pos = Position { - lon: -97.5, - lat: 30.2, - alt: 200.0, - }; - println!("Setting AID data..."); - match dev.load_aid_data(Some(pos), Some(Utc::now())) { - Err(e) => { - println!("Got error setting AID data: {:?}", e); - } - _ => {} - } - + /* + let pos = Position { + lon: -97.5, + lat: 30.2, + alt: 200.0, + }; + println!("Setting AID data..."); + match dev.load_aid_data(Some(pos), Some(Utc::now())) { + Err(e) => { + println!("Got error setting AID data: {:?}", e); + } + _ => {} + } loop { dev.poll_for(Duration::from_millis(500)).unwrap(); println!("{:?}", dev.get_solution()); - } + } */ } } diff --git a/ublox/src/error.rs b/ublox/src/error.rs index d979b06..5ab827d 100644 --- a/ublox/src/error.rs +++ b/ublox/src/error.rs @@ -1,25 +1,68 @@ -use std::convert; -use std::io; +use std::fmt; #[derive(Debug)] -pub enum Error { - InvalidChecksum, - UnexpectedPacket, - TimedOutWaitingForAck(u8, u8), - IoError(io::Error), - BincodeError(bincode::Error), +pub enum MemWriterError +where + E: std::error::Error, +{ + NotEnoughMem, + Custom(E), } -impl convert::From for Error { - fn from(error: io::Error) -> Self { - Error::IoError(error) +impl fmt::Display for MemWriterError +where + E: std::error::Error, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MemWriterError::NotEnoughMem => f.write_str("Not enough memory error"), + MemWriterError::Custom(e) => write!(f, "MemWriterError: {}", e), + } } } -impl convert::From for Error { - fn from(error: bincode::Error) -> Self { - Error::BincodeError(error) +impl std::error::Error for MemWriterError where E: std::error::Error {} + +/// Error that possible during packets parsing +#[derive(Debug, PartialEq)] +pub enum ParserError { + InvalidChecksum { + expect: u16, + got: u16, + }, + InvalidField { + packet: &'static str, + field: &'static str, + }, + InvalidPacketLen { + packet: &'static str, + expect: usize, + got: usize, + }, +} + +impl fmt::Display for ParserError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParserError::InvalidChecksum { expect, got } => write!( + f, + "Not valid packet's checksum, expect {:x}, got {:x}", + expect, got + ), + ParserError::InvalidField { packet, field } => { + write!(f, "Invalid field {} of packet {}", field, packet) + } + ParserError::InvalidPacketLen { + packet, + expect, + got, + } => write!( + f, + "Invalid packet({}) length, expect {}, got {}", + packet, expect, got + ), + } } } -pub type Result = std::result::Result; +impl std::error::Error for ParserError {} diff --git a/ublox/src/lib.rs b/ublox/src/lib.rs index b7d68c1..fbe2045 100644 --- a/ublox/src/lib.rs +++ b/ublox/src/lib.rs @@ -4,13 +4,16 @@ //! At time of writing this library is developed for a device which behaves like //! a NEO-6M device. -pub use crate::segmenter::Segmenter; #[cfg(feature = "serial")] pub use crate::serialport::{Device, ResetType}; -pub use crate::ubx_packets::*; +pub use crate::{ + error::{MemWriterError, ParserError}, + parser::{Parser, ParserIter}, + ubx_packets::*, +}; mod error; -mod segmenter; +mod parser; #[cfg(feature = "serial")] mod serialport; mod ubx_packets; diff --git a/ublox/src/parser.rs b/ublox/src/parser.rs new file mode 100644 index 0000000..0a75e41 --- /dev/null +++ b/ublox/src/parser.rs @@ -0,0 +1,122 @@ +use crate::{ + error::ParserError, + ubx_packets::{ + match_packet, ubx_checksum, PacketRef, MAX_PAYLOAD_LEN, SYNC_CHAR_1, SYNC_CHAR_2, + }, +}; + +/// Streaming parser for UBX protocol with buffer +#[derive(Default)] +pub struct Parser { + buf: Vec, +} + +impl Parser { + pub fn is_buffer_empty(&self) -> bool { + self.buf.is_empty() + } + + pub fn buffer_len(&self) -> usize { + self.buf.len() + } + + pub fn consume(&mut self, new_data: &[u8]) -> ParserIter { + match self + .buf + .iter() + .chain(new_data.iter()) + .position(|x| *x == SYNC_CHAR_1) + { + Some(mut off) => { + if off >= self.buf.len() { + off -= self.buf.len(); + self.buf.clear(); + self.buf.extend_from_slice(&new_data[off..]); + off = 0; + } else { + self.buf.extend_from_slice(new_data); + } + ParserIter { + buf: &mut self.buf, + off, + } + } + None => { + self.buf.clear(); + ParserIter { + buf: &mut self.buf, + off: 0, + } + } + } + } +} + +/// Iterator over data stored in `Parser` buffer +pub struct ParserIter<'a> { + buf: &'a mut Vec, + off: usize, +} + +impl<'a> Drop for ParserIter<'a> { + fn drop(&mut self) { + if self.off <= self.buf.len() { + self.buf.drain(0..self.off); + } + } +} + +impl<'a> ParserIter<'a> { + /// Analog of `core::iter::Iterator::next`, should be switched to + /// trait implmentation after merge of https://github.com/rust-lang/rust/issues/44265 + pub fn next(&mut self) -> Option> { + while self.off < self.buf.len() { + let data = &self.buf[self.off..]; + let pos = data.iter().position(|x| *x == SYNC_CHAR_1)?; + let maybe_pack = &data[pos..]; + + if maybe_pack.len() <= 1 { + return None; + } + if maybe_pack[1] != SYNC_CHAR_2 { + self.off += pos + 2; + continue; + } + + if maybe_pack.len() <= 5 { + return None; + } + + let pack_len: usize = u16::from_le_bytes([maybe_pack[4], maybe_pack[5]]).into(); + if pack_len > usize::from(MAX_PAYLOAD_LEN) { + self.off += pos + 2; + continue; + } + if (pack_len + 6 + 2) > maybe_pack.len() { + return None; + } + let (ck_a, ck_b) = ubx_checksum(&maybe_pack[2..(4 + pack_len + 2)]); + + let (expect_ck_a, expect_ck_b) = + (maybe_pack[6 + pack_len], maybe_pack[6 + pack_len + 1]); + if (ck_a, ck_b) != (expect_ck_a, expect_ck_b) { + self.off += pos + 2; + return Some(Err(ParserError::InvalidChecksum { + expect: u16::from_le_bytes([expect_ck_a, expect_ck_b]), + got: u16::from_le_bytes([ck_a, ck_b]), + })); + } + let msg_data = &maybe_pack[6..(6 + pack_len)]; + let class_id = maybe_pack[2]; + let msg_id = maybe_pack[3]; + self.off += pos + 6 + pack_len + 2; + return Some(match_packet(class_id, msg_id, msg_data)); + } + None + } +} + +#[test] +fn test_max_payload_len() { + assert!(MAX_PAYLOAD_LEN >= 1240); +} diff --git a/ublox/src/segmenter.rs b/ublox/src/segmenter.rs deleted file mode 100644 index 6715fba..0000000 --- a/ublox/src/segmenter.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::error::{Error, Result}; -use crate::ubx_packets::Packet; -use std::collections::VecDeque; - -fn segment_stream(data: &mut VecDeque) -> Result> { - loop { - match (data.get(0), data.get(1)) { - (Some(0xb5), Some(0x62)) => { - break; - } - (_, None) => { - return Ok(None); - } - (Some(_), Some(_)) => { - data.pop_front(); - } - (None, Some(_)) => { - panic!("This should never happen!"); - } - } - } - if data.len() < 6 { - return Ok(None); - } - - // Check length - let length = (data[4] as u16 + data[5] as u16 * 256) as usize; - if data.len() < length + 8 { - return Ok(None); - } - - // Check checksum - let expected_checksum = data[length + 6] as u16 + data[length + 7] as u16 * 256; - - let data: Vec = data.drain(..length + 8).collect(); - let mut cka = 0; - let mut ckb = 0; - for c in &data[2..length + 6] { - cka = ((cka as usize + *c as usize) & 0xFF) as u8; - ckb = ((cka as usize + ckb as usize) & 0xFF) as u8; - } - let checksum = cka as u16 + ckb as u16 * 256; - if checksum != expected_checksum { - return Err(Error::InvalidChecksum); - } - - // Parse - let classid = data[2]; - let msgid = data[3]; - let data = &data[6..length + 6]; - Ok(Some(Packet::deserialize(classid, msgid, data)?)) -} - -pub struct Segmenter { - buffer: VecDeque, -} - -impl Segmenter { - pub fn new() -> Self { - Segmenter { - buffer: VecDeque::new(), - } - } - - pub fn consume(&mut self, data: &[u8]) -> Result> { - for c in data { - self.buffer.push_back(*c); - } - segment_stream(&mut self.buffer) - } - - pub fn consume_all(&mut self, data: &[u8]) -> Result> { - for c in data { - self.buffer.push_back(*c); - } - let mut packets = vec![]; - loop { - match segment_stream(&mut self.buffer)? { - Some(packet) => { - packets.push(packet); - } - None => { - return Ok(packets); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ubx_packets::AckAck; - - #[test] - fn segmentation_works() { - let mut buf: VecDeque = - vec![0xb5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x02, 0x03, 0x0d, 0x32] - .into_iter() - .collect(); - let res = segment_stream(&mut buf); - assert_eq!( - res.unwrap().unwrap(), - Packet::AckAck(AckAck { - classid: 0x02, - msgid: 0x03, - }) - ); - assert_eq!(buf.len(), 0); - } - - #[test] - fn segmentation_skips_to_sync_leaves_extras() { - let mut buf: VecDeque = vec![ - 0x64, 0x12, 0x06, 0xb5, 0x01, 0x62, 0xb5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x02, 0x03, - 0x0d, 0x32, 0x1, 0x2, 0x3, - ] - .into_iter() - .collect(); - let res = segment_stream(&mut buf); - assert_eq!( - res.unwrap().unwrap(), - Packet::AckAck(AckAck { - classid: 0x02, - msgid: 0x03, - }) - ); - assert_eq!(buf.len(), 3); - } - - #[test] - fn consume_all_consumes_all() { - let buf = vec![ - 0x64, 0x12, 0x06, 0xb5, 0x01, 0x62, 0xb5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x02, 0x03, - 0x0d, 0x32, 0x1, 0x2, 0x3, - ]; - let mut buf2 = vec![]; - buf2.extend_from_slice(&buf[..]); - buf2.extend_from_slice(&buf[..]); - let buf = buf2; - - let mut p = Segmenter::new(); - - let res = p.consume_all(&buf).unwrap(); - assert_eq!(res.len(), 2); - assert_eq!( - res[0], - Packet::AckAck(AckAck { - classid: 0x02, - msgid: 0x03, - }) - ); - assert_eq!( - res[1], - Packet::AckAck(AckAck { - classid: 0x02, - msgid: 0x03, - }) - ); - } -} diff --git a/ublox/src/serialport.rs b/ublox/src/serialport.rs index 165835c..da5ce81 100644 --- a/ublox/src/serialport.rs +++ b/ublox/src/serialport.rs @@ -1,6 +1,6 @@ use crate::{ - error::{Error, Result}, - segmenter::Segmenter, + error::ParserError, + parser::{Parser, ParserIter}, ubx_packets::*, }; use chrono::prelude::*; @@ -8,7 +8,7 @@ use crc::{crc16, Hasher16}; use std::io; use std::time::{Duration, Instant}; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum ResetType { /// The fastest, clears only the SV data. Hot, @@ -20,17 +20,42 @@ pub enum ResetType { Cold, } +#[derive(Debug)] +pub enum Error { + InvalidChecksum, + UnexpectedPacket, + TimedOutWaitingForAck(u8, u8), + IoError(io::Error), + ParserError(ParserError), +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::IoError(error) + } +} + +impl From for Error { + fn from(e: ParserError) -> Self { + Self::ParserError(e) + } +} + +type Result = std::result::Result; + pub struct Device { port: Box, - segmenter: Segmenter, + ubx_parser: Parser, //buf: Vec, alp_data: Vec, alp_file_id: u16, - - navpos: Option, - navvel: Option, + /* + TODO: should be something like Position, Velocity, DateTime here, + why data copy? + navpos: Option, + navvel: Option, navstatus: Option, - solution: Option, + solution: Option, */ } impl Device { @@ -63,13 +88,14 @@ impl Device { let port = serialport::open_with_settings(device, &s).unwrap(); let mut dev = Device { port: port, - segmenter: Segmenter::new(), + ubx_parser: Parser::default(), alp_data: Vec::new(), alp_file_id: 0, + /* navpos: None, navvel: None, navstatus: None, - solution: None, + solution: None,*/ }; dev.init_protocol()?; @@ -78,9 +104,9 @@ impl Device { fn init_protocol(&mut self) -> Result<()> { // Disable NMEA output in favor of the UBX protocol - self.send( - CfgPrtUart { - portid: 1, + self.port.write_all( + &CfgPrtUartBuilder { + portid: UartPortId::Uart1, reserved0: 0, tx_ready: 0, mode: 0x8d0, @@ -90,57 +116,54 @@ impl Device { flags: 0, reserved5: 0, } - .into(), + .into_packet_bytes(), )?; // Eat the acknowledge and let the device start - self.wait_for_ack(0x06, 0x00)?; - - self.enable_packet(0x01, 0x07)?; // Nav pos vel time + self.wait_for_ack::()?; + self.enable_packet::()?; // Nav pos vel time // Go get mon-ver - self.send(UbxPacket { - class: 0x0A, - id: 0x04, - payload: vec![], - })?; + self.port + .write_all(&UbxPacketRequest::request_for::().into_packet_bytes())?; self.poll_for(Duration::from_millis(200))?; - Ok(()) } - fn enable_packet(&mut self, classid: u8, msgid: u8) -> Result<()> { - self.send( - CfgMsg { - classid: classid, - msgid: msgid, - rates: [0, 1, 0, 0, 0, 0], - } - .into(), + fn enable_packet(&mut self) -> Result<()> { + self.port.write_all( + &CfgMsgAllPortsBuilder::set_rate_for::([0, 1, 0, 0, 0, 0]).into_packet_bytes(), )?; - self.wait_for_ack(0x06, 0x01)?; + self.wait_for_ack::()?; Ok(()) } - fn wait_for_ack(&mut self, classid: u8, msgid: u8) -> Result<()> { + fn wait_for_ack(&mut self) -> Result<()> { let now = Instant::now(); while now.elapsed() < Duration::from_millis(1_000) { - match self.get_next_message()? { - Some(Packet::AckAck(packet)) => { - if packet.classid != classid || packet.msgid != msgid { - panic!("Expecting ack, got ack for wrong packet!"); + let mut it = self.recv()?; + match it { + Some(mut it) => { + while let Some(pack) = it.next() { + match pack { + Ok(PacketRef::AckAck(ack_ack)) => { + if ack_ack.class() != T::CLASS || ack_ack.msg_id() != T::ID { + eprintln!("Expecting ack, got ack for wrong packet!"); + return Err(Error::UnexpectedPacket); + } + return Ok(()); + } + Ok(_) => return Err(Error::UnexpectedPacket), + Err(err) => return Err(err.into()), + } } - return Ok(()); - } - Some(_) => { - return Err(Error::UnexpectedPacket); } None => { // Keep waiting } } } - return Err(Error::TimedOutWaitingForAck(classid, msgid)); + return Err(Error::TimedOutWaitingForAck(T::CLASS, T::ID)); } /// Runs the processing loop for the given amount of time. You must run the @@ -160,74 +183,77 @@ impl Device { self.get_next_message()?; Ok(()) } - - /// DO NOT USE. Use get_solution instead. - pub fn get_position(&mut self) -> Option { - match (&self.navstatus, &self.navpos) { - (Some(status), Some(pos)) => { - if status.itow != pos.get_itow() { - None - } else if status.flags & 0x1 == 0 { - None - } else { - Some(pos.into()) + /* + /// DO NOT USE. Use get_solution instead. + pub fn get_position(&mut self) -> Option { + match (&self.navstatus, &self.navpos) { + (Some(status), Some(pos)) => { + if status.itow != pos.get_itow() { + None + } else if status.flags & 0x1 == 0 { + None + } else { + Some(pos.into()) + } } + _ => None, } - _ => None, } - } - /// DO NOT USE. Use get_solution instead. - pub fn get_velocity(&mut self) -> Option { - match (&self.navstatus, &self.navvel) { - (Some(status), Some(vel)) => { - if status.itow != vel.get_itow() { - None - } else if status.flags & 0x1 == 0 { - None - } else { - Some(vel.into()) + /// DO NOT USE. Use get_solution instead. + pub fn get_velocity(&mut self) -> Option { + match (&self.navstatus, &self.navvel) { + (Some(status), Some(vel)) => { + if status.itow != vel.get_itow() { + None + } else if status.flags & 0x1 == 0 { + None + } else { + Some(vel.into()) + } } + _ => None, } - _ => None, } - } - /// Returns the most recent solution, as a tuple of position/velocity/time - /// options. Note that a position may have any combination of these, - /// including none of them - if no solution has been returned from the - /// device, all fields will be None. - pub fn get_solution(&mut self) -> (Option, Option, Option>) { - match &self.solution { - Some(sol) => { - let has_time = sol.fix_type == 0x03 || sol.fix_type == 0x04 || sol.fix_type == 0x05; - let has_posvel = sol.fix_type == 0x03 || sol.fix_type == 0x04; - let pos = if has_posvel { Some(sol.into()) } else { None }; - let vel = if has_posvel { Some(sol.into()) } else { None }; - let time = if has_time { Some(sol.into()) } else { None }; - (pos, vel, time) + /// Returns the most recent solution, as a tuple of position/velocity/time + /// options. Note that a position may have any combination of these, + /// including none of them - if no solution has been returned from the + /// device, all fields will be None. + pub fn get_solution(&mut self) -> (Option, Option, Option>) { + match &self.solution { + Some(sol) => { + let has_time = sol.fix_type == 0x03 || sol.fix_type == 0x04 || sol.fix_type == 0x05; + let has_posvel = sol.fix_type == 0x03 || sol.fix_type == 0x04; + let pos = if has_posvel { Some(sol.into()) } else { None }; + let vel = if has_posvel { Some(sol.into()) } else { None }; + let time = if has_time { Some(sol.into()) } else { None }; + (pos, vel, time) + } + None => (None, None, None), } - None => (None, None, None), } - } - + */ /// Performs a reset of the device, and waits for the device to fully reset. - pub fn reset(&mut self, temperature: &ResetType) -> Result<()> { - match temperature { - ResetType::Hot => { - self.send(CfgRst::HOT.into())?; - } - ResetType::Warm => { - self.send(CfgRst::WARM.into())?; - } - ResetType::Cold => { - self.send(CfgRst::COLD.into())?; + pub fn reset(&mut self, temperature: ResetType) -> Result<()> { + let reset_mask = match temperature { + ResetType::Hot => NavBbrPredefinedMask::HOT_START, + ResetType::Warm => NavBbrPredefinedMask::WARM_START, + ResetType::Cold => NavBbrPredefinedMask::COLD_START, + }; + + self.port.write_all( + &CfgRstBuilder { + nav_bbr_mask: reset_mask.into(), + reset_mode: ResetMode::ControlledSoftwareReset, + reserved1: 0, } - } + .into_packet_bytes(), + )?; // Clear our device state - self.navpos = None; - self.navstatus = None; + // self.navpos = None; + // self.navstatus = None; // Wait a bit for it to reset // (we can't wait for the ack, because we get a bad checksum) @@ -241,153 +267,148 @@ impl Device { self.init_protocol()?; Ok(()) } - - /// If the position and time are known, you can pass them to the GPS device - /// on startup using this method. - /// - /// # Errors - /// - /// Will throw an error if there is an error sending the packet. - /// - /// # Panics - /// - /// Panics if there is an issue serializing the message. - pub fn load_aid_data( - &mut self, - position: Option, - tm: Option>, - ) -> Result<()> { - let mut aid = AidIni::new(); - match position { - Some(pos) => { - aid.set_position(pos); - } - _ => {} + /* + /// If the position and time are known, you can pass them to the GPS device + /// on startup using this method. + /// + /// # Errors + /// + /// Will throw an error if there is an error sending the packet. + /// + /// # Panics + /// + /// Panics if there is an issue serializing the message. + pub fn load_aid_data( + &mut self, + position: Option, + tm: Option>, + ) -> Result<()> { + let mut aid = AidIni::new(); + match position { + Some(pos) => { + aid.set_position(pos); + } + _ => {} + }; + match tm { + Some(tm) => { + aid.set_time(tm); + } + _ => {} + }; + + self.send(UbxPacket { + class: 0x0B, + id: 0x01, + payload: bincode::serialize(&aid).unwrap(), + })?; + Ok(()) + } + + /// DO NOT USE. Experimental! + pub fn set_alp_offline(&mut self, data: &[u8]) -> Result<()> { + self.alp_data = vec![0; data.len()]; + self.alp_data.copy_from_slice(data); + + let mut digest = crc16::Digest::new(crc16::X25); + digest.write(&self.alp_data); + self.alp_file_id = digest.sum16(); + + self.send(UbxPacket { + class: 0x06, + id: 0x01, + payload: vec![0x0B, 0x32, 0x01], + })?; + self.wait_for_ack(0x06, 0x01)?; + Ok(()) + } + + */ + + fn get_next_message(&mut self) -> Result> { + let mut it = match self.recv()? { + Some(it) => it, + None => return Ok(None), }; - match tm { - Some(tm) => { - aid.set_time(tm); - } - _ => {} - }; - - self.send(UbxPacket { - class: 0x0B, - id: 0x01, - payload: bincode::serialize(&aid).unwrap(), - })?; - Ok(()) - } - - /// DO NOT USE. Experimental! - pub fn set_alp_offline(&mut self, data: &[u8]) -> Result<()> { - self.alp_data = vec![0; data.len()]; - self.alp_data.copy_from_slice(data); - - let mut digest = crc16::Digest::new(crc16::X25); - digest.write(&self.alp_data); - self.alp_file_id = digest.sum16(); - - self.send(UbxPacket { - class: 0x06, - id: 0x01, - payload: vec![0x0B, 0x32, 0x01], - })?; - self.wait_for_ack(0x06, 0x01)?; - Ok(()) - } - - fn get_next_message(&mut self) -> Result> { - let packet = self.recv()?; - match packet { - Some(Packet::AckAck(packet)) => { - return Ok(Some(Packet::AckAck(packet))); - } - Some(Packet::MonVer(packet)) => { - println!( - "Got versions: SW={} HW={}", - packet.sw_version, packet.hw_version - ); - return Ok(None); - } - Some(Packet::NavPosVelTime(packet)) => { - self.solution = Some(packet); - return Ok(None); - } - Some(Packet::NavVelNED(packet)) => { - self.navvel = Some(packet); - return Ok(None); - } - Some(Packet::NavStatus(packet)) => { - self.navstatus = Some(packet); - return Ok(None); - } - Some(Packet::NavPosLLH(packet)) => { - self.navpos = Some(packet); - return Ok(None); - } - Some(Packet::AlpSrv(packet)) => { - if self.alp_data.len() == 0 { - // Uh-oh... we must be connecting to a device which was already in alp mode, let's just ignore it + while let Some(pack) = it.next() { + let pack = pack?; + + match pack { + PacketRef::MonVer(packet) => { + println!( + "Got versions: SW={} HW={}", + packet.software_version(), + packet.hardware_version() + ); + return Ok(None); + } + PacketRef::NavPosVelTime(packet) => { + // self.solution = Some(packet); + return Ok(None); + } + PacketRef::NavVelNed(packet) => { + // self.navvel = Some(packet); return Ok(None); } + PacketRef::NavStatus(packet) => { + // self.navstatus = Some(packet); + return Ok(None); + } + PacketRef::NavPosLlh(packet) => { + // self.navpos = Some(packet); + return Ok(None); + } + PacketRef::AlpSrv(packet) => { + /* + if alp_data.len() == 0 { + // Uh-oh... we must be connecting to a device which was already in alp mode, let's just ignore it + return Ok(None); + } - let offset = packet.offset as usize * 2; - let mut size = packet.size as usize * 2; - println!( - "Got ALP request for contents offset={} size={}", - offset, size - ); + let offset = packet.offset() as usize * 2; + let mut size = packet.size() as usize * 2; + println!( + "Got ALP request for contents offset={} size={}", + offset, size + ); + TODO: why we need clone? + let mut reply = packet.clone(); + reply.file_id = self.alp_file_id; + + if offset > self.alp_data.len() { + size = 0; + } else if offset + size > self.alp_data.len() { + size = self.alp_data.len() - reply.offset as usize; + } + reply.data_size = size as u16; - let mut reply = packet.clone(); - reply.file_id = self.alp_file_id; + //println!("Have {} bytes of data, ultimately requesting range {}..{}", self.alp_data.len(), offset, offset+size); - if offset > self.alp_data.len() { - size = 0; - } else if offset + size > self.alp_data.len() { - size = self.alp_data.len() - reply.offset as usize; - } - reply.data_size = size as u16; + TODO: have no idea why `AlpSrv` not used here + let contents = &self.alp_data[offset..offset + size]; + let mut payload = bincode::serialize(&reply).unwrap(); + for b in contents.iter() { + payload.push(*b); + } + //println!("Payload size: {}", payload.len()); + self.send(UbxPacket { + class: 0x0B, + id: 0x32, + payload: payload, + })?;*/ - //println!("Have {} bytes of data, ultimately requesting range {}..{}", self.alp_data.len(), offset, offset+size); - let contents = &self.alp_data[offset..offset + size]; - let mut payload = bincode::serialize(&reply).unwrap(); - for b in contents.iter() { - payload.push(*b); + return Ok(None); + } + _ => { + println!("Received packet"); + return Ok(None); } - //println!("Payload size: {}", payload.len()); - self.send(UbxPacket { - class: 0x0B, - id: 0x32, - payload: payload, - })?; - - return Ok(None); - } - Some(packet) => { - println!("Received packet {:?}", packet); - return Ok(None); - } - None => { - // Got nothing, do nothing - return Ok(None); } } + Ok(None) } - fn send(&mut self, packet: UbxPacket) -> Result<()> { - CfgMsg { - classid: 5, - msgid: 4, - rates: [0, 0, 0, 0, 0, 0], - } - .to_bytes(); - let serialized = packet.serialize(); - self.port.write_all(&serialized)?; - Ok(()) - } - - fn recv(&mut self) -> Result> { + fn recv(&mut self) -> Result> { // Read bytes until we see the header 0xB5 0x62 loop { let mut local_buf = [0; 1]; @@ -406,14 +427,9 @@ impl Device { return Ok(None); } - match self.segmenter.consume(&local_buf[..bytes_read])? { - Some(packet) => { - return Ok(Some(packet)); - } - None => { - // Do nothing - } - } + let it = self.ubx_parser.consume(&local_buf[..bytes_read]); + + return Ok(Some(it)); } } } diff --git a/ublox/src/ubx_packets.rs b/ublox/src/ubx_packets.rs index 1562b76..3f3f9af 100644 --- a/ublox/src/ubx_packets.rs +++ b/ublox/src/ubx_packets.rs @@ -1,517 +1,129 @@ -use crate::error::Result; -use bincode; -use chrono::prelude::*; -use serde_derive::{Deserialize, Serialize}; -use std::str; -use std::vec::Vec; -use ublox_derive::ubx_packet; - -// These are needed for ubx_packet -use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::{FromPrimitive, ToPrimitive}; -use std::convert::TryInto; - -#[derive(Debug)] -pub struct Position { - pub lon: f32, - pub lat: f32, - pub alt: f32, -} - -#[derive(Debug)] -pub struct Velocity { - pub speed: f32, // m/s over ground - pub heading: f32, // degrees -} - -#[derive(Debug)] -pub struct UbxPacket { - pub class: u8, - pub id: u8, - pub payload: Vec, -} - -impl UbxPacket { - pub fn serialize(&self) -> Vec { - let mut v = Vec::new(); - v.push(0xB5); - v.push(0x62); - v.push(self.class); - v.push(self.id); - - let length = self.payload.len() as u16; - v.push((length & 0xFF) as u8); - v.push(((length >> 8) & 0xFF) as u8); - - for b in self.payload.iter() { - v.push(*b); - } - - // Calculate the checksum - let mut cka = 0; - let mut ckb = 0; - for i in 0..self.payload.len() + 4 { - cka = ((cka as usize + v[i + 2] as usize) & 0xFF) as u8; - ckb = ((cka as usize + ckb as usize) & 0xFF) as u8; - } - v.push(cka); - v.push(ckb); - v - } - - fn compute_checksum(&self) -> (u8, u8) { - let s = self.serialize(); - let cka = s[s.len() - 2]; - let ckb = s[s.len() - 1]; - return (cka, ckb); - } - - pub fn check_checksum(&self, test_cka: u8, test_ckb: u8) -> bool { - let (cka, ckb) = self.compute_checksum(); - cka == test_cka && ckb == test_ckb +mod packets; +mod types; + +use crate::error::MemWriterError; +pub use packets::*; +pub use types::*; + +/// Information about concrete UBX protocol's packet +pub trait UbxPacketMeta { + const CLASS: u8; + const ID: u8; + const FIXED_PAYLOAD_LEN: Option; + const MAX_PAYLOAD_LEN: u16; +} + +pub(crate) const SYNC_CHAR_1: u8 = 0xb5; +pub(crate) const SYNC_CHAR_2: u8 = 0x62; + +/// The checksum is calculated over the packet, starting and including +/// the CLASS field, up until, but excluding, the checksum field. +/// So slice should starts with class id. +/// Return ck_a and ck_b +pub(crate) fn ubx_checksum(data: &[u8]) -> (u8, u8) { + let mut ck_a = 0_u8; + let mut ck_b = 0_u8; + for byte in data { + ck_a = ck_a.overflowing_add(*byte).0; + ck_b = ck_b.overflowing_add(ck_a).0; } + (ck_a, ck_b) } -pub trait UbxMeta { - fn get_classid() -> u8; - fn get_msgid() -> u8; - - fn to_bytes(&self) -> Vec; +/// For ubx checksum on the fly +#[derive(Default)] +struct UbxChecksumCalc { + ck_a: u8, + ck_b: u8, } -macro_rules! ubx_meta { - ($struct:ident, $classid:literal, $msgid:literal) => { - impl $struct { - fn get_classid() -> u8 { - $classid - } - - fn get_msgid() -> u8 { - $msgid - } - - pub fn to_bytes(&self) -> Vec { - let upacket: UbxPacket = self.into(); - upacket.serialize() - } - } - - impl From<&$struct> for UbxPacket { - fn from(packet: &$struct) -> UbxPacket { - UbxPacket { - class: $classid, - id: $msgid, - payload: bincode::serialize(packet).unwrap(), - } - } - } - - impl From<$struct> for UbxPacket { - fn from(packet: $struct) -> UbxPacket { - UbxPacket { - class: $classid, - id: $msgid, - payload: bincode::serialize(&packet).unwrap(), - } - } - } - }; -} - -#[ubx_packet] -pub struct NavPosLLH { - itow: u32, - lon: i32, - lat: i32, - height: i32, - height_msl: i32, - horizontal_accuracy: u32, - vertical_accuracy: u32, -} - -/*#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct NavPosLLH { - pub itow: u32, - pub lon: i32, - pub lat: i32, - pub height: i32, - pub height_msl: i32, - pub horizontal_accuracy: u32, - pub vertical_accuracy: u32, -}*/ - -//ubx_meta!(NavPosLLH, 0x01, 0x02); - -impl From<&NavPosLLH> for Position { - fn from(packet: &NavPosLLH) -> Self { - Position { - lon: packet.get_lon() as f32 / 10_000_000.0, - lat: packet.get_lat() as f32 / 10_000_000.0, - alt: packet.get_height_msl() as f32 / 1000.0, +impl UbxChecksumCalc { + fn update(&mut self, chunk: &[u8]) { + for byte in chunk { + self.ck_a = self.ck_a.overflowing_add(*byte).0; + self.ck_b = self.ck_b.overflowing_add(self.ck_a).0; } } -} - -#[ubx_packet] -pub struct NavVelNED { - pub itow: u32, - pub vel_north: i32, // cm/s - pub vel_east: i32, - pub vel_down: i32, - pub speed: u32, - pub ground_speed: u32, - pub heading: i32, // 1e-5 degrees -} - -/*#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct NavVelNED { - pub itow: u32, - pub vel_north: i32, // cm/s - pub vel_east: i32, - pub vel_down: i32, - pub speed: u32, - pub ground_speed: u32, - pub heading: i32, // 1e-5 degrees -} - -ubx_meta!(NavVelNED, 0x01, 0x12);*/ - -impl From<&NavVelNED> for Velocity { - fn from(packet: &NavVelNED) -> Self { - Velocity { - speed: packet.get_ground_speed() as f32 / 1_000.0, - heading: packet.get_heading() as f32 / 100_000.0, - } + fn result(self) -> (u8, u8) { + (self.ck_a, self.ck_b) } } -/*pub struct NavPosVelTime { - itow: u32, - year: u16, - month: u8, - day: u8, - hour: u8, - min: u8, - sec: u8, - - #[ubx_bitfield(8)] - #[ubx_range(0:0)] - valid: bool, - - // etc. -}*/ - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct NavPosVelTime { - pub itow: u32, - pub year: u16, - pub month: u8, - pub day: u8, - pub hour: u8, - pub min: u8, - pub sec: u8, - pub valid: u8, - pub time_accuracy: u32, - pub nanosecond: i32, - pub fix_type: u8, - pub flags: u8, - pub reserved1: u8, - pub num_satellites: u8, - pub lon: i32, - pub lat: i32, - pub height: i32, - pub height_msl: i32, - pub horiz_accuracy: u32, - pub vert_accuracy: u32, - pub vel_north: i32, // mm/s - pub vel_east: i32, - pub vel_down: i32, - pub ground_speed: i32, // mm/s - pub heading: i32, // 1e-5 deg - pub speed_accuracy: u32, - pub heading_accuracy: u32, - pub pos_dop: u16, - pub reserved2: u16, - pub reserved3: u32, +/// Abstraction for buffer creation/reallocation +/// to storing packet +pub trait MemWriter { + type Error: std::error::Error; + /// make sure that we have at least `len` bytes for writing + fn reserve_allocate(&mut self, len: usize) -> Result<(), MemWriterError>; + fn write(&mut self, buf: &[u8]) -> Result<(), MemWriterError>; } -ubx_meta!(NavPosVelTime, 0x01, 0x07); - -impl From<&NavPosVelTime> for Position { - fn from(packet: &NavPosVelTime) -> Self { - Position { - lon: packet.lon as f32 / 10_000_000.0, - lat: packet.lat as f32 / 10_000_000.0, - alt: packet.height_msl as f32 / 1000.0, - } - } -} +impl MemWriter for Vec { + type Error = std::io::Error; -impl From<&NavPosVelTime> for Velocity { - fn from(packet: &NavPosVelTime) -> Self { - Velocity { - speed: packet.ground_speed as f32 / 1_000.0, - heading: packet.heading as f32 / 100_000.0, - } + fn reserve_allocate(&mut self, len: usize) -> Result<(), MemWriterError> { + self.reserve(len); + Ok(()) } -} - -impl From<&NavPosVelTime> for DateTime { - fn from(sol: &NavPosVelTime) -> Self { - let ns = if sol.nanosecond < 0 { - 0 + fn write(&mut self, buf: &[u8]) -> Result<(), MemWriterError> { + let ret = ::write(self, buf).map_err(MemWriterError::Custom)?; + if ret == buf.len() { + Ok(()) } else { - sol.nanosecond - } as u32; - Utc.ymd(sol.year as i32, sol.month.into(), sol.day.into()) - .and_hms_nano(sol.hour.into(), sol.min.into(), sol.sec.into(), ns) - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct NavStatus { - pub itow: u32, - pub gps_fix: u8, - pub flags: u8, - pub fix_status: u8, - pub flags2: u8, - pub time_to_first_fix: u32, - pub uptime_ms: u32, -} - -ubx_meta!(NavStatus, 0x01, 0x03); - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct AidIni { - ecef_x_or_lat: i32, - ecef_y_or_lon: i32, - ecef_z_or_alt: i32, - pos_accuracy: u32, - time_cfg: u16, - week_or_ym: u16, - tow_or_hms: u32, - tow_ns: i32, - tm_accuracy_ms: u32, - tm_accuracy_ns: u32, - clk_drift_or_freq: i32, - clk_drift_or_freq_accuracy: u32, - flags: u32, -} - -ubx_meta!(AidIni, 0x0B, 0x01); - -impl AidIni { - pub fn new() -> AidIni { - AidIni { - ecef_x_or_lat: 0, - ecef_y_or_lon: 0, - ecef_z_or_alt: 0, - pos_accuracy: 0, - time_cfg: 0, - week_or_ym: 0, - tow_or_hms: 0, - tow_ns: 0, - tm_accuracy_ms: 0, - tm_accuracy_ns: 0, - clk_drift_or_freq: 0, - clk_drift_or_freq_accuracy: 0, - flags: 0, + Err(MemWriterError::NotEnoughMem) } } - - pub fn set_position(&mut self, pos: Position) { - self.ecef_x_or_lat = (pos.lat * 10_000_000.0) as i32; - self.ecef_y_or_lon = (pos.lon * 10_000_000.0) as i32; - self.ecef_z_or_alt = (pos.alt * 100.0) as i32; // Height is in centimeters, here - self.flags |= (1 << 0) | (1 << 5); - } - - pub fn set_time(&mut self, tm: DateTime) { - self.week_or_ym = (match tm.year_ce() { - (true, yr) => yr - 2000, - (false, _) => { - panic!("Jesus must have been born for this method to work"); - } - } * 100 - + tm.month0()) as u16; - self.tow_or_hms = tm.hour() * 10000 + tm.minute() * 100 + tm.second(); - self.tow_ns = tm.nanosecond() as i32; - self.flags |= (1 << 1) | (1 << 10); - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -pub struct AlpSrv { - pub id_size: u8, - pub data_type: u8, - pub offset: u16, - pub size: u16, - pub file_id: u16, - pub data_size: u16, - pub id1: u8, - pub id2: u8, - pub id3: u32, } -ubx_meta!(AlpSrv, 0x0B, 0x32); - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct CfgPrtUart { - pub portid: u8, - pub reserved0: u8, - pub tx_ready: u16, - pub mode: u32, - pub baud_rate: u32, - pub in_proto_mask: u16, - pub out_proto_mask: u16, - pub flags: u16, - pub reserved5: u16, +pub trait UbxPacketCreator { + /// Create packet and store bytes sequence to somewhere using `out` + fn create_packet(self, out: &mut T) -> Result<(), MemWriterError>; } -ubx_meta!(CfgPrtUart, 0x06, 0x00); - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct CfgPrtSpi { - pub portid: u8, - pub reserved0: u8, - pub tx_ready: u16, - pub mode: u32, - pub reserved3: u32, - pub in_proto_mask: u16, - pub out_proto_mask: u16, - pub flags: u16, - pub reserved5: u16, -} - -ubx_meta!(CfgPrtSpi, 0x06, 0x00); - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct AckAck { - pub classid: u8, - pub msgid: u8, -} - -ubx_meta!(AckAck, 0x05, 0x01); - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct CfgRst { - pub nav_bbr_mask: u16, - pub reset_mode: u8, - pub reserved1: u8, -} - -ubx_meta!(CfgRst, 0x06, 0x04); - -impl CfgRst { - pub const HOT: CfgRst = CfgRst { - nav_bbr_mask: 0, - reset_mode: 1, - reserved1: 0, - }; - - pub const WARM: CfgRst = CfgRst { - nav_bbr_mask: 0x01, - reset_mode: 1, - reserved1: 0, - }; - - pub const COLD: CfgRst = CfgRst { - nav_bbr_mask: 0xFFFF, - reset_mode: 1, - reserved1: 0, - }; +/// Packet not supported yet by this crate +#[derive(Debug)] +pub struct UbxUnknownPacketRef<'a> { + pub payload: &'a [u8], + pub class: u8, + pub msg_id: u8, } -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct CfgMsg { - pub classid: u8, - pub msgid: u8, - pub rates: [u8; 6], +/// Request specific packet +pub struct UbxPacketRequest { + req_class: u8, + req_id: u8, } -ubx_meta!(CfgMsg, 0x06, 0x01); +impl UbxPacketRequest { + pub const PACKET_LEN: usize = 8; -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct MonVer { - pub sw_version: String, - pub hw_version: String, -} - -impl UbxMeta for MonVer { - fn get_classid() -> u8 { - 0x0a - } - fn get_msgid() -> u8 { - 0x04 + #[inline] + pub fn request_for() -> Self { + Self { + req_class: T::CLASS, + req_id: T::ID, + } } - - fn to_bytes(&self) -> Vec { - unimplemented!("Sending MonVer packets is unimplemented"); + #[inline] + pub fn request_for_unknown(req_class: u8, req_id: u8) -> Self { + Self { req_class, req_id } } -} -#[derive(Debug, PartialEq)] -pub enum Packet { - NavPosLLH(NavPosLLH), - NavStatus(NavStatus), - NavPosVelTime(NavPosVelTime), - NavVelNED(NavVelNED), - AckAck(AckAck), - CfgPrtUart(CfgPrtUart), - CfgPrtSpi(CfgPrtSpi), - CfgMsg(CfgMsg), - CfgRst(CfgRst), - MonVer(MonVer), - AidIni(AidIni), - AlpSrv(AlpSrv), -} - -macro_rules! parse_packet_branch { - ($struct: path, $payload: ident) => {{ - let packet = bincode::deserialize($payload)?; - Ok($struct(packet)) - }}; -} - -impl Packet { - pub fn deserialize(classid: u8, msgid: u8, payload: &[u8]) -> Result { - match (classid, msgid) { - //(0x01, 0x02) => parse_packet_branch!(Packet::NavPosLLH, payload), - (0x01, 0x02) => Ok(Packet::NavPosLLH(NavPosLLH::new( - payload.try_into().unwrap(), - ))), - (0x01, 0x03) => parse_packet_branch!(Packet::NavStatus, payload), - (0x01, 0x07) => parse_packet_branch!(Packet::NavPosVelTime, payload), - //(0x01, 0x12) => parse_packet_branch!(Packet::NavVelNED, payload), - (0x01, 0x12) => Ok(Packet::NavVelNED(NavVelNED::new( - payload.try_into().unwrap(), - ))), - (0x05, 0x01) => parse_packet_branch!(Packet::AckAck, payload), - (0x06, 0x00) => { - // Depending on the port ID, we parse different packets - match payload[0] { - 1 => parse_packet_branch!(Packet::CfgPrtUart, payload), - 4 => parse_packet_branch!(Packet::CfgPrtSpi, payload), - _ => { - panic!("Unrecognized port ID {}! (is it USB?)", payload[0]); - } - } - } - (0x06, 0x01) => parse_packet_branch!(Packet::CfgMsg, payload), - (0x06, 0x04) => parse_packet_branch!(Packet::CfgRst, payload), - (0x0A, 0x04) => { - let sw_version = str::from_utf8(&payload[0..30]).unwrap(); - let hw_version = str::from_utf8(&payload[31..40]).unwrap(); - return Ok(Packet::MonVer(MonVer { - sw_version: sw_version.to_string(), - hw_version: hw_version.to_string(), - })); - } - (0x0B, 0x01) => parse_packet_branch!(Packet::AidIni, payload), - (0x0B, 0x32) => parse_packet_branch!(Packet::AlpSrv, payload), - (c, m) => { - panic!("Unimplemented packet classid={} msgid={}", c, m); - } - } + #[inline] + pub fn into_packet_bytes(self) -> [u8; Self::PACKET_LEN] { + let mut ret = [ + SYNC_CHAR_1, + SYNC_CHAR_2, + self.req_class, + self.req_id, + 0, + 0, + 0, + 0, + ]; + let (ck_a, ck_b) = ubx_checksum(&ret[2..6]); + ret[6] = ck_a; + ret[7] = ck_b; + ret } } diff --git a/ublox/src/ubx_packets/packets.rs b/ublox/src/ubx_packets/packets.rs new file mode 100644 index 0000000..f16c6b0 --- /dev/null +++ b/ublox/src/ubx_packets/packets.rs @@ -0,0 +1,788 @@ +use super::{ + ubx_checksum, MemWriter, Position, UbxChecksumCalc, UbxPacketCreator, UbxPacketMeta, + UbxUnknownPacketRef, SYNC_CHAR_1, SYNC_CHAR_2, +}; +use crate::error::{MemWriterError, ParserError}; +use bitflags::bitflags; +use chrono::prelude::*; +use std::fmt; +use ublox_derive::{ + define_recv_packets, ubx_extend, ubx_extend_bitflags, ubx_packet_recv, ubx_packet_recv_send, + ubx_packet_send, +}; + +/// Geodetic Position Solution +#[ubx_packet_recv] +#[ubx(class = 1, id = 2, fixed_payload_len = 28)] +struct NavPosLlh { + /// GPS Millisecond Time of Week + itow: u32, + + /// Longitude + #[ubx(map_type = f64, scale = 1e-7, alias = lon_degrees)] + lon: i32, + + /// Latitude + #[ubx(map_type = f64, scale = 1e-7, alias = lat_degrees)] + lat: i32, + + /// Height above Ellipsoid + #[ubx(map_type = f64, scale = 1e-3)] + height_meters: i32, + + /// Height above mean sea level + #[ubx(map_type = f64, scale = 1e-3)] + height_msl: i32, + + /// Horizontal Accuracy Estimate + #[ubx(map_type = f64, scale = 1e-3)] + h_ack: u32, + + /// Vertical Accuracy Estimate + #[ubx(map_type = f64, scale = 1e-3)] + v_acc: u32, +} + +/// Velocity Solution in NED +#[ubx_packet_recv] +#[ubx(class = 1, id = 0x12, fixed_payload_len = 36)] +struct NavVelNed { + /// GPS Millisecond Time of Week + itow: u32, + + /// north velocity (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + vel_north: i32, + + /// east velocity (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + vel_east: i32, + + /// down velocity (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + vel_down: i32, + + /// Speed 3-D (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + speed_3d: u32, + + /// Ground speed (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + ground_speed: u32, + + /// Heading of motion 2-D (degrees) + #[ubx(map_type = f64, scale = 1e-5, alias = heading_degrees)] + heading: i32, + + /// Speed Accuracy Estimate (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + speed_accuracy_estimate: u32, + + /// Course / Heading Accuracy Estimate (degrees) + #[ubx(map_type = f64, scale = 1e-5)] + course_heading_accuracy_estimate: u32, +} + +/// Navigation Position Velocity Time Solution +#[ubx_packet_recv] +#[ubx(class = 1, id = 0x07, fixed_payload_len = 92)] +struct NavPosVelTime { + /// GPS Millisecond Time of Week + itow: u32, + year: u16, + month: u8, + day: u8, + hour: u8, + min: u8, + sec: u8, + valid: u8, + time_accuracy: u32, + nanosecond: i32, + + /// GNSS fix Type + #[ubx(map_type = GpsFix)] + fix_type: u8, + flags: u8, + flags2: u8, + num_satellites: u8, + #[ubx(map_type = f64, scale = 1e-7, alias = lon_degrees)] + lon: i32, + #[ubx(map_type = f64, scale = 1e-7, alias = lat_degrees)] + lat: i32, + + /// Height above Ellipsoid + #[ubx(map_type = f64, scale = 1e-3)] + height_meters: i32, + + /// Height above mean sea level + #[ubx(map_type = f64, scale = 1e-3)] + height_msl: i32, + horiz_accuracy: u32, + vert_accuracy: u32, + + /// north velocity (m/s) + #[ubx(map_type = f64, scale = 1e-3)] + vel_north: i32, + + /// east velocity (m/s) + #[ubx(map_type = f64, scale = 1e-3)] + vel_east: i32, + + /// down velocity (m/s) + #[ubx(map_type = f64, scale = 1e-3)] + vel_down: i32, + + /// Ground speed (m/s) + #[ubx(map_type = f64, scale = 1e-3)] + ground_speed: u32, + + /// Heading of motion 2-D (degrees) + #[ubx(map_type = f64, scale = 1e-5, alias = heading_degrees)] + heading: i32, + + /// Speed Accuracy Estimate (m/s) + #[ubx(map_type = f64, scale = 1e-3)] + speed_accuracy_estimate: u32, + + /// Course / Heading Accuracy Estimate (degrees) + #[ubx(map_type = f64, scale = 1e-5)] + course_heading_accuracy_estimate: u32, + pos_dop: u16, + reserved1: [u8; 6], + #[ubx(map_type = f64, scale = 1e-5, alias = heading_of_vehicle_degrees)] + heading_of_vehicle: i32, + #[ubx(map_type = f64, scale = 1e-2, alias = magnetic_declination_degrees)] + magnetic_declination: i16, + #[ubx(map_type = f64, scale = 1e-2, alias = magnetic_declination_accuracy_degrees)] + magnetic_declination_accuracy: u16, +} + +/// Receiver Navigation Status +#[ubx_packet_recv] +#[ubx(class = 1, id = 3, fixed_payload_len = 16)] +struct NavStatus { + /// GPS Millisecond Time of Week + itow: u32, + + /// GPS fix Type, this value does not qualify a fix as + + /// valid and within the limits + #[ubx(map_type = GpsFix)] + fix_type: u8, + + /// Navigation Status Flags + #[ubx(map_type = NavStatusFlags)] + flags: u8, + + /// Fix Status Information + #[ubx(map_type = FixStatusInfo)] + fix_stat: u8, + + /// further information about navigation output + #[ubx(map_type = NavStatusFlags2)] + flags2: u8, + + /// Time to first fix (millisecond time tag) + time_to_first_fix: u32, + + /// Milliseconds since Startup / Reset + uptime_ms: u32, +} + +/// Dilution of precision +#[ubx_packet_recv] +#[ubx(class = 1, id = 4, fixed_payload_len = 18)] +struct NavDop { + /// GPS Millisecond Time of Week + itow: u32, + #[ubx(map_type = f32, scale = 1e-2)] + geometric_dop: u16, + #[ubx(map_type = f32, scale = 1e-2)] + position_dop: u16, + #[ubx(map_type = f32, scale = 1e-2)] + time_dop: u16, + #[ubx(map_type = f32, scale = 1e-2)] + vertical_dop: u16, + #[ubx(map_type = f32, scale = 1e-2)] + horizontal_dop: u16, + #[ubx(map_type = f32, scale = 1e-2)] + northing_dop: u16, + #[ubx(map_type = f32, scale = 1e-2)] + easting_dop: u16, +} + +/// Navigation Solution Information +#[ubx_packet_recv] +#[ubx(class = 1, id = 6, fixed_payload_len = 52)] +struct NavSolution { + /// GPS Millisecond Time of Week + itow: u32, + + /// Fractional part of iTOW (range: +/-500000). + ftow_ns: i32, + + /// GPS week number of the navigation epoch + week: i16, + + /// GPS fix Type + #[ubx(map_type = GpsFix)] + fix_type: u8, + + /// Navigation Status Flags + #[ubx(map_type = NavStatusFlags)] + flags: u8, + + /// ECEF X coordinate (meters) + #[ubx(map_type = f64, scale = 1e-2)] + ecef_x: i32, + + /// ECEF Y coordinate (meters) + #[ubx(map_type = f64, scale = 1e-2)] + ecef_y: i32, + + /// ECEF Z coordinate (meters) + #[ubx(map_type = f64, scale = 1e-2)] + ecef_z: i32, + + /// 3D Position Accuracy Estimate + #[ubx(map_type = f64, scale = 1e-2)] + position_accuracy_estimate: u32, + + /// ECEF X velocity (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + ecef_vx: i32, + + /// ECEF Y velocity (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + ecef_vy: i32, + + /// ECEF Z velocity (m/s) + #[ubx(map_type = f64, scale = 1e-2)] + ecef_vz: i32, + + /// Speed Accuracy Estimate + #[ubx(map_type = f64, scale = 1e-2)] + speed_accuracy_estimate: u32, + + /// Position DOP + #[ubx(map_type = f32, scale = 1e-2)] + pdop: u16, + reserved1: u8, + + /// Number of SVs used in Nav Solution + num_sv: u8, + reserved2: [u8; 4], +} + +/// GPS fix Type +#[ubx_extend] +#[ubx(from, rest_reserved)] +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +pub enum GpsFix { + NoFix = 0, + DeadReckoningOnly = 1, + Fix2D = 2, + Fix3D = 3, + GPSPlusDeadReckoning = 4, + TimeOnlyFix = 5, +} + +#[ubx_extend_bitflags] +#[ubx(from, rest_reserved)] +bitflags! { + /// Navigation Status Flags + pub struct NavStatusFlags: u8 { + /// position and velocity valid and within DOP and ACC Masks + const GPS_FIX_OK = 1; + /// DGPS used + const DIFF_SOLN = 2; + /// Week Number valid + const WKN_SET = 4; + /// Time of Week valid + const TOW_SET = 8; + } +} + +/// Fix Status Information +#[repr(transparent)] +#[derive(Copy, Clone)] +pub struct FixStatusInfo(u8); + +impl FixStatusInfo { + pub const fn has_pr_prr_correction(self) -> bool { + (self.0 & 1) == 1 + } + pub fn map_matching(self) -> MapMatchingStatus { + let bits = (self.0 >> 6) & 3; + match bits { + 0 => MapMatchingStatus::None, + 1 => MapMatchingStatus::Valid, + 2 => MapMatchingStatus::Used, + 3 => MapMatchingStatus::Dr, + _ => unreachable!(), + } + } + pub const fn from(x: u8) -> Self { + Self(x) + } +} + +impl fmt::Debug for FixStatusInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FixStatusInfo") + .field("has_pr_prr_correction", &self.has_pr_prr_correction()) + .field("map_matching", &self.map_matching()) + .finish() + } +} + +#[derive(Copy, Clone, Debug)] +pub enum MapMatchingStatus { + None = 0, + /// valid, i.e. map matching data was received, but was too old + Valid = 1, + /// used, map matching data was applied + Used = 2, + /// map matching was the reason to enable the dead reckoning + /// gpsFix type instead of publishing no fix + Dr = 3, +} + +/// Further information about navigation output +/// Only for FW version >= 7.01; undefined otherwise +#[ubx_extend] +#[ubx(from, rest_reserved)] +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +enum NavStatusFlags2 { + Acquisition = 0, + Tracking = 1, + PowerOptimizedTracking = 2, + Inactive = 3, +} + +#[ubx_packet_send] +#[ubx( + class = 0x0B, + id = 0x01, + fixed_payload_len = 48, + flags = "default_for_builder" +)] +struct AidIni { + ecef_x_or_lat: i32, + ecef_y_or_lon: i32, + ecef_z_or_alt: i32, + pos_accuracy: u32, + time_cfg: u16, + week_or_ym: u16, + tow_or_hms: u32, + tow_ns: i32, + tm_accuracy_ms: u32, + tm_accuracy_ns: u32, + clk_drift_or_freq: i32, + clk_drift_or_freq_accuracy: u32, + flags: u32, +} + +impl AidIniBuilder { + pub fn set_position(mut self, pos: Position) -> Self { + self.ecef_x_or_lat = (pos.lat * 10_000_000.0) as i32; + self.ecef_y_or_lon = (pos.lon * 10_000_000.0) as i32; + self.ecef_z_or_alt = (pos.alt * 100.0) as i32; // Height is in centimeters, here + self.flags |= (1 << 0) | (1 << 5); + self + } + + pub fn set_time(mut self, tm: DateTime) -> Self { + self.week_or_ym = (match tm.year_ce() { + (true, yr) => yr - 2000, + (false, _) => { + panic!("Jesus must have been born for this method to work"); + } + } * 100 + + tm.month0()) as u16; + self.tow_or_hms = tm.hour() * 10000 + tm.minute() * 100 + tm.second(); + self.tow_ns = tm.nanosecond() as i32; + self.flags |= (1 << 1) | (1 << 10); + self + } +} + +/// ALP client requests AlmanacPlus data from server +#[ubx_packet_recv] +#[ubx(class = 0x0B, id = 0x32, fixed_payload_len = 16)] +struct AlpSrv { + pub id_size: u8, + pub data_type: u8, + pub offset: u16, + pub size: u16, + pub file_id: u16, + pub data_size: u16, + pub id1: u8, + pub id2: u8, + pub id3: u32, +} + +/// Messages in this class are sent as a result of a CFG message being +/// received, decoded and processed by thereceiver. +#[ubx_packet_recv] +#[ubx(class = 5, id = 1, fixed_payload_len = 2)] +struct AckAck { + /// Class ID of the Acknowledged Message + class: u8, + + /// Message ID of the Acknowledged Message + msg_id: u8, +} + +impl<'a> AckAckRef<'a> { + pub fn is_ack_for(&self) -> bool { + self.class() == T::CLASS && self.msg_id() == T::ID + } +} + +/// Message Not-Acknowledge +#[ubx_packet_recv] +#[ubx(class = 5, id = 0, fixed_payload_len = 2)] +struct AckNak { + /// Class ID of the Acknowledged Message + class: u8, + + /// Message ID of the Acknowledged Message + msg_id: u8, +} + +impl<'a> AckNakRef<'a> { + pub fn is_nak_for(&self) -> bool { + self.class() == T::CLASS && self.msg_id() == T::ID + } +} + +/// Reset Receiver / Clear Backup Data Structures +#[ubx_packet_send] +#[ubx(class = 6, id = 4, fixed_payload_len = 4)] +struct CfgRst { + /// Battery backed RAM sections to clear + #[ubx(map_type = NavBbrMask)] + nav_bbr_mask: u16, + + /// Reset Type + #[ubx(map_type = ResetMode)] + reset_mode: u8, + reserved1: u8, +} + +#[ubx_extend_bitflags] +#[ubx(into_raw, rest_reserved)] +bitflags! { + /// Battery backed RAM sections to clear + pub struct NavBbrMask: u16 { + const EPHEMERIS = 1; + const ALMANACH = 2; + const HEALTH = 4; + const KLOBUCHARD = 8; + const POSITION = 16; + const CLOCK_DRIFT = 32; + const OSCILATOR_PARAMETER = 64; + const UTC_CORRECTION_PARAMETERS = 0x80; + const RTC = 0x100; + const SFDR_PARAMETERS = 0x800; + const SFDR_VEHICLE_MONITORING_PARAMETERS = 0x1000; + const TCT_PARAMETERS = 0x2000; + const AUTONOMOUS_ORBIT_PARAMETERS = 0x8000; + } +} + +/// Predefined values for `NavBbrMask` +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct NavBbrPredefinedMask(u16); + +impl From for NavBbrMask { + fn from(x: NavBbrPredefinedMask) -> Self { + Self::from_bits_truncate(x.0) + } +} + +impl NavBbrPredefinedMask { + pub const HOT_START: NavBbrPredefinedMask = NavBbrPredefinedMask(0); + pub const WARM_START: NavBbrPredefinedMask = NavBbrPredefinedMask(1); + pub const COLD_START: NavBbrPredefinedMask = NavBbrPredefinedMask(0xFFFF); +} + +/// Reset Type +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum ResetMode { + /// Hardware reset (Watchdog) immediately + HardwareResetImmediately = 0, + ControlledSoftwareReset = 0x1, + ControlledSoftwareResetGpsOnly = 0x02, + /// Hardware reset (Watchdog) after shutdown (>=FW6.0) + HardwareResetAfterShutdown = 0x04, + ControlledGpsStop = 0x08, + ControlledGpsStart = 0x09, +} + +impl ResetMode { + const fn into_raw(self) -> u8 { + self as u8 + } +} + +/// Port Configuration for UART +#[ubx_packet_recv_send] +#[ubx(class = 0x06, id = 0x00, fixed_payload_len = 20)] +struct CfgPrtUart { + #[ubx(map_type = UartPortId, may_fail)] + portid: u8, + reserved0: u8, + tx_ready: u16, + mode: u32, + baud_rate: u32, + in_proto_mask: u16, + out_proto_mask: u16, + flags: u16, + reserved5: u16, +} + +/// Port Identifier Number (= 1 or 2 for UART ports) +#[ubx_extend] +#[ubx(from_unchecked, into_raw, rest_error)] +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +pub enum UartPortId { + Uart1 = 1, + Uart2 = 2, +} + +/// Port Configuration for SPI Port +#[ubx_packet_recv_send] +#[ubx(class = 0x06, id = 0x00, fixed_payload_len = 20)] +struct CfgPrtSpi { + #[ubx(map_type = SpiPortId, may_fail)] + portid: u8, + reserved0: u8, + tx_ready: u16, + mode: u32, + reserved3: u32, + in_proto_mask: u16, + out_proto_mask: u16, + flags: u16, + reserved5: u16, +} + +/// Port Identifier Number (= 4 for SPI port) +#[ubx_extend] +#[ubx(from_unchecked, into_raw, rest_error)] +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +pub enum SpiPortId { + Spi = 4, +} + +/// UTC Time Solution +#[ubx_packet_recv] +#[ubx(class = 1, id = 0x21, fixed_payload_len = 20)] +struct NavTimeUTC { + /// GPS Millisecond Time of Week + itow: u32, + time_accuracy_estimate_ns: u32, + + /// Nanoseconds of second, range -1e9 .. 1e9 + nanos: i32, + + /// Year, range 1999..2099 + year: u16, + + /// Month, range 1..12 + month: u8, + + /// Day of Month, range 1..31 + day: u8, + + /// Hour of Day, range 0..23 + hour: u8, + + /// Minute of Hour, range 0..59 + min: u8, + + /// Seconds of Minute, range 0..59 + sec: u8, + + /// Validity Flags + #[ubx(map_type = NavTimeUtcFlags)] + valid: u8, +} + +#[ubx_extend_bitflags] +#[ubx(from, rest_reserved)] +bitflags! { + /// Validity Flags of `NavTimeUTC` + pub struct NavTimeUtcFlags: u8 { + /// Valid Time of Week + const VALID_TOW = 1; + /// Valid Week Number + const VALID_WKN = 2; + /// Valid UTC (Leap Seconds already known) + const VALID_UTC = 4; + } +} + +/// Navigation/Measurement Rate Settings +#[ubx_packet_send] +#[ubx(class = 6, id = 8, fixed_payload_len = 6)] +struct CfgRate { + /// Measurement Rate, GPS measurements are taken every `measure_rate_ms` milliseconds + measure_rate_ms: u16, + + /// Navigation Rate, in number of measurement cycles. + + /// On u-blox 5 and u-blox 6, this parametercannot be changed, and is always equals 1. + nav_rate: u16, + + /// Alignment to reference time + #[ubx(map_type = AlignmentToReferenceTime)] + time_ref: u16, +} + +/// Alignment to reference time +#[repr(u16)] +#[derive(Clone, Copy, Debug)] +pub enum AlignmentToReferenceTime { + Utc = 0, + Gps = 1, +} + +impl AlignmentToReferenceTime { + const fn into_raw(self) -> u16 { + self as u16 + } +} + +/// Set Message Rate the current port +#[ubx_packet_send] +#[ubx(class = 6, id = 1, fixed_payload_len = 3)] +struct CfgMsgSinglePort { + msg_class: u8, + msg_id: u8, + + /// Send rate on current Target + rate: u8, +} + +impl CfgMsgSinglePortBuilder { + #[inline] + pub fn set_rate_for(rate: u8) -> Self { + Self { + msg_class: T::CLASS, + msg_id: T::ID, + rate, + } + } +} + +/// Set Message rate configuration +/// Send rate is relative to the event a message is registered on. +/// For example, if the rate of a navigation message is set to 2, +/// the message is sent every second navigation solution +#[ubx_packet_send] +#[ubx(class = 6, id = 1, fixed_payload_len = 8)] +struct CfgMsgAllPorts { + msg_class: u8, + msg_id: u8, + + /// Send rate on I/O Port (6 Ports) + rates: [u8; 6], +} + +impl CfgMsgAllPortsBuilder { + #[inline] + pub fn set_rate_for(rates: [u8; 6]) -> Self { + Self { + msg_class: T::CLASS, + msg_id: T::ID, + rates, + } + } +} + +/// Receiver/Software Version +#[ubx_packet_recv] +#[ubx(class = 0x0a, id = 0x04, max_payload_len = 1240)] +struct MonVer { + #[ubx(map_type = &str, may_fail, from = mon_ver::convert_to_str_unchecked, + is_valid = mon_ver::is_cstr_valid, get_as_ref)] + software_version: [u8; 30], + #[ubx(map_type = &str, may_fail, from = mon_ver::convert_to_str_unchecked, + is_valid = mon_ver::is_cstr_valid, get_as_ref)] + hardware_version: [u8; 10], + + /// Extended software information strings + #[ubx(map_type = impl Iterator, may_fail, + from = mon_ver::extension_to_iter, + is_valid = mon_ver::is_extension_valid)] + extension: [u8; 0], +} + +mod mon_ver { + use std::ffi::CStr; + + pub(crate) fn convert_to_str_unchecked(bytes: &[u8]) -> &str { + let null_pos = bytes + .iter() + .position(|x| *x == 0) + .expect("is_cstr_valid bug?"); + let cstr = CStr::from_bytes_with_nul(&bytes[0..=null_pos]).expect("is_cstr_valid bug?"); + cstr.to_str().expect("is_cstr_valid bug?") + } + + pub(crate) fn is_cstr_valid(bytes: &[u8]) -> bool { + if let Some(pos) = bytes.iter().position(|x| *x == 0) { + if let Ok(cstr) = CStr::from_bytes_with_nul(&bytes[0..=pos]) { + cstr.to_str().is_ok() + } else { + false + } + } else { + false + } + } + + pub(crate) fn is_extension_valid(payload: &[u8]) -> bool { + if payload.len() % 30 == 0 { + for chunk in payload.chunks(30) { + if !is_cstr_valid(chunk) { + return false; + } + } + true + } else { + false + } + } + + pub(crate) fn extension_to_iter(payload: &[u8]) -> impl Iterator { + payload.chunks(30).map(|x| convert_to_str_unchecked(x)) + } +} + +define_recv_packets!( + enum PacketRef { + _ = UbxUnknownPacketRef, + NavPosLlh, + NavStatus, + NavDop, + NavPosVelTime, + NavSolution, + NavVelNed, + AlpSrv, + AckAck, + AckNak, + CfgPrtSpi, + CfgPrtUart, + NavTimeUTC, + MonVer + } +); diff --git a/ublox/src/ubx_packets/types.rs b/ublox/src/ubx_packets/types.rs new file mode 100644 index 0000000..bca13a7 --- /dev/null +++ b/ublox/src/ubx_packets/types.rs @@ -0,0 +1,65 @@ +use super::packets::*; +use chrono::prelude::*; + +#[derive(Debug)] +pub struct Position { + pub lon: f64, + pub lat: f64, + pub alt: f64, +} + +#[derive(Debug)] +pub struct Velocity { + pub speed: f64, // m/s over ground + pub heading: f64, // degrees +} + +impl<'a> From<&NavPosLlhRef<'a>> for Position { + fn from(packet: &NavPosLlhRef<'a>) -> Self { + Position { + lon: packet.lon_degrees(), + lat: packet.lat_degrees(), + alt: packet.height_msl(), + } + } +} + +impl<'a> From<&NavVelNedRef<'a>> for Velocity { + fn from(packet: &NavVelNedRef<'a>) -> Self { + Velocity { + speed: packet.ground_speed(), + heading: packet.heading_degrees(), + } + } +} + +impl<'a> From<&NavPosVelTimeRef<'a>> for Position { + fn from(packet: &NavPosVelTimeRef<'a>) -> Self { + Position { + lon: packet.lon_degrees(), + lat: packet.lat_degrees(), + alt: packet.height_msl(), + } + } +} + +impl<'a> From<&NavPosVelTimeRef<'a>> for Velocity { + fn from(packet: &NavPosVelTimeRef<'a>) -> Self { + Velocity { + speed: packet.ground_speed(), + heading: packet.heading_degrees(), + } + } +} + +impl<'a> From<&NavPosVelTimeRef<'a>> for DateTime { + fn from(sol: &NavPosVelTimeRef<'a>) -> Self { + let ns = if sol.nanosecond().is_negative() { + 0 + } else { + sol.nanosecond() + } as u32; + Utc.ymd(sol.year() as i32, sol.month().into(), sol.day().into()) + .and_hms_nano(sol.hour().into(), sol.min().into(), sol.sec().into(), ns) + } +} diff --git a/ublox/tests/generator_test.rs b/ublox/tests/generator_test.rs new file mode 100644 index 0000000..afad89c --- /dev/null +++ b/ublox/tests/generator_test.rs @@ -0,0 +1,14 @@ +use ublox::{CfgMsgSinglePortBuilder, NavPosLlh, NavStatus}; + +#[test] +fn test_cfg_msg_simple() { + assert_eq!( + [0xb5, 0x62, 0x06, 0x01, 0x03, 0x00, 0x01, 0x02, 0x01, 0x0E, 0x47], + CfgMsgSinglePortBuilder::set_rate_for::(1).into_packet_bytes() + ); + + assert_eq!( + [0xb5, 0x62, 0x06, 0x01, 0x03, 0x00, 0x01, 0x03, 0x01, 0x0F, 0x49], + CfgMsgSinglePortBuilder::set_rate_for::(1).into_packet_bytes() + ); +} diff --git a/ublox/tests/parser_binary_dump_test.rs b/ublox/tests/parser_binary_dump_test.rs new file mode 100644 index 0000000..1fba6ee --- /dev/null +++ b/ublox/tests/parser_binary_dump_test.rs @@ -0,0 +1,133 @@ +use cpu_time::ProcessTime; +use rand::{thread_rng, Rng}; +use std::{env, ffi::OsString, fs, path::Path}; +use ublox::{PacketRef, Parser, ParserError}; + +/// To run test against file with path X, +/// use such command (if use shell compatible with /bin/sh). +/// ```sh +/// UBX_BIG_LOG_PATH=X time cargo test --release test_parse_big_dump -- --ignored --nocapture +/// ``` +/// Binary dump should be at path X and at path X.meta, should be file with meta +/// information about what packets you expect find in dump, example: +/// ```sh +/// $ cat /var/tmp/gps.bin.meta +///wrong_chksum=0 +///other_errors=0 +///nav_pos_llh=38291 +///nav_stat=38291 +///unknown=120723 +///ack_ack=1 +/// ``` +#[test] +#[ignore] +fn test_parse_big_dump() { + let ubx_big_log_path = env::var("UBX_BIG_LOG_PATH").unwrap(); + let ubx_big_log_path = Path::new(&ubx_big_log_path); + + let meta_ext: OsString = if let Some(ext) = ubx_big_log_path.extension() { + let mut ext: OsString = ext.into(); + ext.push(".meta"); + ext + } else { + "meta".into() + }; + let ubx_big_log_path_meta = ubx_big_log_path.with_extension(meta_ext); + let meta_data = fs::read_to_string(ubx_big_log_path_meta).unwrap(); + let expect = parse_meta_data(&meta_data).unwrap(); + + let biglog = fs::read(ubx_big_log_path).unwrap(); + const MAX_SIZE: usize = 100; + let mut read_sizes = Vec::with_capacity(biglog.len() / MAX_SIZE / 2); + let mut rng = thread_rng(); + let mut i = 0; + while i < biglog.len() { + let chunk: usize = rng.gen_range(1, MAX_SIZE); + let chunk = (biglog.len() - i).min(chunk); + read_sizes.push(chunk); + i += chunk; + } + + let mut meta = Meta::default(); + let mut log = biglog.as_slice(); + let mut parser = Parser::default(); + + let start = ProcessTime::now(); + for chunk_size in &read_sizes { + let (buf, rest) = log.split_at(*chunk_size); + log = rest; + let mut it = parser.consume(buf); + while let Some(pack) = it.next() { + match pack { + Ok(pack) => match pack { + PacketRef::AckAck(_) => meta.ack_ack += 1, + PacketRef::NavPosLlh(_) => meta.nav_pos_llh += 1, + PacketRef::NavStatus(_) => meta.nav_stat += 1, + _ => meta.unknown += 1, + }, + Err(ParserError::InvalidChecksum { .. }) => meta.wrong_chksum += 1, + Err(_) => meta.other_errors += 1, + } + } + } + let cpu_time = start.elapsed(); + println!( + "parse time of {}: {:?}", + ubx_big_log_path.display(), + cpu_time + ); + + assert_eq!(expect, meta); +} + +#[derive(Default, PartialEq, Debug)] +struct Meta { + wrong_chksum: usize, + other_errors: usize, + nav_pos_llh: usize, + nav_stat: usize, + ack_ack: usize, + unknown: usize, +} + +fn parse_meta_data(text: &str) -> Result { + let mut wrong_chksum = None; + let mut other_errors = None; + let mut nav_pos_llh = None; + let mut nav_stat = None; + let mut ack_ack = None; + let mut unknown = None; + + for line in text.lines() { + let mut it = line.split('='); + let name = it + .next() + .ok_or_else(|| "missed variable name".to_string())? + .trim(); + let value = it + .next() + .ok_or_else(|| "missed variable value".to_string())? + .trim(); + let value: usize = value + .parse() + .map_err(|err| format!("Can not parse integer as usize: {}", err))?; + match name { + "wrong_chksum" => wrong_chksum = Some(value), + "other_errors" => other_errors = Some(value), + "nav_pos_llh" => nav_pos_llh = Some(value), + "nav_stat" => nav_stat = Some(value), + "ack_ack" => ack_ack = Some(value), + "unknown" => unknown = Some(value), + _ => return Err(format!("wrong field name: '{}'", name)), + } + } + let missed = || "missed field".to_string(); + Ok(Meta { + wrong_chksum: wrong_chksum.ok_or_else(&missed)?, + other_errors: other_errors.ok_or_else(&missed)?, + nav_pos_llh: nav_pos_llh.ok_or_else(&missed)?, + nav_stat: nav_stat.ok_or_else(&missed)?, + ack_ack: ack_ack.ok_or_else(&missed)?, + unknown: unknown.ok_or_else(&missed)?, + }) +} diff --git a/ublox/tests/parser_tests.rs b/ublox/tests/parser_tests.rs new file mode 100644 index 0000000..55079b7 --- /dev/null +++ b/ublox/tests/parser_tests.rs @@ -0,0 +1,131 @@ +use ublox::{PacketRef, Parser, ParserError, ParserIter}; + +macro_rules! my_vec { + ($($x:expr),*) => {{ + let v: Vec> = vec![$($x),*]; + v + }} + } + +fn extract_only_ack_ack(mut it: ParserIter) -> Vec> { + let mut ret = vec![]; + while let Some(pack) = it.next() { + match pack { + Ok(PacketRef::AckAck(pack)) => { + ret.push(Ok((pack.class(), pack.msg_id()))); + } + Err(err) => ret.push(Err(err)), + _ => assert!(false), + } + } + ret +} + +static FULL_ACK_ACK_PACK: [u8; 10] = [0xb5, 0x62, 0x5, 0x1, 0x2, 0x0, 0x6, 0x1, 0xf, 0x38]; + +#[test] +fn test_parse_empty_buffer() { + let mut parser = Parser::default(); + assert!(parser.is_buffer_empty()); + assert_eq!(my_vec![], extract_only_ack_ack(parser.consume(&[]))); + assert!(parser.is_buffer_empty()); +} + +#[test] +fn test_parse_ack_ack_byte_by_byte() { + let mut parser = Parser::default(); + for b in FULL_ACK_ACK_PACK.iter().take(FULL_ACK_ACK_PACK.len() - 1) { + assert_eq!(my_vec![], extract_only_ack_ack(parser.consume(&[*b]))); + assert!(!parser.is_buffer_empty()); + } + let last_byte = FULL_ACK_ACK_PACK[FULL_ACK_ACK_PACK.len() - 1]; + assert_eq!( + my_vec![Ok((6, 1))], + extract_only_ack_ack(parser.consume(&[last_byte])), + ); + assert!(parser.is_buffer_empty()); +} + +#[test] +fn test_parse_ack_ack_in_one_go() { + let mut parser = Parser::default(); + assert_eq!( + my_vec![Ok((6, 1))], + extract_only_ack_ack(parser.consume(&FULL_ACK_ACK_PACK)), + ); + assert!(parser.is_buffer_empty()); +} + +#[test] +fn test_parse_ack_ack_bad_checksum() { + let mut parser = Parser::default(); + let mut bad_pack = FULL_ACK_ACK_PACK.clone(); + bad_pack[bad_pack.len() - 3] = 5; + assert_eq!( + my_vec![Err(ParserError::InvalidChecksum { + expect: 0x380f, + got: 0x3c13 + })], + extract_only_ack_ack(parser.consume(&bad_pack)), + ); + assert_eq!(bad_pack.len() - 2, parser.buffer_len()); + + let mut two_packs = FULL_ACK_ACK_PACK.to_vec(); + two_packs.extend_from_slice(&FULL_ACK_ACK_PACK); + assert_eq!( + my_vec![Ok((6, 1)), Ok((6, 1))], + extract_only_ack_ack(parser.consume(&two_packs)), + ); + assert!(parser.is_buffer_empty()); +} + +#[test] +fn test_parse_ack_ack_parted_two_packets() { + let mut parser = Parser::default(); + assert_eq!( + my_vec![], + extract_only_ack_ack(parser.consume(&FULL_ACK_ACK_PACK[0..5])), + ); + assert_eq!(5, parser.buffer_len()); + let mut rest_and_next = (&FULL_ACK_ACK_PACK[5..]).to_vec(); + rest_and_next.extend_from_slice(&FULL_ACK_ACK_PACK); + assert_eq!( + my_vec![Ok((6, 1)), Ok((6, 1))], + extract_only_ack_ack(parser.consume(&rest_and_next)), + ); + assert!(parser.is_buffer_empty()); +} + +#[test] +fn test_parse_ack_ack_two_in_one_go() { + let mut parser = Parser::default(); + let mut two_packs = FULL_ACK_ACK_PACK.to_vec(); + two_packs.extend_from_slice(&FULL_ACK_ACK_PACK); + assert_eq!( + my_vec![Ok((6, 1)), Ok((6, 1))], + extract_only_ack_ack(parser.consume(&two_packs)) + ); + assert!(parser.is_buffer_empty()); +} + +#[test] +fn test_parse_ack_ack_garbage_before() { + let mut parser = Parser::default(); + let mut garbage_before = vec![0x00, 0x06, 0x01, 0x0f, 0x38]; + garbage_before.extend_from_slice(&FULL_ACK_ACK_PACK); + assert_eq!( + my_vec![Ok((6, 1))], + extract_only_ack_ack(parser.consume(&garbage_before)), + "garbage before1" + ); + assert!(parser.is_buffer_empty()); + + let mut garbage_before = vec![0xb5, 0xb5, 0x62, 0x62, 0x38]; + garbage_before.extend_from_slice(&FULL_ACK_ACK_PACK); + assert_eq!( + my_vec![Ok((6, 1))], + extract_only_ack_ack(parser.consume(&garbage_before)), + "garbage before2" + ); + assert!(parser.is_buffer_empty()); +} diff --git a/ublox_derive/Cargo.toml b/ublox_derive/Cargo.toml index c4b5eb5..d60c335 100644 --- a/ublox_derive/Cargo.toml +++ b/ublox_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ublox_derive" -version = "0.0.0" +version = "0.0.1" authors = ["Lane Kolbly "] edition = "2018" publish = false @@ -11,5 +11,8 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0.14", features = ["extra-traits"] } -proc-macro-error = "0.4.8" +syn = { version = "1.0.14", features = ["extra-traits", "full"] } + +[dev-dependencies] +which = { version = "3.0", default-features = false } +proc-macro2 = { version = "1.0", features = ["span-locations"] } \ No newline at end of file diff --git a/ublox_derive/src/input.rs b/ublox_derive/src/input.rs new file mode 100644 index 0000000..e38540d --- /dev/null +++ b/ublox_derive/src/input.rs @@ -0,0 +1,652 @@ +use crate::types::{ + BitFlagsMacro, BitFlagsMacroItem, MapType, PackDesc, PackField, PackFieldMap, PackHeader, + PacketFlag, PayloadLen, RecvPackets, UbxEnumRestHandling, UbxExtendEnum, UbxTypeFromFn, + UbxTypeIntoFn, +}; +use proc_macro2::Span; +use quote::ToTokens; +use std::num::NonZeroUsize; +use syn::{ + braced, parse::Parse, punctuated::Punctuated, spanned::Spanned, Attribute, Error, Fields, + Ident, Token, Type, +}; + +pub fn parse_packet_description( + struct_name: Ident, + attrs: Vec, + fields: Fields, +) -> syn::Result { + let main_sp = struct_name.span(); + + let header = parse_ubx_attr(&attrs, &struct_name)?; + let struct_comment = extract_item_comment(&attrs)?; + + let name = struct_name.to_string(); + let fields = parse_fields(fields)?; + + if let Some(field) = fields.iter().rev().skip(1).find(|x| x.size_bytes.is_none()) { + return Err(Error::new( + field.name.span(), + "Non-finite size for field which is not the last field", + )); + } + + let ret = PackDesc { + name, + header, + comment: struct_comment, + fields, + }; + + if ret.header.payload_len.fixed().map(usize::from) == ret.packet_payload_size() { + Ok(ret) + } else { + Err(Error::new( + main_sp, + format!( + "Calculated packet size ({:?}) doesn't match specified ({:?})", + ret.packet_payload_size(), + ret.header.payload_len + ), + )) + } +} + +pub fn parse_ubx_enum_type( + enum_name: Ident, + attrs: Vec, + in_variants: Punctuated, +) -> syn::Result { + let (from_fn, into_fn, rest_handling) = + parse_ubx_extend_attrs("#[ubx_extend]", enum_name.span(), &attrs)?; + + let attr = attrs + .iter() + .find(|a| a.path.is_ident("repr")) + .ok_or_else(|| { + Error::new( + enum_name.span(), + format!("No repr attribute for ubx_type enum {}", enum_name), + ) + })?; + let meta = attr.parse_meta()?; + let repr: Type = match meta { + syn::Meta::List(list) if list.nested.len() == 1 => { + if let syn::NestedMeta::Meta(syn::Meta::Path(ref p)) = list.nested[0] { + if !p.is_ident("u8") { + unimplemented!(); + } + } else { + return Err(Error::new( + list.nested[0].span(), + "Invalid repr attribute for ubx_type enum", + )); + } + syn::parse_quote! { u8 } + } + _ => { + return Err(Error::new( + attr.span(), + "Invalid repr attribute for ubx_type enum", + )) + } + }; + let mut variants = Vec::with_capacity(in_variants.len()); + for var in in_variants { + if syn::Fields::Unit != var.fields { + return Err(Error::new( + var.fields.span(), + "Invalid variant for ubx_type enum", + )); + } + let var_sp = var.ident.span(); + let (_, expr) = var + .discriminant + .ok_or_else(|| Error::new(var_sp, "ubx_type enum variant should has value"))?; + let variant_value = if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(litint), + .. + }) = expr + { + litint.base10_parse::()? + } else { + return Err(Error::new( + expr.span(), + "Invalid variant value for ubx_type enum", + )); + }; + variants.push((var.ident, variant_value)); + } + + let attrs = attrs + .into_iter() + .filter(|x| !x.path.is_ident("ubx") && !x.path.is_ident("ubx_extend")) + .collect(); + + Ok(UbxExtendEnum { + attrs, + name: enum_name, + repr, + from_fn, + into_fn, + rest_handling, + variants, + }) +} + +pub fn parse_bitflags(mac: syn::ItemMacro) -> syn::Result { + let (from_fn, into_fn, rest_handling) = + parse_ubx_extend_attrs("#[ubx_extend_bitflags]", mac.span(), &mac.attrs)?; + + let ast: BitFlagsAst = syn::parse2(mac.mac.tokens)?; + + let valid_types: [(Type, u32); 3] = [ + (syn::parse_quote!(u8), 1), + (syn::parse_quote!(u16), 2), + (syn::parse_quote!(u32), 4), + ]; + let nbits = if let Some((_ty, size)) = valid_types.iter().find(|x| x.0 == ast.repr_ty) { + size * 8 + } else { + let mut valid_type_names = String::with_capacity(200); + for (t, _) in &valid_types { + if !valid_type_names.is_empty() { + valid_type_names.push_str(", "); + } + valid_type_names.push_str(&t.into_token_stream().to_string()); + } + return Err(Error::new( + ast.repr_ty.span(), + format!("Not supported type, expect one of {:?}", valid_type_names), + )); + }; + + let mut consts = Vec::with_capacity(ast.items.len()); + for item in ast.items { + consts.push(BitFlagsMacroItem { + attrs: item.attrs, + name: item.name, + value: item.value.base10_parse()?, + }); + } + + Ok(BitFlagsMacro { + nbits, + vis: ast.vis, + attrs: ast.attrs, + name: ast.ident, + repr_ty: ast.repr_ty, + consts, + from_fn, + into_fn, + rest_handling, + }) +} + +pub fn parse_idents_list(input: proc_macro2::TokenStream) -> syn::Result { + syn::parse2(input) +} + +fn parse_ubx_extend_attrs( + ubx_extend_name: &str, + item_sp: Span, + attrs: &[Attribute], +) -> syn::Result<( + Option, + Option, + Option, +)> { + let attr = attrs + .iter() + .find(|a| a.path.is_ident("ubx")) + .ok_or_else(|| Error::new(item_sp, format!("No ubx attribute for {}", ubx_extend_name)))?; + let meta = attr.parse_meta()?; + let mut from_fn = None; + let mut rest_handling = None; + let mut into_fn = None; + let meta_sp = meta.span(); + match meta { + syn::Meta::List(list) => { + for item in list.nested { + if let syn::NestedMeta::Meta(syn::Meta::Path(p)) = item { + if p.is_ident("from") { + from_fn = Some(UbxTypeFromFn::From); + } else if p.is_ident("into_raw") { + into_fn = Some(UbxTypeIntoFn::Raw); + } else if p.is_ident("from_unchecked") { + from_fn = Some(UbxTypeFromFn::FromUnchecked); + } else if p.is_ident("rest_reserved") || p.is_ident("rest_error") { + if rest_handling.is_some() { + return Err(Error::new( + p.span(), + "rest_reserved or rest_error already defined", + )); + } + + rest_handling = Some(if p.is_ident("rest_reserved") { + UbxEnumRestHandling::Reserved + } else { + UbxEnumRestHandling::ErrorProne + }); + } else { + return Err(Error::new(p.span(), "Invalid ubx attribute")); + } + } else { + return Err(Error::new(item.span(), "Invalid ubx attribute")); + } + } + } + _ => return Err(Error::new(attr.span(), "Invalid ubx attributes")), + } + + if from_fn == Some(UbxTypeFromFn::From) + && rest_handling == Some(UbxEnumRestHandling::ErrorProne) + { + return Err(Error::new( + meta_sp, + "you should use rest_error with from_unchecked", + )); + } + + Ok((from_fn, into_fn, rest_handling)) +} + +fn parse_ubx_attr(attrs: &[Attribute], struct_name: &Ident) -> syn::Result { + let attr = attrs + .iter() + .find(|a| a.path.is_ident("ubx")) + .ok_or_else(|| { + Error::new( + struct_name.span(), + format!("No ubx attribute for struct {}", struct_name), + ) + })?; + let meta = attr.parse_meta()?; + let meta = match meta { + syn::Meta::List(x) => x, + _ => return Err(Error::new(meta.span(), "Invalid ubx attribute syntax")), + }; + + let mut class = None; + let mut id = None; + let mut fixed_payload_len = None; + let mut flags = Vec::new(); + let mut max_payload_len = None; + + for e in &meta.nested { + match e { + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + path, lit, .. + })) => { + if path.is_ident("class") { + if class.is_some() { + return Err(Error::new(e.span(), "Duplicate \"class\" attribute")); + } + class = match lit { + syn::Lit::Int(x) => Some(x.base10_parse::()?), + _ => return Err(Error::new(lit.span(), "Should be integer literal")), + }; + } else if path.is_ident("id") { + if id.is_some() { + return Err(Error::new(e.span(), "Duplicate \"id\" attribute")); + } + id = match lit { + syn::Lit::Int(x) => Some(x.base10_parse::()?), + _ => return Err(Error::new(lit.span(), "Should be integer literal")), + }; + } else if path.is_ident("fixed_payload_len") { + if fixed_payload_len.is_some() { + return Err(Error::new( + e.span(), + "Duplicate \"fixed_payload_len\" attribute", + )); + } + fixed_payload_len = match lit { + syn::Lit::Int(x) => Some(x.base10_parse::()?), + _ => return Err(Error::new(lit.span(), "Should be integer literal")), + }; + } else if path.is_ident("max_payload_len") { + if max_payload_len.is_some() { + return Err(Error::new( + e.span(), + "Duplicate \"max_payload_len\" attribute", + )); + } + max_payload_len = match lit { + syn::Lit::Int(x) => Some(x.base10_parse::()?), + _ => return Err(Error::new(lit.span(), "Should be integer literal")), + }; + } else if path.is_ident("flags") { + if !flags.is_empty() { + return Err(Error::new(path.span(), "Duplicate flags")); + } + let my_flags = match lit { + syn::Lit::Str(x) => x.parse::()?, + _ => return Err(Error::new(lit.span(), "Should be string literal")), + }; + flags = my_flags.0.into_iter().collect(); + } else { + return Err(Error::new(path.span(), "Unsupported attribute")); + } + } + _ => return Err(Error::new(e.span(), "Unsupported attribute")), + } + } + let class = class.ok_or_else(|| Error::new(meta.span(), "No \"class\" attribute"))?; + let id = id.ok_or_else(|| Error::new(meta.span(), "No \"id\" attribute"))?; + + let payload_len = match (max_payload_len, fixed_payload_len) { + (Some(x), None) => PayloadLen::Max(x), + (None, Some(x)) => PayloadLen::Fixed(x), + (Some(_), Some(_)) => { + return Err(Error::new( + meta.span(), + "You should not note max_payload_len AND fixed_payload_len", + )) + } + (None, None) => { + return Err(Error::new( + meta.span(), + "You should note max_payload_len or fixed_payload_len", + )) + } + }; + + Ok(PackHeader { + class, + id, + payload_len, + flags, + }) +} + +fn extract_item_comment(attrs: &[Attribute]) -> syn::Result { + let mut doc_comments = String::new(); + for a in attrs { + if a.path.is_ident("doc") { + let meta = a.parse_meta()?; + match meta { + syn::Meta::NameValue(syn::MetaNameValue { lit, .. }) => { + let lit = match lit { + syn::Lit::Str(s) => s, + _ => return Err(Error::new(lit.span(), "Invalid comment")), + }; + doc_comments.push_str(&lit.value()); + } + _ => return Err(Error::new(a.span(), "Invalid comments")), + } + } + } + Ok(doc_comments) +} + +fn parse_fields(fields: Fields) -> syn::Result> { + let fields = match fields { + syn::Fields::Named(x) => x, + _ => { + return Err(Error::new(fields.span(), "Unsupported fields format")); + } + }; + let mut ret = Vec::with_capacity(fields.named.len()); + for f in fields.named { + let f_sp = f.span(); + let syn::Field { + ident: name, + attrs, + ty, + .. + } = f; + let size_bytes = field_size_bytes(&ty)?; + let name = name.ok_or_else(|| Error::new(f_sp, "No field name"))?; + let comment = extract_item_comment(&attrs)?; + let mut map = PackFieldMap::none(); + for a in attrs { + if !a.path.is_ident("doc") { + if !map.is_none() { + return Err(Error::new( + a.span(), + "Two map attributes for the same field", + )); + } + map = a.parse_args::()?; + } + } + + if let Some(ref map_ty) = map.map_type { + if map_ty.ty == ty { + return Err(Error::new( + map_ty.ty.span(), + "You map type to the same type", + )); + } + } + + ret.push(PackField { + name, + ty, + map, + comment, + size_bytes, + }); + } + + Ok(ret) +} + +mod kw { + syn::custom_keyword!(map_type); + syn::custom_keyword!(scale); + syn::custom_keyword!(alias); + syn::custom_keyword!(default_for_builder); + syn::custom_keyword!(may_fail); + syn::custom_keyword!(from); + syn::custom_keyword!(is_valid); + syn::custom_keyword!(get_as_ref); +} + +impl Parse for PackFieldMap { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut map = PackFieldMap::none(); + let mut map_ty = None; + let mut custom_from_fn: Option = None; + let mut custom_is_valid_fn: Option = None; + + while !input.is_empty() { + let lookahead = input.lookahead1(); + + if lookahead.peek(kw::map_type) { + input.parse::()?; + input.parse::()?; + map_ty = Some(input.parse()?); + } else if lookahead.peek(kw::scale) { + input.parse::()?; + input.parse::()?; + map.scale = Some(input.parse()?); + } else if lookahead.peek(kw::alias) { + input.parse::()?; + input.parse::()?; + map.alias = Some(input.parse()?); + } else if lookahead.peek(kw::may_fail) { + input.parse::()?; + map.convert_may_fail = true; + } else if lookahead.peek(kw::from) { + input.parse::()?; + input.parse::()?; + custom_from_fn = Some(input.parse()?); + } else if lookahead.peek(kw::is_valid) { + input.parse::()?; + input.parse::()?; + custom_is_valid_fn = Some(input.parse()?); + } else if lookahead.peek(kw::get_as_ref) { + input.parse::()?; + map.get_as_ref = true; + } else { + return Err(lookahead.error()); + } + + if input.peek(Token![,]) { + input.parse::()?; + } + } + + if let Some(map_ty) = map_ty { + let mut map_type = MapType::new(map_ty, map.convert_may_fail); + if let Some(custom_from_fn) = custom_from_fn { + map_type.from_fn = custom_from_fn.into_token_stream(); + } + if let Some(custom_is_valid_fn) = custom_is_valid_fn { + map_type.is_valid_fn = custom_is_valid_fn.into_token_stream(); + } + map.map_type = Some(map_type); + } + + Ok(map) + } +} + +struct Comment(String); + +impl Parse for Comment { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.peek(Token![#]) && input.peek2(syn::token::Bracket) && input.peek3(Ident) { + let attrs = input.call(Attribute::parse_outer)?; + + Ok(Comment(extract_item_comment(&attrs)?)) + } else { + Ok(Comment(String::new())) + } + } +} + +fn field_size_bytes(ty: &Type) -> syn::Result> { + //TODO: make this array static + //TODO: support f32, f64 + let valid_types: [(Type, NonZeroUsize); 6] = [ + (syn::parse_quote!(u8), NonZeroUsize::new(1).unwrap()), + (syn::parse_quote!(i8), NonZeroUsize::new(1).unwrap()), + (syn::parse_quote!(u16), NonZeroUsize::new(2).unwrap()), + (syn::parse_quote!(i16), NonZeroUsize::new(2).unwrap()), + (syn::parse_quote!(u32), NonZeroUsize::new(4).unwrap()), + (syn::parse_quote!(i32), NonZeroUsize::new(4).unwrap()), + ]; + if let Some((_ty, size)) = valid_types.iter().find(|x| x.0 == *ty) { + Ok(Some(*size)) + } else if let syn::Type::Array(ref fixed_array) = ty { + if *fixed_array.elem != syn::parse_quote!(u8) { + return Err(Error::new(fixed_array.elem.span(), "Only u8 supported")); + } + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(ref len), + .. + }) = fixed_array.len + { + let len_val: usize = len.base10_parse()?; + Ok(NonZeroUsize::new(len_val)) + } else { + Err(Error::new( + fixed_array.len.span(), + "Can not interpret array length", + )) + } + } else { + let mut valid_type_names = String::with_capacity(200); + for (t, _) in &valid_types { + if !valid_type_names.is_empty() { + valid_type_names.push_str(", "); + } + valid_type_names.push_str(&t.into_token_stream().to_string()); + } + Err(Error::new( + ty.span(), + format!("Not supported type, expect one of {:?}", valid_type_names), + )) + } +} + +struct BitFlagsAst { + attrs: Vec, + vis: syn::Visibility, + _struct_token: Token![struct], + ident: Ident, + _colon_token: Token![:], + repr_ty: Type, + _brace_token: syn::token::Brace, + items: Punctuated, +} + +struct BitFlagsAstConst { + attrs: Vec, + _const_token: Token![const], + name: Ident, + _eq_token: Token![=], + value: syn::LitInt, +} + +impl Parse for BitFlagsAst { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + Ok(Self { + attrs: input.call(Attribute::parse_outer)?, + vis: input.parse()?, + _struct_token: input.parse()?, + ident: input.parse()?, + _colon_token: input.parse()?, + repr_ty: input.parse()?, + _brace_token: braced!(content in input), + items: content.parse_terminated(BitFlagsAstConst::parse)?, + }) + } +} + +impl Parse for BitFlagsAstConst { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { + attrs: input.call(Attribute::parse_outer)?, + _const_token: input.parse()?, + name: input.parse()?, + _eq_token: input.parse()?, + value: input.parse()?, + }) + } +} + +impl Parse for PacketFlag { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(kw::default_for_builder) { + input.parse::()?; + Ok(PacketFlag::DefaultForBuilder) + } else { + Err(lookahead.error()) + } + } +} + +struct StructFlags(Punctuated); + +impl Parse for StructFlags { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let flags = input.parse_terminated(PacketFlag::parse)?; + Ok(Self(flags)) + } +} + +impl Parse for RecvPackets { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let union_enum_name: Ident = input.parse()?; + let content; + let _brace_token: syn::token::Brace = braced!(content in input); + content.parse::()?; + content.parse::()?; + let unknown_ty: Ident = content.parse()?; + content.parse::()?; + let packs: Punctuated = content.parse_terminated(Ident::parse)?; + Ok(Self { + union_enum_name, + unknown_ty, + all_packets: packs.into_iter().collect(), + }) + } +} diff --git a/ublox_derive/src/lib.rs b/ublox_derive/src/lib.rs index 84446f6..8a7720a 100644 --- a/ublox_derive/src/lib.rs +++ b/ublox_derive/src/lib.rs @@ -1,566 +1,182 @@ extern crate proc_macro; +mod input; +mod output; +#[cfg(test)] +mod tests; +mod types; + use proc_macro2::TokenStream; -use proc_macro_error::{abort, proc_macro_error}; -use quote::{format_ident, quote, quote_spanned}; -use syn::parse::Parser; -use syn::spanned::Spanned; +use quote::ToTokens; use syn::{ - parse_macro_input, parse_quote, Attribute, Data, DeriveInput, Fields, FieldsNamed, Ident, + parse_macro_input, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DeriveInput, + Fields, Ident, Variant, }; -#[derive(Clone, Debug)] -struct Bitrange { - lsb: usize, - msb: usize, - ident: Ident, - enum_type: Option, - field_type: syn::TypePath, -} +#[proc_macro_attribute] +pub fn ubx_packet_recv( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ret = if let Data::Struct(data) = input.data { + generate_code_for_recv_packet(input.ident, input.attrs, data.fields) + } else { + Err(syn::Error::new( + input.ident.span(), + "This attribute can only be used for struct", + )) + }; -#[derive(Clone, Debug)] -struct Bitfield { - num_bits: usize, - ranges: Vec, + ret.map(|x| x.into()) + .unwrap_or_else(|err| err.to_compile_error().into()) } -impl Bitfield { - fn new(num_bits: usize) -> Bitfield { - Bitfield { - num_bits: num_bits, - ranges: vec![], - } - } +#[proc_macro_attribute] +pub fn ubx_packet_send( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ret = if let Data::Struct(data) = input.data { + generate_code_for_send_packet(input.ident, input.attrs, data.fields) + } else { + Err(syn::Error::new( + input.ident.span(), + "This attribute can only be used for struct", + )) + }; - fn add_range(&mut self, range: Bitrange) { - self.ranges.push(range); - } + ret.map(|x| x.into()) + .unwrap_or_else(|err| err.to_compile_error().into()) } -#[derive(Debug)] -enum Member { - Bitfield(Bitfield), - Primitive(syn::Field), -} +#[proc_macro_attribute] +pub fn ubx_packet_recv_send( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ret = if let Data::Struct(data) = input.data { + generate_code_for_recv_send_packet(input.ident, input.attrs, data.fields) + } else { + Err(syn::Error::new( + input.ident.span(), + "This attribute can only be used for struct", + )) + }; -enum UbxAttribute { - UbxBitRange((usize, usize)), - UbxBitField(usize), - UbxEnum(Ident), + ret.map(|x| x.into()) + .unwrap_or_else(|err| err.to_compile_error().into()) } -fn parse_attribute(attr: &Attribute) -> Option { - //println!("{:#?}", attr); - let parser = - syn::punctuated::Punctuated::::parse_separated_nonempty; - - let name = attr.path.get_ident().unwrap(); - let arguments = attr.parse_args_with(parser).unwrap(); +#[proc_macro_attribute] +pub fn ubx_extend( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ret = if let Data::Enum(data) = input.data { + extend_enum(input.ident, input.attrs, data.variants) + } else { + Err(syn::Error::new( + input.ident.span(), + "This attribute can only be used for enum", + )) + }; - match name.to_string().as_str() { - "ubx_bitfield" => { - if arguments.len() != 1 { - panic!("Incorrect number of arguments to ubx_bitfield"); - } - let arg = syn::parse2(arguments[0].clone()).unwrap(); - match &arg { - syn::Lit::Int(litint) => { - Some(UbxAttribute::UbxBitField(litint.base10_parse().unwrap())) - } - _ => { - abort!(&arguments[0], "Only int literals allowed!"); - } - } - } - "ubx_bitrange" => { - if arguments.len() != 1 { - panic!("Incorrect number of arguments to ubx_bitrange"); - } - let parser = syn::punctuated::Punctuated::::parse_separated_nonempty; - let bits = parser.parse2(arguments[0].clone()).unwrap(); - if bits.len() != 2 { - panic!("Bit slice may only contain 2 elements in ubx_bitrange"); - } - let msb: usize = match &bits[0] { - syn::Lit::Int(litint) => litint.base10_parse().unwrap(), - _ => { - abort!(&bits[0], "Only int literals allowed!"); - } - }; - let lsb: usize = match &bits[1] { - syn::Lit::Int(litint) => litint.base10_parse().unwrap(), - _ => { - abort!(&bits[1], "Only int literals allowed!"); - } - }; - Some(UbxAttribute::UbxBitRange((msb, lsb))) - } - "ubx_enum" => { - if arguments.len() != 1 { - panic!("Incorrect number of arguments to ubx_enum"); - } - Some(UbxAttribute::UbxEnum( - match arguments[0].clone().into_iter().next().unwrap() { - proc_macro2::TokenTree::Ident(ident) => ident, - _ => { - abort!(arguments[0], "Must specify an identifier for ubx_enum"); - } - }, - )) - } - _ => None, - } + ret.map(|x| x.into()) + .unwrap_or_else(|err| err.to_compile_error().into()) } -fn find_struct_segments(fields: &FieldsNamed) -> Vec { - let mut segments = vec![]; - let mut current_bitfield: Option = None; - for field in fields.named.iter() { - let tags: Vec<_> = field.attrs.iter().map(parse_attribute).collect(); - let bitfield: Vec<_> = tags - .iter() - .filter_map(|x| match x { - Some(UbxAttribute::UbxBitField(size)) => Some(size), - _ => None, - }) - .collect(); - let has_bitfield = bitfield.len() > 0; - - let bitrange: Vec<_> = tags - .iter() - .filter_map(|x| match x { - Some(UbxAttribute::UbxBitRange((msb, lsb))) => Some((msb, lsb)), - _ => None, - }) - .collect(); - let has_bitrange = bitrange.len() > 0; - - let enum_type: Vec<_> = tags - .iter() - .filter_map(|x| match x { - Some(UbxAttribute::UbxEnum(e)) => Some(e), - _ => None, - }) - .collect(); - let enum_type = if enum_type.len() > 0 { - Some(enum_type[0].clone()) - } else { - None - }; - - if has_bitfield { - if let Some(field) = current_bitfield { - segments.push(Member::Bitfield(field)); - } - current_bitfield = Some(Bitfield::new(*bitfield[0])); - } - - if has_bitrange { - let (msb, lsb) = bitrange[0]; - let bitrange = Bitrange { - lsb: *lsb, - msb: *msb, - ident: field.ident.as_ref().unwrap().clone(), - enum_type: enum_type, - field_type: match &field.ty { - syn::Type::Path(path) => path.clone(), - _ => { - abort!(field, "Only path types allowed for bitmap ranges"); - } - }, - }; - match &mut current_bitfield { - Some(bitfield) => { - bitfield.add_range(bitrange); - } - None => { - abort!(field, "Must have an active bitfield to specify a bitrange!"); - } - } - } else { - if let Some(bitfield) = current_bitfield { - segments.push(Member::Bitfield(bitfield)); - } - current_bitfield = None; +#[proc_macro_attribute] +pub fn ubx_extend_bitflags( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as syn::ItemMacro); - segments.push(Member::Primitive(field.clone())); - } - } - if let Some(field) = current_bitfield { - segments.push(Member::Bitfield(field)); - } - //println!("{:#?}", segments); - segments + extend_bitflags(input) + .map(|x| x.into()) + .unwrap_or_else(|err| err.to_compile_error().into()) } -struct Accessor { - getter: TokenStream, - setter: TokenStream, - trait_getter: TokenStream, - trait_setter: TokenStream, +#[proc_macro] +pub fn define_recv_packets(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + do_define_recv_packets(input.into()) + .map(|x| x.into()) + .unwrap_or_else(|err| err.to_compile_error().into()) } -//fn build_bitrange_accessors(offset: &TokenStream, bitfield: &Bitfield, bitrange: &Bitrange) -> (TokenStream, TokenStream, TokenStream) { -fn build_bitrange_accessors( - offset: &TokenStream, - bitfield: &Bitfield, - bitrange: &Bitrange, -) -> Accessor { - let underlying_fn_name = format_ident!("get_{}_underlying", bitrange.ident); - let underlying_set_fn_name = format_ident!("set_{}_underlying", bitrange.ident); - let underlying_type = format_ident!("u{}", bitfield.num_bits); - let getter_fn_name = format_ident!("get_{}", bitrange.ident); - let setter_fn_name = format_ident!("set_{}", bitrange.ident); - let span = bitrange.ident.span(); - let return_type = if let Some(ident) = &bitrange.enum_type { - let field_type = &ident; - parse_quote! { Option<#field_type> } - } else { - bitrange.field_type.clone() - }; - let field_type = if let Some(ident) = &bitrange.enum_type { - parse_quote! { #ident } - } else { - bitrange.field_type.clone() - }; - let msb = bitrange.msb; - let lsb = bitrange.lsb; - let shifter_fn_name = format_ident!("shift_{}", bitrange.ident); - let type_cvt_name = format_ident!("type_cvt_{}", bitrange.ident); - let type_uncvt_name = format_ident!("type_uncvt_{}", bitrange.ident); - let type_cvt = if let Some(enumtype) = &bitrange.enum_type { - let fromname = format_ident!("from_{}", underlying_type); - quote_spanned! { - span => - #enumtype::#fromname(value) - } - } else if return_type.path.get_ident().unwrap().to_string() == "bool" { - quote_spanned! { - span => - value != 0 - } - } else { - quote_spanned! { - span => - value.try_into().unwrap() - } - }; - let type_uncvt = if let Some(enumtype) = &bitrange.enum_type { - let toname = format_ident!("to_{}", underlying_type); - quote_spanned! { - span => - value.#toname().unwrap() - } - } else if return_type.path.get_ident().unwrap().to_string() == "bool" { - quote_spanned! { - span => - if value { 1 } else { 0 } - } - } else { - quote_spanned! { - span => - value.try_into().unwrap() - } - }; - Accessor { - getter: quote_spanned! { - span => - fn #underlying_fn_name(&self) -> #underlying_type { - #underlying_type::from_le_bytes(self.data[#offset..#offset + std::mem::size_of::<#underlying_type>()].try_into().unwrap()) - } - - fn #type_cvt_name(value: #underlying_type) -> #return_type { - #type_cvt - } - - fn #shifter_fn_name(&self) -> #underlying_type { - let underlying = self.#underlying_fn_name(); - (underlying >> #lsb) & ((1 << (#msb - #lsb + 1)) - 1) - } - - fn #getter_fn_name(&self) -> #return_type { - Self::#type_cvt_name(self.#shifter_fn_name()) - } - }, - trait_getter: quote_spanned! { - span => - fn #getter_fn_name(&self) -> #return_type; - }, - setter: quote_spanned! { - span => - fn #underlying_set_fn_name(&mut self, value: #underlying_type) { - let bytes = value.to_le_bytes(); - self.data[#offset..#offset + std::mem::size_of::<#underlying_type>()].clone_from_slice(&bytes); - } - - fn #type_uncvt_name(value: #field_type) -> #underlying_type { - #type_uncvt - } - - fn #setter_fn_name(&mut self, value: #field_type) { - let original = self.#underlying_fn_name(); - let new_field_value = Self::#type_uncvt_name(value); - let mask = ((1 << (#msb - #lsb + 1)) - 1) << #lsb; - let newval = (original & !mask) | (new_field_value << #lsb); - //println!("{} {} {} {}", original, new_field_value, mask, newval); - self.#underlying_set_fn_name(newval); - } - }, - trait_setter: quote_spanned! { - span => - fn #setter_fn_name(&mut self, value: #field_type); - }, - } +fn generate_code_for_recv_packet( + pack_name: Ident, + attrs: Vec, + fields: Fields, +) -> syn::Result { + let pack_desc = input::parse_packet_description(pack_name, attrs, fields)?; + + let mut code = output::generate_types_for_packet(&pack_desc); + let recv_code = output::generate_recv_code_for_packet(&pack_desc); + code.extend(recv_code); + Ok(code) } -fn process_struct(struct_name: &Ident, fields: &FieldsNamed) -> TokenStream { - let segments = find_struct_segments(fields); - - let fs: Vec<_> = fields - .named - .iter() - .map(|f| { - for attr in f.attrs.iter() { - parse_attribute(&attr); - } - }) - .collect(); - - let sizes: Vec<_> = segments - .iter() - .map(|f| match f { - Member::Bitfield(bitfield) => { - let nbits = bitfield.num_bits; - quote_spanned! { - bitfield.ranges[0].ident.span() => - (#nbits / 8) - } - } - Member::Primitive(f) => { - let ftype = &f.ty; - quote_spanned! { - f.span() => - std::mem::size_of::<#ftype>() - } - } - }) - .collect(); - - let offsets: Vec<_> = sizes - .iter() - .scan(quote! { 0 }, |state, size| { - let orig_state = state.clone(); - *state = quote! { - #state + #size - }; - Some(orig_state) - }) - .collect(); - - let accessors: Vec = segments.iter().zip(offsets).map(|(f, o)| { - match f { - Member::Bitfield(bitfield) => { - let accessors: Vec = bitfield.ranges.iter().map(|range| { - //bitfield.ranges.iter().map(|range| { - build_bitrange_accessors(&o, &bitfield, &range) - }).collect(); - accessors - /*((quote_spanned! { - bitfield.ranges[0].ident.span() => - #(#getters)* - }, quote_spanned! { - bitfield.ranges[0].ident.span() => - #(#trait_getters)* - }), - quote_spanned! { - bitfield.ranges[0].ident.span() => - #(#setters)* - })*/ - } - Member::Primitive(f) => { - let enum_attrs: Vec<_> = f.attrs.iter().filter_map(|attr| { - parse_attribute(attr) - }).filter_map(|attr| { - match attr { - UbxAttribute::UbxEnum(ident) => Some(ident), - _ => None, - } - }).collect(); - - let membername = f.ident.as_ref().unwrap(); - let get_fname = format_ident!("get_{}", membername); - let set_fname = format_ident!("set_{}", membername); - let ftype = &f.ty; - if enum_attrs.len() > 0 { - let enumtype = &enum_attrs[0]; - let fromname = format_ident!("from_{}", match ftype { - syn::Type::Path(path) => { path.path.get_ident().unwrap() } - _ => { abort!(f, "Must specify a primitive int field type"); } - }); - let enum_cvt = format_ident!("to_{}", match ftype { - syn::Type::Path(path) => { path.path.get_ident().unwrap() } - _ => { abort!(f, "Must specify a primitive int field type"); } - }); - vec![Accessor { - getter: quote_spanned! { - f.span() => - fn #get_fname(&self) -> Option<#enumtype> { - let x = #ftype::from_le_bytes(self.data[#o..#o + std::mem::size_of::<#ftype>()].try_into().unwrap()); - #enumtype::#fromname(x) - } - }, - trait_getter: quote_spanned! { - f.span() => - fn #get_fname(&self) -> Option<#enumtype>; - }, - setter: quote_spanned! { - f.span() => - fn #set_fname(&mut self, value: #enumtype) { - let value = value.#enum_cvt(); - let bytes = value.to_le_bytes(); - self.data[#o..#o + std::mem::size_of::<#ftype>()].clone_from_slice(&bytes); - } - }, - trait_setter: quote_spanned! { - f.span() => - fn #set_fname(&mut self, value: #enumtype); - } - }] - } else { - vec![Accessor { - getter: quote_spanned! { - f.span() => - fn #get_fname(&self) -> #ftype { - #ftype::from_le_bytes(self.data[#o..#o + std::mem::size_of::<#ftype>()].try_into().unwrap()) - } - }, - trait_getter: quote_spanned! { - f.span() => - fn #get_fname(&self) -> #ftype; - }, - setter: quote_spanned! { - f.span() => - fn #set_fname(&mut self, value: #ftype) { - let bytes = value.to_le_bytes(); - self.data[#o..#o + std::mem::size_of::<#ftype>()].clone_from_slice(&bytes); - } - }, - trait_setter: quote_spanned! { - f.span() => - fn #set_fname(&mut self, value: #ftype); - } - }] - } - } - } - }).flatten().collect(); - //let (getters, trait_getters) = getters.iter().unzip(); - - let mut getters = vec![]; - let mut trait_getters = vec![]; - let mut setters = vec![]; - let mut trait_setters = vec![]; - for accessor in accessors.iter() { - getters.push(&accessor.getter); - trait_getters.push(&accessor.trait_getter); - setters.push(&accessor.setter); - trait_setters.push(&accessor.trait_setter); - } - - let getter_trait_name = format_ident!("{}Getter", struct_name); - let setter_trait_name = format_ident!("{}Setter", struct_name); - let ref_struct_name = format_ident!("{}Ref", struct_name); - let mut_ref_struct_name = format_ident!("{}MutRef", struct_name); - - quote! { - pub trait #getter_trait_name { - #(#trait_getters)* - } - - pub trait #setter_trait_name { - #(#trait_setters)* - } - - struct #ref_struct_name<'a> { - data: &'a [u8; 0 #(+ #sizes)*], - } - - impl<'a> #ref_struct_name<'a> { - pub fn new(data: &'a [u8; 0 #(+ #sizes)*]) -> #ref_struct_name { - #ref_struct_name { - data: data, - } - } - } - - impl<'a> #getter_trait_name for #ref_struct_name<'a> { - #(#getters)* - } - - struct #mut_ref_struct_name<'a> { - data: &'a mut [u8; 0 #(+ #sizes)*], - } - - impl<'a> #mut_ref_struct_name<'a> { - pub fn new(data: &'a mut [u8; 0 #(+ #sizes)*]) -> #mut_ref_struct_name { - #mut_ref_struct_name { - data: data, - } - } - } - - impl<'a> #getter_trait_name for #mut_ref_struct_name<'a> { - #(#getters)* - } - - impl<'a> #setter_trait_name for #mut_ref_struct_name<'a> { - #(#setters)* - } - - // TODO: Implement a more logical Debug trait - // TODO: Make the pub-ness optional - #[derive(Debug, PartialEq)] - pub struct #struct_name { - data: [u8; 0 #(+ #sizes)*], - } +fn generate_code_for_send_packet( + pack_name: Ident, + attrs: Vec, + fields: Fields, +) -> syn::Result { + let pack_desc = input::parse_packet_description(pack_name, attrs, fields)?; + + let mut code = output::generate_types_for_packet(&pack_desc); + let send_code = output::generate_send_code_for_packet(&pack_desc); + code.extend(send_code); + Ok(code) +} - impl #struct_name { - pub fn new(data: [u8; 0 #(+ #sizes)*]) -> #struct_name { - #struct_name { - data: data, - } - } - } +fn generate_code_for_recv_send_packet( + pack_name: Ident, + attrs: Vec, + fields: Fields, +) -> syn::Result { + let pack_desc = input::parse_packet_description(pack_name, attrs, fields)?; + + let mut code = output::generate_types_for_packet(&pack_desc); + let send_code = output::generate_send_code_for_packet(&pack_desc); + code.extend(send_code); + let recv_code = output::generate_recv_code_for_packet(&pack_desc); + code.extend(recv_code); + Ok(code) +} - impl #getter_trait_name for #struct_name { - #(#getters)* - } +fn extend_enum( + name: Ident, + attrs: Vec, + variants: Punctuated, +) -> syn::Result { + let ext_enum = input::parse_ubx_enum_type(name, attrs, variants)?; + let code = output::generate_code_to_extend_enum(&ext_enum); + Ok(code) +} - impl #setter_trait_name for #struct_name { - #(#setters)* - } +fn extend_bitflags(mac: syn::ItemMacro) -> syn::Result { + if !mac.mac.path.is_ident("bitflags") { + return Err(syn::Error::new( + mac.ident + .as_ref() + .map(|x| x.span()) + .unwrap_or_else(|| mac.span()), + format!( + "Expect bitflags invocation here, instead got '{}'", + mac.mac.path.into_token_stream().to_string() + ), + )); } + let bitflags = input::parse_bitflags(mac)?; + output::generate_code_to_extend_bitflags(bitflags) } -#[proc_macro_error] -#[proc_macro_attribute] -pub fn ubx_packet( - attr: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - match input.data { - Data::Struct(ref data) => match data.fields { - Fields::Named(ref fields) => { - proc_macro::TokenStream::from(process_struct(&input.ident, fields)) - } - Fields::Unnamed(ref fields) => { - unimplemented!(); - } - Fields::Unit => { - unimplemented!(); - } - }, - Data::Enum(_) | Data::Union(_) => unimplemented!(), - } +fn do_define_recv_packets(input: TokenStream) -> syn::Result { + let recv_packs = input::parse_idents_list(input)?; + Ok(output::generate_code_for_parse(&recv_packs)) } diff --git a/ublox_derive/src/output.rs b/ublox_derive/src/output.rs new file mode 100644 index 0000000..42fadb3 --- /dev/null +++ b/ublox_derive/src/output.rs @@ -0,0 +1,571 @@ +use crate::types::BitFlagsMacro; +use crate::types::{ + PackDesc, PackField, PacketFlag, PayloadLen, RecvPackets, UbxEnumRestHandling, UbxExtendEnum, + UbxTypeFromFn, UbxTypeIntoFn, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use std::{collections::HashSet, convert::TryFrom}; +use syn::{parse_quote, Ident, Type}; + +pub fn generate_recv_code_for_packet(pack_descr: &PackDesc) -> TokenStream { + let pack_name = &pack_descr.name; + let ref_name = format_ident!("{}Ref", pack_descr.name); + + let mut getters = Vec::with_capacity(pack_descr.fields.len()); + let mut field_validators = Vec::new(); + + let mut off = 0usize; + for (field_index, f) in pack_descr.fields.iter().enumerate() { + let ty = f.intermediate_type(); + let get_name = f.intermediate_field_name(); + let field_comment = &f.comment; + + if let Some(size_bytes) = f.size_bytes.map(|x| x.get()) { + let mut get_value_lines = vec![get_raw_field_code(f, off, quote! { self.0 })]; + if f.map.get_as_ref { + get_value_lines[0] = quote! { &self.0[#off .. (#off + #size_bytes)] }; + } + + if let Some(ref out_ty) = f.map.map_type { + let get_raw = &get_value_lines[0]; + let new_line = quote! { let val = #get_raw ; }; + get_value_lines[0] = new_line; + if f.map.convert_may_fail { + let get_val = if !f.map.get_as_ref { + get_raw_field_code(f, off, quote! { payload }) + } else { + quote! { &payload[#off .. (#off + #size_bytes)] } + }; + let is_valid_fn = &out_ty.is_valid_fn; + field_validators.push(quote! { + let val = #get_val; + if !#is_valid_fn(val) { + return Err(ParserError::InvalidField{ + packet: #pack_name, + field: stringify!(#get_name) + }); + } + }); + } + let from_fn = &out_ty.from_fn; + get_value_lines.push(quote! { + #from_fn(val) + }); + } + + if let Some(ref scale) = f.map.scale { + let last_i = get_value_lines.len() - 1; + let last_line = &get_value_lines[last_i]; + let new_last_line = quote! { let val = #last_line ; }; + get_value_lines[last_i] = new_last_line; + get_value_lines.push(quote! {val * #scale }); + } + getters.push(quote! { + #[doc = #field_comment] + #[inline] + pub fn #get_name(&self) -> #ty { + #(#get_value_lines)* + } + }); + off += size_bytes; + } else { + assert_eq!(field_index, pack_descr.fields.len() - 1); + let mut get_value_lines = vec![quote! { &self.0[#off..] }]; + + if let Some(ref out_ty) = f.map.map_type { + let get_raw = &get_value_lines[0]; + let new_line = quote! { let val = #get_raw ; }; + get_value_lines[0] = new_line; + let from_fn = &out_ty.from_fn; + get_value_lines.push(quote! { + #from_fn(val) + }); + + if f.map.convert_may_fail { + let is_valid_fn = &out_ty.is_valid_fn; + field_validators.push(quote! { + let val = &payload[#off..]; + if !#is_valid_fn(val) { + return Err(ParserError::InvalidField{ + packet: #pack_name, + field: stringify!(#get_name) + }); + } + }); + } + } + let out_ty = if f.has_intermediate_type() { + ty.clone() + } else { + parse_quote! { &[u8] } + }; + getters.push(quote! { + #[doc = #field_comment] + #[inline] + pub fn #get_name(&self) -> #out_ty { + #(#get_value_lines)* + } + }); + } + } + let struct_comment = &pack_descr.comment; + let validator = if let Some(payload_len) = pack_descr.packet_payload_size() { + quote! { + fn validate(payload: &[u8]) -> Result<(), ParserError> { + let expect = #payload_len; + let got = payload.len(); + if got == expect { + #(#field_validators)* + Ok(()) + } else { + Err(ParserError::InvalidPacketLen{ packet: #pack_name, expect, got }) + } + } + } + } else { + let min_size = pack_descr + .packet_payload_size_except_last_field() + .expect("except last all fields should have fixed size"); + + quote! { + fn validate(payload: &[u8]) -> Result<(), ParserError> { + let min = #min_size; + let got = payload.len(); + if got >= min { + #(#field_validators)* + Ok(()) + } else { + Err(ParserError::InvalidPacketLen{ packet: #pack_name, expect: min, got }) + } + } + } + }; + + quote! { + #[doc = #struct_comment] + #[doc = "It is just reference to internal parser's buffer"] + pub struct #ref_name<'a>(&'a [u8]); + impl<'a> #ref_name<'a> { + #(#getters)* + + #validator + } + } +} + +pub fn generate_types_for_packet(pack_descr: &PackDesc) -> TokenStream { + let name = Ident::new(&pack_descr.name, Span::call_site()); + let class = pack_descr.header.class; + let id = pack_descr.header.id; + let fixed_payload_len = match pack_descr.header.payload_len.fixed() { + Some(x) => quote! { Some(#x) }, + None => quote! { None }, + }; + let struct_comment = &pack_descr.comment; + let max_payload_len = match pack_descr.header.payload_len { + PayloadLen::Fixed(x) => x, + PayloadLen::Max(x) => x, + }; + quote! { + + #[doc = #struct_comment] + pub struct #name; + impl UbxPacketMeta for #name { + const CLASS: u8 = #class; + const ID: u8 = #id; + const FIXED_PAYLOAD_LEN: Option = #fixed_payload_len; + const MAX_PAYLOAD_LEN: u16 = #max_payload_len; + } + } +} + +pub fn generate_send_code_for_packet(pack_descr: &PackDesc) -> TokenStream { + let main_name = Ident::new(&pack_descr.name, Span::call_site()); + let payload_struct = format_ident!("{}Builder", pack_descr.name); + + let mut fields = Vec::with_capacity(pack_descr.fields.len()); + let mut pack_fields = Vec::with_capacity(pack_descr.fields.len()); + let mut write_fields = Vec::with_capacity(pack_descr.fields.len()); + let mut off = 6usize; + for f in &pack_descr.fields { + let ty = f.intermediate_type(); + let name = f.intermediate_field_name(); + let field_comment = &f.comment; + fields.push(quote! { + #[doc = #field_comment] + pub #name: #ty + }); + let size_bytes = match f.size_bytes { + Some(x) => x, + None => unimplemented!(), + }; + if f.has_intermediate_type() { + pack_fields.push(quote! { + let bytes = self.#name.into_raw().to_le_bytes() + }); + } else if !f.is_field_raw_ty_byte_array() { + pack_fields.push(quote! { + let bytes = self.#name.to_le_bytes() + }); + } else { + pack_fields.push(quote! { + let bytes: &[u8] = &self.#name; + }); + } + + write_fields.push(pack_fields.last().unwrap().clone()); + write_fields.push(quote! { + out.write(&bytes)?; + checksum_calc.update(&bytes) + }); + for i in 0..size_bytes.get() { + let byte_off = off.checked_add(i).unwrap(); + pack_fields.push(quote! { + ret[#byte_off] = bytes[#i] + }); + } + + off += size_bytes.get(); + } + let builder_attr = if pack_descr + .header + .flags + .iter() + .any(|x| *x == PacketFlag::DefaultForBuilder) + { + quote! { #[derive(Default)] } + } else { + quote! {} + }; + let struct_comment = &pack_descr.comment; + let mut ret = quote! { + #[doc = #struct_comment] + #[doc = "Struct that is used as \"builder\" for packet"] + #builder_attr + pub struct #payload_struct { + #(#fields),* + } + }; + + if let Some(packet_payload_size) = pack_descr.packet_payload_size() { + let packet_size = packet_payload_size + 8; + let packet_payload_size_u16 = u16::try_from(packet_payload_size).unwrap(); + ret.extend(quote! { + impl #payload_struct { + pub const PACKET_LEN: usize = #packet_size; + + #[inline] + pub fn into_packet_bytes(self) -> [u8; Self::PACKET_LEN] { + let mut ret = [0u8; Self::PACKET_LEN]; + ret[0] = SYNC_CHAR_1; + ret[1] = SYNC_CHAR_2; + ret[2] = #main_name::CLASS; + ret[3] = #main_name::ID; + let pack_len_bytes = #packet_payload_size_u16 .to_le_bytes(); + ret[4] = pack_len_bytes[0]; + ret[5] = pack_len_bytes[1]; + #(#pack_fields);*; + let (ck_a, ck_b) = ubx_checksum(&ret[2..#packet_size-2]); + ret[#packet_size-2] = ck_a; + ret[#packet_size-1] = ck_b; + ret + } + } + impl From<#payload_struct> for [u8; #packet_size] { + fn from(x: #payload_struct) -> Self { + x.into_packet_bytes() + } + } + + impl UbxPacketCreator for #payload_struct { + #[inline] + fn create_packet(self, out: &mut T) -> Result<(), MemWriterError> { + out.reserve_allocate(#packet_size)?; + let len_bytes = #packet_payload_size_u16 .to_le_bytes(); + let header = [SYNC_CHAR_1, SYNC_CHAR_2, #main_name::CLASS, #main_name::ID, len_bytes[0], len_bytes[1]]; + out.write(&header)?; + let mut checksum_calc = UbxChecksumCalc::default(); + checksum_calc.update(&header[2..]); + #(#write_fields);*; + let (ck_a, ck_b) = checksum_calc.result(); + out.write(&[ck_a, ck_b])?; + Ok(()) + } + } + }); + } else { + unimplemented!(); + } + + ret +} + +pub fn generate_code_to_extend_enum(ubx_enum: &UbxExtendEnum) -> TokenStream { + assert_eq!(ubx_enum.repr, { + let ty: Type = parse_quote! { u8 }; + ty + }); + let name = &ubx_enum.name; + let mut variants = ubx_enum.variants.clone(); + let attrs = &ubx_enum.attrs; + if let Some(UbxEnumRestHandling::Reserved) = ubx_enum.rest_handling { + let defined: HashSet = ubx_enum.variants.iter().map(|x| x.1).collect(); + for i in 0..=u8::max_value() { + if !defined.contains(&i) { + let name = format_ident!("Reserved{}", i); + variants.push((name, i)); + } + } + } + let repr_ty = &ubx_enum.repr; + let from_code = match ubx_enum.from_fn { + Some(UbxTypeFromFn::From) => { + assert_ne!( + Some(UbxEnumRestHandling::ErrorProne), + ubx_enum.rest_handling + ); + let mut match_branches = Vec::with_capacity(variants.len()); + for (id, val) in &variants { + match_branches.push(quote! { #val => #name :: #id }); + } + + quote! { + impl #name { + fn from(x: #repr_ty) -> Self { + match x { + #(#match_branches),* + } + } + } + } + } + Some(UbxTypeFromFn::FromUnchecked) => { + assert_ne!(Some(UbxEnumRestHandling::Reserved), ubx_enum.rest_handling); + let mut match_branches = Vec::with_capacity(variants.len()); + for (id, val) in &variants { + match_branches.push(quote! { #val => #name :: #id }); + } + + let mut values = Vec::with_capacity(variants.len()); + for (i, (_, val)) in variants.iter().enumerate() { + if i != 0 { + values.push(quote! { | #val }); + } else { + values.push(quote! { #val }); + } + } + + quote! { + impl #name { + fn from_unchecked(x: #repr_ty) -> Self { + match x { + #(#match_branches),*, + _ => unreachable!(), + } + } + fn is_valid(x: #repr_ty) -> bool { + match x { + #(#values)* => true, + _ => false, + } + } + } + } + } + None => quote! {}, + }; + + let to_code = match ubx_enum.into_fn { + None => quote! {}, + Some(UbxTypeIntoFn::Raw) => quote! { + impl #name { + const fn into_raw(self) -> #repr_ty { + self as #repr_ty + } + } + }, + }; + + let mut enum_variants = Vec::with_capacity(variants.len()); + for (id, val) in &variants { + enum_variants.push(quote! { #id = #val }); + } + + let code = quote! { + #(#attrs)* + pub enum #name { + #(#enum_variants),* + } + + #from_code + #to_code + }; + code +} + +pub fn generate_code_to_extend_bitflags(bitflags: BitFlagsMacro) -> syn::Result { + match bitflags.rest_handling { + Some(UbxEnumRestHandling::ErrorProne) | None => { + return Err(syn::Error::new( + bitflags.name.span(), + "Only reserved supported", + )) + } + Some(UbxEnumRestHandling::Reserved) => (), + } + + let mut known_flags = HashSet::new(); + let mut items = Vec::with_capacity(usize::try_from(bitflags.nbits).unwrap()); + let repr_ty = &bitflags.repr_ty; + + for bit in 0..bitflags.nbits { + let flag_bit = 1u64.checked_shl(bit).unwrap(); + if let Some(item) = bitflags.consts.iter().find(|x| x.value == flag_bit) { + known_flags.insert(flag_bit); + let name = &item.name; + let attrs = &item.attrs; + if bit != 0 { + items.push(quote! { + #(#attrs)* + const #name = ((1 as #repr_ty) << #bit) + }); + } else { + items.push(quote! { + #(#attrs)* + const #name = (1 as #repr_ty) + }); + } + } else { + let name = format_ident!("RESERVED{}", bit); + if bit != 0 { + items.push(quote! { const #name = ((1 as #repr_ty) << #bit)}); + } else { + items.push(quote! { const #name = (1 as #repr_ty) }); + } + } + } + + if known_flags.len() != bitflags.consts.len() { + let user_flags: HashSet<_> = bitflags.consts.iter().map(|x| x.value).collect(); + let set = user_flags.difference(&known_flags); + return Err(syn::Error::new( + bitflags.name.span(), + format!("Strange flags, not power of 2?: {:?}", set), + )); + } + + let vis = &bitflags.vis; + let attrs = &bitflags.attrs; + let name = &bitflags.name; + + let from = match bitflags.from_fn { + None => quote! {}, + Some(UbxTypeFromFn::From) => quote! { + impl #name { + const fn from(x: #repr_ty) -> Self { + Self::from_bits_truncate(x) + } + } + }, + Some(UbxTypeFromFn::FromUnchecked) => unimplemented!(), + }; + + let into = match bitflags.into_fn { + None => quote! {}, + Some(UbxTypeIntoFn::Raw) => quote! { + impl #name { + const fn into_raw(self) -> #repr_ty { + self.bits() + } + } + }, + }; + + Ok(quote! { + bitflags! { + #(#attrs)* + #vis struct #name : #repr_ty { + #(#items);*; + } + } + #from + #into + }) +} + +pub fn generate_code_for_parse(recv_packs: &RecvPackets) -> TokenStream { + let mut pack_enum_variants = Vec::with_capacity(recv_packs.all_packets.len()); + let mut matches = Vec::with_capacity(recv_packs.all_packets.len()); + + for name in &recv_packs.all_packets { + let ref_name = format_ident!("{}Ref", name); + pack_enum_variants.push(quote! { + #name(#ref_name <'a>) + }); + + matches.push(quote! { + (#name::CLASS, #name::ID) if <#ref_name>::validate(payload).is_ok() => { + Ok(PacketRef::#name(#ref_name(payload))) + } + }); + } + + let union_enum_name = &recv_packs.union_enum_name; + let unknown_var = &recv_packs.unknown_ty; + + let max_payload_len_calc = recv_packs + .all_packets + .iter() + .fold(quote! { 0u16 }, |prev, name| { + quote! { max_u16(#name::MAX_PAYLOAD_LEN, #prev) } + }); + + quote! { + #[doc = "All possible packets enum"] + pub enum #union_enum_name<'a> { + #(#pack_enum_variants),*, + Unknown(#unknown_var<'a>) + } + + pub(crate) fn match_packet(class: u8, msg_id: u8, payload: &[u8]) -> Result { + match (class, msg_id) { + #(#matches)* + _ => Ok(PacketRef::Unknown(#unknown_var { + payload, + class, + msg_id + })), + } + } + + const fn max_u16(a: u16, b: u16) -> u16 { + [a, b][(a < b) as usize] + } + pub(crate) const MAX_PAYLOAD_LEN: u16 = #max_payload_len_calc; + } +} + +fn get_raw_field_code(field: &PackField, cur_off: usize, data: TokenStream) -> TokenStream { + let size_bytes = match field.size_bytes { + Some(x) => x, + None => unimplemented!(), + }; + + let mut bytes = Vec::with_capacity(size_bytes.get()); + for i in 0..size_bytes.get() { + let byte_off = cur_off.checked_add(i).unwrap(); + bytes.push(quote! { #data[#byte_off] }); + } + let raw_ty = &field.ty; + + let signed_byte: Type = parse_quote! { i8 }; + + if field.is_field_raw_ty_byte_array() { + quote! { [#(#bytes),*] } + } else if size_bytes.get() != 1 || *raw_ty == signed_byte { + quote! { <#raw_ty>::from_le_bytes([#(#bytes),*]) } + } else { + quote! { #data[#cur_off] } + } +} diff --git a/ublox_derive/src/tests.rs b/ublox_derive/src/tests.rs new file mode 100644 index 0000000..f9f5d05 --- /dev/null +++ b/ublox_derive/src/tests.rs @@ -0,0 +1,587 @@ +use super::*; +use quote::quote; +use std::{ + io::{self, Write}, + process::{Command, Stdio}, + sync::Arc, +}; +use syn::Error; +use which::which; + +#[test] +fn test_ubx_packet_recv_simple() { + let src_code = quote! { + #[ubx_packet_recv] + #[ubx(class = 1, id = 2, fixed_payload_len = 16)] + #[doc = "Some comment"] + struct Test { + itow: u32, + #[doc = "this is lat"] + #[ubx(map_type = f64, scale = 1e-7, alias = lat_degrees)] + lat: i32, + #[doc = "this is a"] + a: u8, + reserved1: [u8; 5], + #[ubx(map_type = Flags, may_fail)] + flags: u8, + b: i8, + } + }; + let src_code = src_code.to_string(); + + let code: syn::ItemStruct = syn::parse_str(&src_code) + .unwrap_or_else(|err| panic_on_parse_error("test_ubx_packet_recv", &src_code, &err)); + let tokens = generate_code_for_recv_packet(code.ident, code.attrs, code.fields) + .unwrap_or_else(|err| panic_on_parse_error("test_ubx_packet_recv", &src_code, &err)); + + run_compare_test( + tokens, + quote! { + #[doc = "Some comment"] + pub struct Test; + + impl UbxPacketMeta for Test { + const CLASS: u8 = 1u8; + const ID: u8 = 2u8; + const FIXED_PAYLOAD_LEN: Option = Some(16u16); + const MAX_PAYLOAD_LEN: u16 = 16u16; + } + + #[doc = "Some comment"] + #[doc = "It is just reference to internal parser's buffer"] + pub struct TestRef<'a>(&'a [u8]); + impl<'a> TestRef<'a> { + #[doc = ""] + #[inline] + pub fn itow(&self) -> u32 { + ::from_le_bytes([ + self.0[0usize], + self.0[1usize], + self.0[2usize], + self.0[3usize]] + ) + } + #[doc = "this is lat"] + #[inline] + pub fn lat_degrees(&self) -> f64 { + let val = ::from_le_bytes([ + self.0[4usize], + self.0[5usize], + self.0[6usize], + self.0[7usize]] + ); + let val = ::from(val); + val * 1e-7 + } + #[doc = "this is a"] + #[inline] + pub fn a(&self) -> u8 { + self.0[8usize] + } + #[doc = ""] + #[inline] + pub fn reserved1(&self) -> [u8; 5] { + [ + self.0[9usize], + self.0[10usize], + self.0[11usize], + self.0[12usize], + self.0[13usize], + ] + } + #[doc = ""] + #[inline] + pub fn flags(&self) -> Flags { + let val = self.0[14usize]; + ::from_unchecked(val) + } + #[doc = ""] + #[inline] + pub fn b(&self) -> i8 { + ::from_le_bytes([self.0[15usize]]) + } + + fn validate(payload: &[u8]) -> Result<(), ParserError> { + let expect = 16usize; + let got = payload.len(); + if got == expect { + let val = payload[14usize]; + if !::is_valid(val) { + return Err(ParserError::InvalidField{packet: "Test", field: stringify!(flags)}); + } + Ok(()) + } else { + Err(ParserError::InvalidPacketLen{packet: "Test", expect, got}) + } + } + } + }, + ); +} + +#[test] +fn test_ubx_packet_recv_dyn_len() { + let src_code = quote! { + #[ubx_packet_recv] + #[ubx(class = 1, id = 2, max_payload_len = 38)] + struct Test { + #[ubx(map_type = &str, get_as_ref, from = unpack_str)] + f1: [u8; 8], + rest: [u8; 0], + } + }; + let src_code = src_code.to_string(); + + let code: syn::ItemStruct = syn::parse_str(&src_code).unwrap_or_else(|err| { + panic_on_parse_error("test_ubx_packet_recv_dyn_len", &src_code, &err) + }); + let tokens = + generate_code_for_recv_packet(code.ident, code.attrs, code.fields).unwrap_or_else(|err| { + panic_on_parse_error("test_ubx_packet_recv_dyn_len", &src_code, &err) + }); + + run_compare_test( + tokens, + quote! { + #[doc = ""] + pub struct Test; + + impl UbxPacketMeta for Test { + const CLASS: u8 = 1u8; + const ID: u8 = 2u8; + const FIXED_PAYLOAD_LEN: Option = None; + const MAX_PAYLOAD_LEN: u16 = 38u16; + } + + #[doc = ""] + #[doc = "It is just reference to internal parser's buffer"] + pub struct TestRef<'a>(&'a [u8]); + impl<'a> TestRef<'a> { + #[doc = ""] + #[inline] + pub fn f1(&self) -> &str { + let val = &self.0[0usize..(0usize + 8usize)]; + unpack_str(val) + } + + #[doc = ""] + #[inline] + pub fn rest(&self) -> &[u8] { + &self.0[8usize..] + } + + fn validate(payload: &[u8]) -> Result<(), ParserError> { + let min = 8usize; + let got = payload.len(); + if got >= min { + Ok(()) + } else { + Err(ParserError::InvalidPacketLen{packet: "Test", expect: min, got}) + } + } + } + }, + ); +} + +#[test] +fn test_ubx_packet_send() { + let src_code = quote! { + #[ubx_packet_send] + #[ubx(class = 1, id = 2, fixed_payload_len = 9, flags = "default_for_builder")] + #[doc = "Some comment"] + struct Test { + itow: u32, + #[doc = "this is lat"] + #[ubx(map_type = f64, scale = 1e-7, alias = lat_degrees)] + lat: i32, + #[doc = "this is a"] + a: u8, + } + }; + let src_code = src_code.to_string(); + + let code: syn::ItemStruct = syn::parse_str(&src_code) + .unwrap_or_else(|err| panic_on_parse_error("test_ubx_packet_send", &src_code, &err)); + let tokens = generate_code_for_send_packet(code.ident, code.attrs, code.fields) + .unwrap_or_else(|err| panic_on_parse_error("test_ubx_packet_send", &src_code, &err)); + + run_compare_test( + tokens, + quote! { + #[doc = "Some comment"] + pub struct Test; + + impl UbxPacketMeta for Test { + const CLASS: u8 = 1u8; + const ID: u8 = 2u8; + const FIXED_PAYLOAD_LEN: Option = Some(9u16); + const MAX_PAYLOAD_LEN: u16 = 9u16; + } + + #[doc = "Some comment"] + #[doc = "Struct that is used as \"builder\" for packet"] + #[derive(Default)] + pub struct TestBuilder { + #[doc = ""] + pub itow: u32, + #[doc = "this is lat"] + pub lat_degrees: f64, + #[doc = "this is a"] + pub a: u8, + } + impl TestBuilder { + pub const PACKET_LEN: usize = 17usize; + + #[inline] + pub fn into_packet_bytes(self) -> [u8; Self::PACKET_LEN] { + let mut ret = [0u8; Self::PACKET_LEN]; + ret[0] = SYNC_CHAR_1; + ret[1] = SYNC_CHAR_2; + ret[2] = Test::CLASS; + ret[3] = Test::ID; + let pack_len_bytes = 9u16.to_le_bytes(); + ret[4] = pack_len_bytes[0]; + ret[5] = pack_len_bytes[1]; + let bytes = self.itow.to_le_bytes(); + ret[6usize] = bytes[0usize]; + ret[7usize] = bytes[1usize]; + ret[8usize] = bytes[2usize]; + ret[9usize] = bytes[3usize]; + let bytes = self.lat_degrees.into_raw().to_le_bytes(); + ret[10usize] = bytes[0usize]; + ret[11usize] = bytes[1usize]; + ret[12usize] = bytes[2usize]; + ret[13usize] = bytes[3usize]; + let bytes = self.a.to_le_bytes(); + ret[14usize] = bytes[0usize]; + let (ck_a, ck_b) = ubx_checksum(&ret[2..17usize - 2]); + ret[17usize - 2] = ck_a; + ret[17usize - 1] = ck_b; + ret + } + } + impl From for [u8; 17usize] { + fn from(x: TestBuilder) -> Self { + x.into_packet_bytes() + } + } + impl UbxPacketCreator for TestBuilder { + #[inline] + fn create_packet(self, out: &mut T) -> Result<(), MemWriterError> { + out.reserve_allocate(17usize)?; + let len_bytes = 9u16.to_le_bytes(); + let header = [ + SYNC_CHAR_1, + SYNC_CHAR_2, + Test::CLASS, + Test::ID, + len_bytes[0], + len_bytes[1], + ]; + out.write(&header)?; + let mut checksum_calc = UbxChecksumCalc::default(); + checksum_calc.update(&header[2..]); + let bytes = self.itow.to_le_bytes(); + out.write(&bytes)?; + checksum_calc.update(&bytes); + let bytes = self.lat_degrees.into_raw().to_le_bytes(); + out.write(&bytes)?; + checksum_calc.update(&bytes); + let bytes = self.a.to_le_bytes(); + out.write(&bytes)?; + checksum_calc.update(&bytes); + let (ck_a, ck_b) = checksum_calc.result(); + out.write(&[ck_a, ck_b])?; + Ok(()) + } + } + }, + ); +} + +#[test] +fn test_upgrade_enum() { + let src_code = quote! { + #[doc = "GPS fix Type"] + #[ubx_extend] + #[ubx(from, rest_reserved)] + #[repr(u8)] + #[derive(Debug, Copy, Clone)] + enum GpsFix { + NoFix = 0, + DeadReckoningOnly = 1, + Fix2D = 2, + Fix3D = 3, + GPSPlusDeadReckoning = 4, + TimeOnlyFix = 5, + } + }; + let src_code = src_code.to_string(); + + let code: syn::ItemEnum = syn::parse_str(&src_code) + .unwrap_or_else(|err| panic_on_parse_error("test_upgrade_enum", &src_code, &err)); + let tokens = extend_enum(code.ident, code.attrs, code.variants) + .unwrap_or_else(|err| panic_on_parse_error("test_upgrade_enum", &src_code, &err)); + + let mut reserved_fields = Vec::with_capacity(256); + let mut rev_reserved_fields = Vec::with_capacity(256); + for i in 6..=255 { + let val = i as u8; + let ident = quote::format_ident!("Reserved{}", val); + reserved_fields.push(quote! { #ident = #val }); + rev_reserved_fields.push(quote! { #val => GpsFix::#ident }); + } + + run_compare_test( + tokens, + quote! { + #[doc = "GPS fix Type"] + #[repr(u8)] + #[derive(Debug, Copy, Clone)] + pub enum GpsFix { + NoFix = 0u8, + DeadReckoningOnly = 1u8, + Fix2D = 2u8, + Fix3D = 3u8, + GPSPlusDeadReckoning = 4u8, + TimeOnlyFix = 5u8, + #(#reserved_fields),* + } + impl GpsFix { + fn from(x: u8) -> Self { + match x { + 0u8 => GpsFix::NoFix, + 1u8 => GpsFix::DeadReckoningOnly, + 2u8 => GpsFix::Fix2D, + 3u8 => GpsFix::Fix3D, + 4u8 => GpsFix::GPSPlusDeadReckoning, + 5u8 => GpsFix::TimeOnlyFix, + #(#rev_reserved_fields),* + } + } + } + }, + ); +} + +#[test] +fn test_define_recv_packets() { + let src_code = quote! { + enum PacketRef { + _ = UnknownPacketRef, + Pack1, + Pack2 + } + }; + let src_code = src_code.to_string(); + let tokens: TokenStream = syn::parse_str(&src_code) + .unwrap_or_else(|err| panic_on_parse_error("test_define_recv_packets", &src_code, &err)); + let output = do_define_recv_packets(tokens) + .unwrap_or_else(|err| panic_on_parse_error("test_define_recv_packets", &src_code, &err)); + run_compare_test( + output, + quote! { + #[doc = "All possible packets enum"] + pub enum PacketRef<'a> { + Pack1(Pack1Ref<'a>), + Pack2(Pack2Ref<'a>), + Unknown(UnknownPacketRef<'a>) + } + + pub(crate) fn match_packet( + class: u8, + msg_id: u8, + payload: &[u8], + ) -> Result { + match (class, msg_id) { + (Pack1::CLASS, Pack1::ID) if ::validate(payload).is_ok() => { + Ok(PacketRef::Pack1(Pack1Ref(payload))) + } + (Pack2::CLASS, Pack2::ID) if ::validate(payload).is_ok() => { + Ok(PacketRef::Pack2(Pack2Ref(payload))) + } + _ => Ok(PacketRef::Unknown(UnknownPacketRef { + payload, + class, + msg_id, + })), + } + } + + const fn max_u16(a: u16, b: u16) -> u16 { + [a, b][(a < b) as usize] + } + pub(crate) const MAX_PAYLOAD_LEN: u16 = + max_u16(Pack2::MAX_PAYLOAD_LEN, max_u16(Pack1::MAX_PAYLOAD_LEN, 0u16)); + }, + ); +} + +#[test] +fn test_extend_bitflags() { + let src_code = quote! { + #[ubx_extend_bitflags] + #[ubx(from, rest_reserved)] + bitflags! { + #[doc = "Navigation Status Flags"] + pub struct Test: u8 { + #[doc = "position and velocity valid and within DOP and ACC Masks"] + const F1 = 1; + #[doc = "DGPS used"] + const F2 = 2; + #[doc = "Week Number valid"] + const F3 = 4; + #[doc = "Time of Week valid"] + const F4 = 8; + } + } + }; + let src_code = src_code.to_string(); + + let mac: syn::ItemMacro = syn::parse_str(&src_code) + .unwrap_or_else(|err| panic_on_parse_error("test_extend_bitflags", &src_code, &err)); + let tokens = extend_bitflags(mac) + .unwrap_or_else(|err| panic_on_parse_error("test_extend_bitflags", &src_code, &err)); + run_compare_test( + tokens, + quote! { + bitflags! { + #[doc = "Navigation Status Flags"] + pub struct Test: u8 { + #[doc = "position and velocity valid and within DOP and ACC Masks"] + const F1 = (1 as u8); + #[doc = "DGPS used"] + const F2 = ((1 as u8) << 1u32); + #[doc = "Week Number valid"] + const F3 = ((1 as u8) << 2u32); + #[doc = "Time of Week valid"] + const F4 = ((1 as u8) << 3u32); + const RESERVED4 = ((1 as u8) << 4u32); + const RESERVED5 = ((1 as u8) << 5u32); + const RESERVED6 = ((1 as u8) << 6u32); + const RESERVED7 = ((1 as u8) << 7u32); + } + } + impl Test { + const fn from(x: u8) -> Self { + Self::from_bits_truncate(x) + } + } + }, + ); +} + +fn run_compare_test(output: TokenStream, expect_output: TokenStream) { + let output = output.to_string(); + let output = String::from_utf8(rustfmt_cnt(output.into_bytes()).unwrap()).unwrap(); + let expect_output = expect_output.to_string(); + let expect_output = + String::from_utf8(rustfmt_cnt(expect_output.into_bytes()).unwrap()).unwrap(); + + if expect_output != output { + for (e, g) in expect_output.lines().zip(output.lines()) { + if e != g { + println!("first mismatch:\ne {}\ng {}", e, g); + break; + } + } + panic!("Expect:\n{}\nGot:\n{}\n", expect_output, output); + } +} + +fn rustfmt_cnt(source: Vec) -> io::Result> { + let rustfmt = + which("rustfmt").map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))?; + + let mut cmd = Command::new(&*rustfmt); + + cmd.stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()); + + let mut child = cmd.spawn()?; + let mut child_stdin = child.stdin.take().unwrap(); + let mut child_stdout = child.stdout.take().unwrap(); + let src_len = source.len(); + let src = Arc::new(source); + // Write to stdin in a new thread, so that we can read from stdout on this + // thread. This keeps the child from blocking on writing to its stdout which + // might block us from writing to its stdin. + let stdin_handle = ::std::thread::spawn(move || { + let _ = child_stdin.write_all(src.as_slice()); + src + }); + + let mut output = Vec::with_capacity(src_len); + io::copy(&mut child_stdout, &mut output)?; + let status = child.wait()?; + let src = stdin_handle.join().expect( + "The thread writing to rustfmt's stdin doesn't do \ + anything that could panic", + ); + let src = + Arc::try_unwrap(src).expect("Internal error: rusftfmt_cnt should only one Arc refernce"); + match status.code() { + Some(0) => Ok(output), + Some(2) => Err(io::Error::new( + io::ErrorKind::Other, + "Rustfmt parsing errors.".to_string(), + )), + Some(3) => { + println!("warning=Rustfmt could not format some lines."); + Ok(src) + } + _ => { + println!("warning=Internal rustfmt error"); + Ok(src) + } + } +} + +fn panic_on_parse_error(name: &str, src_cnt: &str, err: &Error) -> ! { + use std::fmt::Write; + + let span = err.span(); + let start = span.start(); + let end = span.end(); + + let mut code_problem = String::new(); + let nlines = end.line - start.line + 1; + for (i, line) in src_cnt + .lines() + .skip(start.line - 1) + .take(nlines) + .enumerate() + { + code_problem.push_str(&line); + code_problem.push('\n'); + if i == 0 && start.column > 0 { + write!(&mut code_problem, "{:1$}", ' ', start.column).expect("write to String failed"); + } + let code_problem_len = if i == 0 { + if i == nlines - 1 { + end.column - start.column + } else { + line.len() - start.column - 1 + } + } else if i != nlines - 1 { + line.len() + } else { + end.column + }; + writeln!(&mut code_problem, "{:^^1$}", '^', code_problem_len).expect("Not enought memory"); + if i == end.line { + break; + } + } + + panic!( + "parsing of {name} failed\nerror: {err}\n{code_problem}\nAt {name}:{line_s}:{col_s}", + name = name, + err = err, + code_problem = code_problem, + line_s = start.line, + col_s = start.column, + ); +} diff --git a/ublox_derive/src/types.rs b/ublox_derive/src/types.rs new file mode 100644 index 0000000..609c9fb --- /dev/null +++ b/ublox_derive/src/types.rs @@ -0,0 +1,188 @@ +use proc_macro2::TokenStream; +use quote::quote; +use std::num::NonZeroUsize; +use syn::{Attribute, Ident, Type}; + +pub struct PackDesc { + pub name: String, + pub header: PackHeader, + pub comment: String, + pub fields: Vec, +} + +impl PackDesc { + /// if packet has variable size, then `None` + pub fn packet_payload_size(&self) -> Option { + PackDesc::fields_size(self.fields.iter()) + } + + pub fn packet_payload_size_except_last_field(&self) -> Option { + PackDesc::fields_size(self.fields.iter().rev().skip(1)) + } + + fn fields_size<'a, I: Iterator>(iter: I) -> Option { + let mut ret: usize = 0; + for f in iter { + let size = f.size_bytes?; + ret = ret + .checked_add(size.get()) + .expect("overflow during packet size calculation"); + } + Some(ret) + } +} + +pub struct PackHeader { + pub class: u8, + pub id: u8, + pub payload_len: PayloadLen, + pub flags: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub enum PayloadLen { + Fixed(u16), + Max(u16), +} + +impl PayloadLen { + pub fn fixed(&self) -> Option { + if let PayloadLen::Fixed(len) = self { + Some(*len) + } else { + None + } + } +} + +pub struct PackField { + pub name: Ident, + pub ty: Type, + pub map: PackFieldMap, + pub comment: String, + pub size_bytes: Option, +} + +impl PackField { + pub fn has_intermediate_type(&self) -> bool { + self.map.map_type.is_some() + } + pub fn intermediate_type(&self) -> &Type { + self.map + .map_type + .as_ref() + .map(|x| &x.ty) + .unwrap_or(&self.ty) + } + pub fn intermediate_field_name(&self) -> &Ident { + self.map.alias.as_ref().unwrap_or(&self.name) + } + pub fn is_field_raw_ty_byte_array(&self) -> bool { + if let syn::Type::Array(ref fixed_array) = self.ty { + *fixed_array.elem == syn::parse_quote!(u8) + } else { + false + } + } +} + +pub struct PackFieldMap { + pub map_type: Option, + pub scale: Option, + pub alias: Option, + pub convert_may_fail: bool, + pub get_as_ref: bool, +} + +impl PackFieldMap { + pub fn is_none(&self) -> bool { + self.map_type.is_none() && self.scale.is_none() && self.alias.is_none() + } + pub fn none() -> Self { + Self { + map_type: None, + scale: None, + alias: None, + convert_may_fail: false, + get_as_ref: false, + } + } +} + +pub struct MapType { + pub ty: Type, + pub from_fn: TokenStream, + pub is_valid_fn: TokenStream, +} + +impl MapType { + pub fn new(ty: Type, convert_may_fail: bool) -> Self { + let from_fn = if !convert_may_fail { + quote! { <#ty>::from } + } else { + quote! { <#ty>::from_unchecked } + }; + let is_valid_fn = quote! { <#ty>::is_valid }; + Self { + ty, + from_fn, + is_valid_fn, + } + } +} + +pub struct UbxExtendEnum { + pub name: Ident, + pub repr: Type, + pub from_fn: Option, + pub rest_handling: Option, + pub into_fn: Option, + pub variants: Vec<(Ident, u8)>, + pub attrs: Vec, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum UbxTypeFromFn { + From, + FromUnchecked, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum UbxTypeIntoFn { + Raw, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum UbxEnumRestHandling { + Reserved, + ErrorProne, +} + +pub struct BitFlagsMacro { + pub nbits: u32, + pub vis: syn::Visibility, + pub attrs: Vec, + pub name: Ident, + pub repr_ty: Type, + pub consts: Vec, + pub from_fn: Option, + pub into_fn: Option, + pub rest_handling: Option, +} + +pub struct BitFlagsMacroItem { + pub attrs: Vec, + pub name: Ident, + pub value: u64, +} + +#[derive(PartialEq, Clone, Copy)] +pub enum PacketFlag { + DefaultForBuilder, +} + +pub struct RecvPackets { + pub union_enum_name: Ident, + pub unknown_ty: Ident, + pub all_packets: Vec, +} diff --git a/ublox_derive/tests/test.rs b/ublox_derive/tests/test.rs deleted file mode 100644 index b4dc3dc..0000000 --- a/ublox_derive/tests/test.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::convert::TryInto; -use ublox_derive::ubx_packet; - -#[ubx_packet] -struct TestPacket { - field1: u32, - field2: u32, - field3: u8, - field4: u16, - field5: i32, -} - -#[test] -fn foo() { - assert_eq!(std::mem::size_of::(), 15); -} - -#[ubx_packet] -struct TestPacket2 { - field1: u32, - field2: u8, - field3: u32, -} - -fn helper(packet: &TestPacket2) -> u32 { - packet.get_field3() -} - -#[test] -fn foo2() { - let data = [1, 0, 0, 0, 0, 2, 0, 0, 0]; - let packet = TestPacket2::new(data); - assert_eq!(helper(&packet), 2); - assert_eq!(packet.get_field2(), 0); -}