gsof_protocol 0.1.23

Software to collect data generated by sources who provide GSOF messages (Trimble)
Documentation
use crate::protocol::tables::{TableSet};
use crate::protocol::tables::{
	HeadTable, 
	GPSTimeTable,
	ECEFTable,
	SIGMATable,
	BattMemTable, 
	PositionVCV
};


use crate::protocol::time::gpstime;
use chrono::{DateTime, Utc};

use super::dt_gen_format;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::error::Error;
use crate::structure;

use tracing::{info, warn, error};
use moebius_tools::time::has_dt_gen::HasDtGen;


#[doc = r#"
This struct contains the message received from source.
Is deserialized ona HasMap an describe by dt_gen, checksum
"#]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GsofData {
    #[serde(with = "dt_gen_format")]
    pub dt_recv: DateTime<Utc>,    
    #[serde(with = "dt_gen_format")]
    pub dt_gen: DateTime<Utc>,
    pub checksum: u16,
	pub head: Option<HeadTable>,
    pub time: Option<GPSTimeTable>,
    pub ecef: Option<ECEFTable>,
    pub sigma: Option<SIGMATable>,
    pub batt_mem: Option<BattMemTable>,
	pub position_vcv: Option<PositionVCV>
}

impl HasDtGen for GsofData {
	fn get_dt_gen(&self) -> DateTime<Utc> {
		self.dt_gen
	}	
}
impl  Default for GsofData {
	/// Create a default object to complete, is recommended to create
	/// as mut and then put the correct information.
    fn default() -> Self {
        GsofData {
            dt_recv: Utc::now(),            
            dt_gen: Utc::now(),
            checksum:0,
			head:None,
            time:None,
            ecef:None,
            sigma:None,
            batt_mem:None,
			position_vcv:None
        }
    }
}


impl fmt::Display for GsofData {
	/// Enables string format for GsofData
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Data(protocol: Gsof, recv {}, gen, {})", self.dt_recv, self.dt_gen )
    }
}


// impl SerdeProtocol for GsofData {
    
// }

use tokio::io::AsyncReadExt;


impl GsofData {
	
	/// Create a new GsofData object, is like default but with some
	/// arguments predefined
    pub fn new(
        dt_recv:DateTime<Utc>,
        dt_gen:DateTime<Utc>,
        checksum:u16)-> Self {
        Self {
            dt_recv,
            dt_gen,
            checksum,
			head:None,
            time:None,
            ecef:None,
            sigma:None,
            batt_mem:None,
			position_vcv:None
        }
    }

	fn set_head(&mut self, table:&HeadTable) {
        self.head = Some(*table);
	}
	/// set table TIME
    fn set_time(&mut self, table: &GPSTimeTable) {
        self.time = Some(*table);
    }

	/// set table ECEF 
    fn set_ecef(&mut self, table: &ECEFTable) {
        self.ecef = Some(*table);
    }

	/// set table SIGMA
    fn set_sigma(&mut self, table: &SIGMATable) {
        self.sigma = Some(*table);
    }

	/// set table BATT_MEM
    fn set_batt_mem(&mut self, table: &BattMemTable) {
        self.batt_mem = Some(*table);
    }

	/// set table POSITION_VCV
    fn set_position_vcv(&mut self, table: &PositionVCV) {
        self.position_vcv = Some(*table);
    }

	/// set dt_gen value
    fn set_dt_gen(&mut self, dt: &DateTime<Utc>) {
        self.dt_gen = *dt;
    }

	/// set dt_recv value
    fn set_dt_recv(&mut self, dt: &DateTime<Utc>) {
        self.dt_recv = *dt;
    }
	
	/// Calculate the latency between generated data and received
    pub fn latency(&self) -> i64 {
		/*
		returns the difference between the dt_recv and dt_gen in milliseconds
		 */
        let diff = self.dt_recv - self.dt_gen;
        diff.num_milliseconds()        
    }
    
	/// set checksum value to object
    fn set_checksum(&mut self, checksum: &u16) {
        self.checksum = *checksum;
    }

	/// Serialize the GsofData
    pub fn json_dumps(&self) -> String {
        serde_json::to_string_pretty(&self).unwrap()
    }

	/// Deserialize the GsofData
    pub fn json_loads(serialized: &str) -> Self {
        serde_json::from_str(serialized).unwrap()
    }
    
	/// Serialize to serde::Value
    pub fn json_to_value(&self) -> serde_json::Value {
        serde_json::to_value(self).unwrap()
    }

	/// Deserialize frm serde::Value
    pub fn json_from_value(
		value:&serde_json::Value) -> Result<Self,Box<dyn  Error>> {
        match serde_json::from_value(value.clone()){
			Ok(val)=> Ok(val),
			Err(err)=>Err(Box::new(err))
		}
    }

	/// Take the bytes and read by slice ad create the identification
	/// with the tables associated to the Gsof protocol
	/// TableData:
	/// - name :: name of the table
	/// - value :: HashMap with names and values
    fn get_records(
        slice_line:&[u8],
        tables: &TableSet,
        checksum: u16) ->
    Result<Self, String> {
		// starting
        let mut line:Vec<u8> = slice_line.to_vec();
        /* let consume line by 2 and msg length the go to the next message*/
        let mut collect_tables = GsofData::default();
        collect_tables.set_checksum(&checksum);
        while line.len() > 3 {
            let byte_struct = structure!(">2B");
            let head = &line[0..2];
            let values = byte_struct.unpack(head).unwrap();
            let table_key:u8 = values.0;
            let record_length:usize = values.1 as usize;
            let table_bytes = &line[2..record_length+2];
            match tables.get(&table_key) {
                Some(table_info) if table_info.name=="TIME"=> {
                    /*
                    reference: https://www.trimble.com/OEM_ReceiverHelp/v5.11/en/GSOFmessages_TIME.html
                     */
                    let byte_struct = structure!(">IH4B");
                    //let conv = &table_info.conversion;
                    //let fields = &table_info.fields;
                    let values = byte_struct.unpack(table_bytes).unwrap();
                    let table = GPSTimeTable::new(values);
                    collect_tables.set_time(&table);
                },
                Some(table_info)  if table_info.name=="ECEF"=> {
                    let byte_struct = structure!(">3d");
                    // let conv = &table_info.conversion;
                    // let fields = &table_info.fields;
                    let values = byte_struct.unpack(table_bytes).unwrap();
                    let table = ECEFTable::new(values);
                    collect_tables.set_ecef(&table);
                },
                Some(table_info)  if table_info.name=="SIGMA"=> {
                    let byte_struct = structure!(">9fH");
                    // let conv = &table_info.conversion;
                    // let fields = &table_info.fields;
                    let values = byte_struct.unpack(table_bytes).unwrap();
                    let table = SIGMATable::new(values);
                    collect_tables.set_sigma(&table);
                },
                Some(table_info)  if table_info.name=="BATT_MEM"=> {
                    let byte_struct = structure!(">Hd");
                    // let conv = &table_info.conversion;
                    // let fields = &table_info.fields;
                    let values = byte_struct.unpack(table_bytes).unwrap();
                    let table = BattMemTable::new(values);
                    collect_tables.set_batt_mem(&table);
                },
                Some(table_info)  if table_info.name=="POSITION_VCV"=> {
                    let byte_struct = structure!(">8fh");
                    // let conv = &table_info.conversion;
                    // let fields = &table_info.fields;
                    let values = byte_struct.unpack(table_bytes).unwrap();
                    let table = PositionVCV::new(values);
                    collect_tables.set_position_vcv(&table);
                },
                Some(table_info) => {
					info!("Table not registered for option {}", table_info.name);
				},
                None => {
					warn!("Error de conversion al obtener table_info");
				}
			};
            if record_length <= line.len() {
                line.drain(0..record_length+2);
            } else {
                line.drain(0..line.len());
            }
        }  // Close while then build tables

		// set the time
        match collect_tables.time { 
            Some(ref table)=>{
                let leap: i64 = 18;
                match gpstime(&table, leap) {
                    Ok(dt_gen)=>{
                        let dt_recv:DateTime<Utc> = Utc::now();
                        collect_tables.set_dt_gen(&dt_gen);
                        collect_tables.set_dt_recv(&dt_recv);
                        Ok(collect_tables)
                    },
                    Err(e)=>{
						error!("Cannot convert table time to a Datetime<Utc>");
						return Err(e.to_string());
					}
				} // close gpstime match
            },
            None => {
                let dt_gen:DateTime<Utc> = Utc::now();
                let dt_recv:DateTime<Utc> = Utc::now();
				warn!("Data received at {} doesn't have TIME table", dt_recv);
                collect_tables.set_dt_gen(&dt_gen);
                collect_tables.set_dt_recv(&dt_recv);
                Ok(collect_tables)
            }
        } // close last match
    } // clsoe fn get_records
    

	/// Take the bufreader stream and read by slice to recognize the
	/// message information and create a new GsofData
    pub async fn read_from_stream<S:AsyncReadExt + Unpin>(
        read_stream:&mut S,
        tables:&TableSet) -> Result<Self,String> {
        /*
        get header by key 0
         */
        let table_key:u8 = 0;
        let header_metadata = tables.get(&table_key).unwrap();
        // these both are vectors
        let header_conv = &header_metadata.conversion;
        //let header_fields = &header_metadata.fields;
        let header_bytes:u8 = 7;
        let mut header_buff = vec![0;header_bytes.into()];
        //let mut line = vec![0;200];
        /* read the header bytes*/
        match read_stream.read_exact(&mut header_buff).await {
            Ok(_n)=>{
                let header = header_buff;
                let conv = &header_conv[0];
                assert_eq!(conv, ">7B");
                let len = header.len();
                let to_sum = &header[1..len];
                let sum_head:u16 = to_sum
                    .iter().fold(0, |a, &b| a as u16 + b as u16);                
                let byte_struct = structure!(">7B");
                let values = byte_struct.unpack(header).unwrap();
                //println!("Header values->{:?}", values);
                let head_table = HeadTable::new(values);
                //println!("{:?}", head_table);
                let msg_len = &head_table.length -3;
                let mut line = vec![0;msg_len.into()];
                match read_stream.read_exact(&mut line).await {
                    Ok(_n)=> {
                        // println!("{} bytes para mensaje", n);
                        // println!("Sumando....{:?}", line);
                        let tot_bytes = line.iter().fold(0, |a, &b| a as u16 + b as
        u16);
						let mut checksum = vec![0;2];

                        /* processing records
                        first a slice of '2' bytes to have the head for every
        table
                        that gives:
                        - record_type :: table id
                        - record_length :: table_length
                         */
                        //let records = -> GsofData
						// MATCH read checksum
						match read_stream.read_exact(&mut checksum).await {
							Ok(_n)=> {
								let byte_struct = structure!(">2B");
								let values = byte_struct.unpack(checksum).unwrap();
								let check = (sum_head+tot_bytes) % 256;

								if check == values.0 as u16 {
									match Self::get_records(
										&line, 
										tables,
										check) {
										Ok(mut data) => {
											data.set_head(&head_table);
											Ok(data)
										},
										Err(_) => Err("Error on get_records".into())
									}
								}
								else {
									warn!("Message with wrong checksum {} != {}",
										  check, 
										  values.0);
									Err("Error on checksum".into())
								}
							},
							Err(err)=>{
								error!("Error at reading stream slice checksum {}", err);
								Err(err.to_string())
							}
						} // close match nested
                    },
                    Err(err)=> {
						error!("Error al leer de stream slice body {}", err);
						Err(err.to_string())
					}
                }
            }, // Closing Ok 1 level
            Err(err)=> {
				error!("Error ar reading stream slice head {}", err);
				Err(err.to_string())
			}
        } // close match 1 level
    }// close  read_from_stream

}// end impl GsofData