Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time stamp parsing #14

Merged
merged 10 commits into from
Jul 7, 2024
232 changes: 232 additions & 0 deletions src/user_data/data_information.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,106 @@ impl From<TextUnit<'_>> for String {
}
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum Month {
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
}

#[cfg(feature = "std")]
impl std::fmt::Display for Month {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Month::January => write!(f, "Jan"),
Month::February => write!(f, "Feb"),
Month::March => write!(f, "Mar"),
Month::April => write!(f, "Apr"),
Month::May => write!(f, "May"),
Month::June => write!(f, "Jun"),
Month::July => write!(f, "Jul"),
Month::August => write!(f, "Aug"),
Month::September => write!(f, "Sep"),
Month::October => write!(f, "Oct"),
Month::November => write!(f, "Nov"),
Month::December => write!(f, "Dec"),
}
}
}

pub type Year = u16;
pub type DayOfMonth = u8;
pub type Hour = u8;
pub type Minute = u8;
pub type Second = u8;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub enum SingleEveryOrInvalid<T> {
Single(T),
Every(),
Invalid(),
}

#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Debug, PartialEq)]
pub enum DataType<'a> {
Text(TextUnit<'a>),
Number(f64),
Date(
SingleEveryOrInvalid<DayOfMonth>,
SingleEveryOrInvalid<Month>,
SingleEveryOrInvalid<Year>,
),
Time(
SingleEveryOrInvalid<Second>,
SingleEveryOrInvalid<Minute>,
SingleEveryOrInvalid<Hour>,
),
DateTime(
SingleEveryOrInvalid<DayOfMonth>,
SingleEveryOrInvalid<Month>,
SingleEveryOrInvalid<Year>,
SingleEveryOrInvalid<Hour>,
SingleEveryOrInvalid<Minute>,
),
DateTimeWithSeconds(
SingleEveryOrInvalid<DayOfMonth>,
SingleEveryOrInvalid<Month>,
SingleEveryOrInvalid<Year>,
SingleEveryOrInvalid<Hour>,
SingleEveryOrInvalid<Minute>,
SingleEveryOrInvalid<Second>,
),
}
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(PartialEq, Debug)]
pub struct Data<'a> {
value: Option<DataType<'a>>,
size: usize,
}

#[cfg(feature = "std")]
impl<T: std::fmt::Display> std::fmt::Display for SingleEveryOrInvalid<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SingleEveryOrInvalid::Single(value) => write!(f, "{}", value),
SingleEveryOrInvalid::Every() => write!(f, "Every"),
SingleEveryOrInvalid::Invalid() => write!(f, "Invalid"),
}
}
}

#[cfg(feature = "std")]
impl std::fmt::Display for Data<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand All @@ -277,6 +365,20 @@ impl std::fmt::Display for Data<'_> {
write!(f, "{}", text)
}
DataType::Number(value) => write!(f, "{}", value),
DataType::Date(day, month, year) => write!(f, "{}/{}/{}", day, month, year),
DataType::DateTime(day, month, year, hour, minute) => {
write!(f, "{}/{}/{} {}:{}:00", day, month, year, hour, minute)
}
DataType::DateTimeWithSeconds(day, month, year, hour, minute, second) => {
write!(
f,
"{}/{}/{} {}:{}:{}",
day, month, year, hour, minute, second
)
}
DataType::Time(seconds, minutes, hours) => {
write!(f, "{}:{}:{}", hours, minutes, seconds)
}
},
None => write!(f, "No Data"),
}
Expand All @@ -290,6 +392,48 @@ impl Data<'_> {
}
}

macro_rules! parse_single_or_every {
($input:expr, $mask:expr, $all_value:expr, $shift:expr) => {
if $input & $mask == $all_value {
SingleEveryOrInvalid::Every()
} else {
SingleEveryOrInvalid::Single(($input & $mask) >> $shift)
}
};
}

macro_rules! parse_month {
($input:expr) => {
match $input & 0xF {
0x1 => SingleEveryOrInvalid::Single(Month::January),
0x2 => SingleEveryOrInvalid::Single(Month::February),
0x3 => SingleEveryOrInvalid::Single(Month::March),
0x4 => SingleEveryOrInvalid::Single(Month::April),
0x5 => SingleEveryOrInvalid::Single(Month::May),
0x6 => SingleEveryOrInvalid::Single(Month::June),
0x7 => SingleEveryOrInvalid::Single(Month::July),
0x8 => SingleEveryOrInvalid::Single(Month::August),
0x9 => SingleEveryOrInvalid::Single(Month::September),
0xA => SingleEveryOrInvalid::Single(Month::October),
0xB => SingleEveryOrInvalid::Single(Month::November),
0xC => SingleEveryOrInvalid::Single(Month::December),
_ => SingleEveryOrInvalid::Invalid(),
}
};
}

macro_rules! parse_year {
($input:expr, $mask_byte1:expr, $mask_byte2:expr, $all_value:expr) => {{
let year = ((u16::from($input[1] & $mask_byte1) >> 1)
| (u16::from($input[0] & $mask_byte2) >> 5)) as u16;
if year == $all_value {
SingleEveryOrInvalid::Every()
} else {
SingleEveryOrInvalid::Single(year)
}
}};
}

impl DataFieldCoding {
pub fn parse<'a>(&self, input: &'a [u8]) -> Result<Data<'a>, DataRecordError> {
match self {
Expand Down Expand Up @@ -519,6 +663,70 @@ impl DataFieldCoding {
// Special functions parsing based on the code
todo!()
}

DataFieldCoding::DateTypeG => {
if input.len() < 2 {
return Err(DataRecordError::InsufficientData);
}
let day = parse_single_or_every!(input[0], 0x1F, 0, 0);
let month = parse_month!(input[1]);
let year = parse_year!(input, 0xF0, 0xE0, 0x7F);

Ok(Data {
value: Some(DataType::Date(day, month, year)),
size: 2,
})
}
DataFieldCoding::DateTimeTypeF => {
if input.len() < 4 {
return Err(DataRecordError::InsufficientData);
}
let minutes = parse_single_or_every!(input[0], 0x3F, 0x3F, 0);
let hour = parse_single_or_every!(input[1], 0x1F, 0x1F, 0);
let day = parse_single_or_every!(input[2], 0x1F, 0x1F, 0);
let month = parse_month!(input[3]);
let year = parse_year!(input, 0xF0, 0xE0, 0x7F);

Ok(Data {
value: Some(DataType::DateTime(day, month, year, hour, minutes)),
size: 4,
})
}
DataFieldCoding::DateTimeTypeJ => {
if input.len() < 2 {
return Err(DataRecordError::InsufficientData);
}
let seconds = parse_single_or_every!(input[0], 0x3F, 0x3F, 0);
let minutes = parse_single_or_every!(input[1], 0x3F, 0x3F, 0);
let hours = parse_single_or_every!(input[2], 0x1F, 0x1F, 0);

Ok(Data {
value: Some(DataType::Time(seconds, minutes, hours)),
size: 4,
})
}
DataFieldCoding::DateTimeTypeI => {
// note: more information can be extracted from the data,
// however, because this data can be derived from the other data that is
// that is extracted, it is not necessary to extract it.

if input.len() < 6 {
return Err(DataRecordError::InsufficientData);
}
let seconds = parse_single_or_every!(input[0], 0x3F, 0x3F, 0);
let minutes = parse_single_or_every!(input[1], 0x3F, 0x3F, 0);
let hours = parse_single_or_every!(input[2], 0x1F, 0x1F, 0);
let days = parse_single_or_every!(input[3], 0x1F, 0x1F, 0);
let months = parse_month!(input[4]);
let year = parse_year!(input, 0xF0, 0xE0, 0x7F);

Ok(Data {
value: Some(DataType::DateTimeWithSeconds(
days, months, year, hours, minutes, seconds,
)),
size: 6,
})
}
}
}
}
Expand Down Expand Up @@ -707,6 +915,22 @@ impl DataFieldCoding {
data: 0.0,
byte_size: 0,
},
DataFieldCoding::DateTypeG => Value {
data: 0.0,
byte_size: 0,
},
DataFieldCoding::DateTimeTypeF => Value {
data: 0.0,
byte_size: 0,
},
DataFieldCoding::DateTimeTypeJ => Value {
data: 0.0,
byte_size: 0,
},
DataFieldCoding::DateTimeTypeI => Value {
data: 0.0,
byte_size: 0,
},
}
}
}
Expand All @@ -729,6 +953,10 @@ pub enum DataFieldCoding {
VariableLength,
BCDDigit12,
SpecialFunctions(SpecialFunctions),
DateTypeG,
DateTimeTypeF,
DateTimeTypeJ,
DateTimeTypeI,
}

#[cfg(feature = "std")]
Expand All @@ -750,6 +978,10 @@ impl std::fmt::Display for DataFieldCoding {
DataFieldCoding::BCD8Digit => write!(f, "BCD 8-digit"),
DataFieldCoding::VariableLength => write!(f, "Variable Length"),
DataFieldCoding::BCDDigit12 => write!(f, "BCD 12-digit"),
DataFieldCoding::DateTypeG => write!(f, "Date Type G"),
DataFieldCoding::DateTimeTypeF => write!(f, "Date Time Type F"),
DataFieldCoding::DateTimeTypeJ => write!(f, "Date Time Type J"),
DataFieldCoding::DateTimeTypeI => write!(f, "Date Time Type I"),
DataFieldCoding::SpecialFunctions(code) => write!(f, "Special Functions ({:?})", code),
}
}
Expand Down
22 changes: 19 additions & 3 deletions src/user_data/data_record.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
data_information::{Data, DataInformation, DataInformationBlock},
value_information::{ValueInformation, ValueInformationBlock},
data_information::{Data, DataFieldCoding, DataInformation, DataInformationBlock},
value_information::{ValueInformation, ValueInformationBlock, ValueLabel},
variable_user_data::DataRecordError,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
Expand Down Expand Up @@ -68,8 +68,24 @@ impl<'a> TryFrom<&RawDataRecordHeader<'a>> for ProcessedDataRecordHeader {
) -> Result<ProcessedDataRecordHeader, DataRecordError> {
let value_information =
ValueInformation::try_from(&raw_data_record_header.value_information_block)?;
let data_information =
let mut data_information =
DataInformation::try_from(&raw_data_record_header.data_information_block)?;

// unfortunately, the data field coding is not always set in the data information block
// so we must do some additional checks to determine the correct data field coding

if value_information.labels.contains(&ValueLabel::Date) {
data_information.data_field_coding = DataFieldCoding::DateTypeG;
} else if value_information.labels.contains(&ValueLabel::DateTime) {
data_information.data_field_coding = DataFieldCoding::DateTimeTypeF;
} else if value_information.labels.contains(&ValueLabel::Time) {
data_information.data_field_coding = DataFieldCoding::DateTimeTypeJ;
} else if value_information
.labels
.contains(&ValueLabel::DateTimeWithSeconds)
{
data_information.data_field_coding = DataFieldCoding::DateTimeTypeI;
}
Ok(ProcessedDataRecordHeader {
data_information,
value_information,
Expand Down
8 changes: 6 additions & 2 deletions src/user_data/value_information.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ impl TryFrom<&ValueInformationBlock> for ValueInformation {
decimal_scale_exponent +=
(value_information_block.value_information.data & 0b11) as isize - 3;
}
0x6C..=0x6D => labels.push(ValueLabel::TimePoint),
0x6C => labels.push(ValueLabel::Date),
0x6D => labels.push(ValueLabel::DateTime),
0x6E => labels.push(ValueLabel::DimensionlessHCA),
0x70..=0x73 => labels.push(ValueLabel::AveragingDuration),
0x74..=0x77 => labels.push(ValueLabel::ActualityDuration),
Expand Down Expand Up @@ -880,7 +881,10 @@ pub enum ValueLabel {
CompactProfile,
ActualityDuration,
AveragingDuration,
TimePoint,
Date,
Time,
DateTime,
DateTimeWithSeconds,
FabricationNumber,
EnhancedIdentification,
Address,
Expand Down
1 change: 1 addition & 0 deletions src/user_data/variable_user_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use super::DataRecords;
pub enum DataRecordError {
DataInformationError(data_information::DataInformationError),
InsufficientData,
InvalidData,
}

#[derive(Debug, PartialEq)]
Expand Down