Skip to content

Commit

Permalink
feat: gprs codec 12 tx and rx
Browse files Browse the repository at this point in the history
  • Loading branch information
DamianoPellegrini committed Nov 25, 2024
1 parent 02d6fae commit 131fca5
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 93 deletions.
13 changes: 13 additions & 0 deletions .zed/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"lsp": {
"rust-analyzer": {
"cargo": {
"features": "all"
}
}
}
}
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ This package makes use of the [nom crate](https://docs.rs/nom) to parse the bina

## Capabilities

It parses Codec 8, 8-Extended and 16 (aka TCP/UDP Protocol).

It **DOES NOT** currently parse Codec 12, 13 and 14 (aka GPRS Protocol), it **MAY** does so in the future.

It fails parsing if any of the following checks fail:

- Preamble **MUST BE** 0x00000000
- CRCs **DOES NOT** match
- Record Counts **DOES NOT** match
- UDP Un-usable byte **MUST BE** 0x01
- Parsing:
- Codec 8, 8-Extended and 16 (aka TCP/UDP Protocol).
- Codec 12 command responses.
- It **DOES NOT** currently parse Codec 13 and 14, it **MAY** does so in the future.

- It fails parsing if any of the following checks fail:
- Preamble **MUST BE** 0x00000000
- CRCs **DOES NOT** match
- Record Counts **DOES NOT** match
- UDP Un-usable byte **MUST BE** 0x01
- Command response type byte **MUST BE** 0x06

- It allows for sending commands to a device using Codec 12 **ONLY**.

## Features

Expand Down
20 changes: 20 additions & 0 deletions examples/from_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::io::Cursor;

use nom_teltonika::TeltonikaStream;

fn main() {
// Write getinfo command to the device
let mut stream = TeltonikaStream::new(Cursor::new(Vec::new()));
stream.write_command("getinfo").expect("Write failed");
let mut buffer = stream.into_inner().into_inner();

// Compare with actual buffer that should be sent
let cmp = hex::decode("000000000000000F0C010500000007676574696E666F0100004312").unwrap();
assert_eq!(cmp, buffer);

// Read back as if it was a response
buffer = hex::decode("000000000000000F0C010600000007676574696E666F0100008017").unwrap();
stream = TeltonikaStream::new(Cursor::new(buffer));
let frame = stream.read_frame().unwrap().unwrap_gprs();
println!("{frame:#?}");
}
114 changes: 82 additions & 32 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use nom::{
character::streaming::anychar,
combinator::{cond, verify},
error::ParseError,
multi::{length_count, length_data},
multi::{count, length_count, length_data},
number::streaming::{be_i32, be_u16, be_u32, be_u64, be_u8},
IResult, Parser,
};
Expand Down Expand Up @@ -178,40 +178,67 @@ fn record<'a>(codec: Codec) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], AVLReco
}
}

/// # Deprecated
/// Use [`tcp_frame`] instead
#[deprecated(note = "Use tcp_frame instead")]
pub fn tcp_packet(input: &[u8]) -> IResult<&[u8], AVLFrame> {
tcp_frame(input)
/// Parse a single command response.
///
/// That means a 4 bytes length and X bytes characters.
fn command_response(input: &[u8]) -> IResult<&[u8], String> {
let (input, response) = length_count(be_u32, anychar)(input)?;
Ok((input, response.iter().collect()))
}

/// Parse a TCP teltonika frame
///
/// Either parse a GPRS Command response or an AVL Record response
///
/// It does 3 main error checks:
/// - Preamble is all zeroes
/// - Both record counts coincide
/// - Both counts coincide
/// - Computes CRC and verifies it against the one sent
pub fn tcp_frame(input: &[u8]) -> IResult<&[u8], AVLFrame> {
pub fn tcp_frame(input: &[u8]) -> IResult<&[u8], TeltonikaFrame> {
let (input, _preamble) = tag("\0\0\0\0")(input)?;

let (input, data) = length_data(be_u32)(input)?;

let calculated_crc16 = crate::crc16(data);
let (data, codec) = codec(data)?;
let (data, records) = length_count(be_u8, record(codec))(data)?;
let (_data, _records_count) = verify(be_u8, |number_of_records| {
*number_of_records as usize == records.len()
})(data)?;
let (input, crc16) = verify(be_u32, |crc16| *crc16 == calculated_crc16 as u32)(input)?;

Ok((
input,
AVLFrame {
codec,
records,
crc16,
},
))
Ok(match codec {
Codec::C8 | Codec::C8Ext | Codec::C16 => {
let (data, records) = length_count(be_u8, record(codec))(data)?;
let (_data, _records_count) = verify(be_u8, |number_of_records| {
*number_of_records as usize == records.len()
})(data)?;
let (input, crc16) = verify(be_u32, |crc16| *crc16 == calculated_crc16 as u32)(input)?;
(
input,
TeltonikaFrame::AVL(AVLFrame {
codec,
records,
crc16,
}),
)
}
Codec::C12 => {
let (data, response_qty) = be_u8(data)?;

// Type of a command response
let (data, _) = tag("\x06")(data)?;
let (data, responses) = count(command_response, response_qty as usize)(data)?;
let (_data, _response_qty) = verify(be_u8, |number_of_responses| {
*number_of_responses as usize == responses.len()
})(data)?;

let (input, crc16) = verify(be_u32, |crc16| *crc16 == calculated_crc16 as u32)(input)?;
(
input,
TeltonikaFrame::GPRS(GPRSFrame {
codec,
command_responses: responses,
crc16,
}),
)
}
_ => panic!("Codec not supported"),
})
}

/// Parse an UDP teltonika datagram
Expand Down Expand Up @@ -353,7 +380,7 @@ mod tests {
assert_eq!(input, &[]);
assert_eq!(
frame,
AVLFrame {
TeltonikaFrame::AVL(AVLFrame {
codec: Codec::C8,
records: vec![AVLRecord {
timestamp: "2019-06-10T10:04:46Z".parse().unwrap(),
Expand Down Expand Up @@ -390,7 +417,7 @@ mod tests {
],
},],
crc16: 51151,
}
})
);
}

Expand All @@ -401,7 +428,7 @@ mod tests {
assert_eq!(input, &[]);
assert_eq!(
frame,
AVLFrame {
TeltonikaFrame::AVL(AVLFrame {
codec: Codec::C8,
records: vec![AVLRecord {
timestamp: "2019-06-10T10:05:36Z".parse().unwrap(),
Expand Down Expand Up @@ -430,7 +457,7 @@ mod tests {
],
},],
crc16: 61994,
}
})
);
}

Expand All @@ -441,7 +468,7 @@ mod tests {
assert_eq!(input, &[]);
assert_eq!(
frame,
AVLFrame {
TeltonikaFrame::AVL(AVLFrame {
codec: Codec::C8,
records: vec![
AVLRecord {
Expand Down Expand Up @@ -478,7 +505,7 @@ mod tests {
},
],
crc16: 9516,
}
})
);
}

Expand All @@ -489,7 +516,7 @@ mod tests {
assert_eq!(input, &[]);
assert_eq!(
frame,
AVLFrame {
TeltonikaFrame::AVL(AVLFrame {
codec: Codec::C8Ext,
records: vec![AVLRecord {
timestamp: "2019-06-10T11:36:32Z".parse().unwrap(),
Expand Down Expand Up @@ -526,7 +553,7 @@ mod tests {
],
},],
crc16: 10644,
}
})
);
}

Expand All @@ -537,7 +564,7 @@ mod tests {
assert_eq!(input, &[]);
assert_eq!(
frame,
AVLFrame {
TeltonikaFrame::AVL(AVLFrame {
codec: Codec::C16,
records: vec![
AVLRecord {
Expand Down Expand Up @@ -602,7 +629,7 @@ mod tests {
}
],
crc16: 24499
}
})
);
}

Expand Down Expand Up @@ -668,8 +695,31 @@ mod tests {
fn parse_negative_emisphere_coordinates() {
let input = hex::decode("00000000000000460801000001776D58189001FA0A1F00F1194D80009C009D05000F9B0D06EF01F0001505C80045019B0105B5000BB6000A424257430F8044000002F1000060191000000BE1000100006E2B").unwrap();
let (input, frame) = tcp_frame(&input).unwrap();
let frame = frame.unwrap_avl();
assert_eq!(input, &[]);
assert_eq!(frame.records[0].longitude, -10.0);
assert_eq!(frame.records[0].latitude, -25.0);
}

#[test]
fn parse_command_response_codec12_1() {
let input = hex::decode("00000000000000900C010600000088494E493A323031392F372F323220373A3232205254433A323031392F372F323220373A3533205253543A32204552523A312053523A302042523A302043463A302046473A3020464C3A302054553A302F302055543A3020534D533A30204E4F4750533A303A3330204750533A31205341543A302052533A332052463A36352053463A31204D443A30010000C78F").unwrap();
let (input, frame) = tcp_frame(&input).unwrap();
let frame = frame.unwrap_gprs();
assert_eq!(input, &[]);
assert_eq!(&frame.command_responses[0], "INI:2019/7/22 7:22 RTC:2019/7/22 7:53 RST:2 ERR:1 SR:0 BR:0 CF:0 FG:0 FL:0 TU:0/0 UT:0 SMS:0 NOGPS:0:30 GPS:1 SAT:0 RS:3 RF:65 SF:1 MD:0");
}

#[test]
fn parse_command_response_codec12_2() {
let input = hex::decode("00000000000000370C01060000002F4449313A31204449323A30204449333A302041494E313A302041494E323A313639323420444F313A3020444F323A3101000066E3").unwrap();
let (input, frame) = tcp_frame(&input).unwrap();
let frame = frame.unwrap_gprs();

assert_eq!(input, &[]);
assert_eq!(
&frame.command_responses[0],
"DI1:1 DI2:0 DI3:0 AIN1:0 AIN2:16924 DO1:0 DO2:1"
);
}
}
56 changes: 44 additions & 12 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,7 @@ impl<'a> TryFrom<&'a [u8]> for AVLDatagram {
}
}

/// # Deprecated
/// Use [`AVLFrame`] instead
#[deprecated = "Use AVLFrame instead"]
pub type AVLPacket = AVLFrame;

/// Frame sent by the device
///
/// Based on [Teltonika Protocol Wiki](https://wiki.teltonika-gps.com/view/Teltonika_Data_Sending_Protocols#)
/// Frame sent by the device when sending records
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AVLFrame {
Expand All @@ -155,8 +148,12 @@ impl<'a> TryFrom<&'a [u8]> for AVLFrame {

fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
match tcp_frame(value) {
Ok((_, frame)) => Ok(frame),
Ok((_, TeltonikaFrame::AVL(frame))) => Ok(frame),
Err(e) => Err(e),
_ => Err(nom::Err::Failure(nom::error::Error::new(
value,
nom::error::ErrorKind::Fail,
))),
}
}
}
Expand All @@ -167,7 +164,7 @@ impl<'a> TryFrom<&'a [u8]> for AVLFrame {
pub struct AVLRecord {
/// In Utc Dates
pub timestamp: DateTime<Utc>,
/// How
/// How important this record is, see [`Priority`]
pub priority: Priority,
pub longitude: f64,
pub latitude: f64,
Expand All @@ -186,8 +183,6 @@ pub struct AVLRecord {
pub io_events: Vec<AVLEventIO>,
}

/// Feature with no enum values io events
/// IO event status
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand All @@ -210,3 +205,40 @@ pub enum AVLEventIOValue {
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
Variable(Vec<u8>),
}

/// Frame sent by the device when sending command responses
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GPRSFrame {
pub codec: Codec,
/// All the commands to send with this buffer
pub command_responses: Vec<String>,
/// CRC16 Calculated using [IBM/CRC16][super::crc16] algorithm and 0xA001 polynomial
pub crc16: u32,
}

/// Frame sent by the device
///
/// Based on [Teltonika Protocol Wiki](https://wiki.teltonika-gps.com/view/Teltonika_Data_Sending_Protocols#)
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TeltonikaFrame {
AVL(AVLFrame),
GPRS(GPRSFrame),
}

impl TeltonikaFrame {
pub fn unwrap_avl(self) -> AVLFrame {
if let Self::AVL(frame) = self {
return frame;
}
panic!("Frame is GPRS!")
}

pub fn unwrap_gprs(self) -> GPRSFrame {
if let Self::GPRS(frame) = self {
return frame;
}
panic!("Frame is AVL!")
}
}
Loading

0 comments on commit 131fca5

Please sign in to comment.