use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use bytes::Bytes;
use chrono::{NaiveTime, Timelike};
use std::{
io::{self, Cursor},
str,
time::Duration,
};
use super::command::{AcHertz, DbMode, DigitCount, TempUnit};
use crate::rawmea::{RawMeasurement, RawRecordingSession};
#[derive(Debug, Clone)]
pub enum Response {
Success(Option<ResponsePayload>), SyntaxError, ExecutionError, NoData, }
impl PartialEq for Response {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Success(_), Self::Success(_)) => true,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
#[derive(Debug, Clone)]
pub enum ResponsePayload {
Id(Ident),
Settings(DeviceSettings),
MeasurementBinary(RawMeasurement),
SavedMeasurements(Vec<RawMeasurement>),
RecordingSession(RawRecordingSession),
}
#[derive(Debug, Clone)]
pub struct Ident {
pub model: String,
pub firmware: String,
pub serial: String,
}
impl TryFrom<&[u8]> for Ident {
type Error = io::Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let value = str::from_utf8(value)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
.to_string();
let values: Vec<&str> = value.split(',').collect();
if values.len() == 3 {
Ok(Self {
model: String::from(values[0]),
firmware: String::from(values[1]),
serial: String::from(values[2]),
})
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Invalid data for ID response: {}", value),
))
}
}
}
#[derive(Debug, Clone, Default)]
pub struct DeviceSettings {
pub interval: u16,
pub db_mode: u16,
pub db_ref: u16,
pub temp_offset: i16,
pub temp_unit: u16,
pub bl_off: u16,
pub clock: u32,
pub po_off: u16,
pub hz: u16,
pub digits: u16,
pub beep: u16,
pub rec_tolerance: u16,
pub unknown1: u8,
pub unknown2: u16,
}
impl DeviceSettings {
pub fn beeper(&self) -> bool {
self.beep == 1
}
pub fn set_beeper(&mut self, beep: bool) {
self.beep = if beep { 1 } else { 0 };
}
pub fn digit_count(&self) -> DigitCount {
match self.digits {
1 => DigitCount::Digit4,
0 => DigitCount::Digit5,
_ => panic!("Unknown digit count: {}", self.digits),
}
}
pub fn set_digit_count(&mut self, digits: DigitCount) {
match digits {
DigitCount::Digit4 => self.digits = 1,
DigitCount::Digit5 => self.digits = 0,
}
}
pub fn ac_hertz(&self) -> AcHertz {
match self.hz {
0 => AcHertz::Hertz50,
1 => AcHertz::Hertz60,
_ => panic!("Unknown ac hertz: {}", self.hz),
}
}
pub fn set_ac_hertz(&mut self, hertz: AcHertz) {
match hertz {
AcHertz::Hertz50 => self.hz = 0,
AcHertz::Hertz60 => self.hz = 1,
}
}
pub fn backlight_off(&self) -> u16 {
self.bl_off / 10
}
pub fn set_backlight_off(&mut self, seconds: u16) {
self.bl_off = seconds * 10;
}
pub fn autopower_off(&self) -> u16 {
self.po_off
}
pub fn set_autopower_off(&mut self, minutes: u16) {
self.po_off = minutes;
}
pub fn temp_unit(&self) -> TempUnit {
match self.temp_unit {
0 => TempUnit::F,
1 => TempUnit::C,
_ => panic!("Invalid temp unit"),
}
}
pub fn set_temp_unit(&mut self, unit: TempUnit) {
self.temp_unit = match unit {
TempUnit::F => 0,
TempUnit::C => 1,
};
}
pub fn db_mode(&self) -> DbMode {
match self.db_mode {
0 => DbMode::Millivolt,
1 => DbMode::Volt,
_ => panic!("Invalid db mode"),
}
}
pub fn set_db_mode(&mut self, mode: DbMode) {
self.db_mode = match mode {
DbMode::Millivolt => 0,
DbMode::Volt => 1,
};
}
pub fn recording_thd(&self) -> u8 {
(self.rec_tolerance / 100) as u8
}
pub fn set_recording_thd(&mut self, thd: u8) {
self.rec_tolerance = thd as u16 * 100;
}
pub fn recording_interval(&self) -> u16 {
self.interval / 10
}
pub fn set_recording_interval(&mut self, seconds: u16) {
self.interval = seconds * 10;
}
pub fn d_bm_ref(&self) -> u16 {
self.db_ref
}
pub fn set_d_bm_ref(&mut self, ohm: u16) {
self.db_ref = ohm & 0xFFF
}
pub fn temp_offset(&self) -> i16 {
match self.temp_unit {
0 => self.temp_offset,
1 => (self.temp_offset as f32 / (9.0 / 5.0)) as i16,
_ => panic!("Unknown temp_unit"),
}
}
pub fn set_temp_offset(&mut self, offset: i16) {
if (-1999..=1999).contains(&offset) {
match self.temp_unit {
0 => self.temp_offset = offset,
1 => self.temp_offset = ((offset as f32) * (9.0 / 5.0)) as i16,
_ => panic!("Unknown temp_unit"),
}
} else {
panic!("Temp. offset max allowed = +/-1999, value is: {}", offset);
}
}
pub fn clock(&self) -> NaiveTime {
clock_to_naive_time(self.clock)
}
pub fn set_clock(&mut self, time: NaiveTime) {
self.clock = time.num_seconds_from_midnight() * 10;
}
pub fn as_bytes(&self) -> Result<Bytes, std::io::Error> {
let mut cur = Cursor::new(Vec::new());
cur.write_u16::<LittleEndian>(self.interval)?;
cur.write_u16::<LittleEndian>(self.db_mode)?;
cur.write_u16::<LittleEndian>(self.db_ref)?;
cur.write_i16::<LittleEndian>(self.temp_offset)?;
cur.write_u16::<LittleEndian>(self.temp_unit)?;
cur.write_u16::<LittleEndian>(self.bl_off)?;
cur.write_u32::<LittleEndian>(self.clock)?;
cur.write_u16::<LittleEndian>(self.po_off)?;
cur.write_u16::<LittleEndian>(self.hz)?;
cur.write_u16::<LittleEndian>(self.digits)?;
cur.write_u16::<LittleEndian>(self.beep)?;
cur.write_u16::<LittleEndian>(self.rec_tolerance)?;
cur.write_u8(self.unknown1)?;
cur.write_u16::<LittleEndian>(self.unknown2)?;
Ok(cur.into_inner().into())
}
}
impl TryFrom<&[u8]> for DeviceSettings {
type Error = std::io::Error;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
if value[0..3].ne(&[b'Q', b'S', b',']) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Failed to read QS header",
));
}
let value = &value[3..];
let mut cur = Cursor::new(value);
let interval = cur.read_u16::<LittleEndian>()?;
let db_mode = cur.read_u16::<LittleEndian>()?;
let db_ref = cur.read_u16::<LittleEndian>()?;
let temp_offset = cur.read_i16::<LittleEndian>()?;
let temp_unit = cur.read_u16::<LittleEndian>()?;
let bl_off = cur.read_u16::<LittleEndian>()?;
let clock = cur.read_u32::<LittleEndian>()?;
let po_off = cur.read_u16::<LittleEndian>()?;
let hz = cur.read_u16::<LittleEndian>()?;
let digits = cur.read_u16::<LittleEndian>()?;
let beep = cur.read_u16::<LittleEndian>()?;
let rec_tolerance = cur.read_u16::<LittleEndian>()?;
let unknown1 = cur.read_u8()?;
let unknown2 = cur.read_u16::<LittleEndian>()?;
Ok(Self {
interval,
db_mode,
db_ref,
temp_offset,
temp_unit,
bl_off,
clock,
po_off,
hz,
digits,
beep,
rec_tolerance,
unknown1,
unknown2,
})
}
}
pub fn clock_to_naive_time(clock: u32) -> NaiveTime {
const DAY_DURATION: f32 = (24 * 60 * 60) as f32;
let internal_clock = clock as f32 / 10.0;
let since_midnight = Duration::from_secs_f32(internal_clock % DAY_DURATION);
NaiveTime::default() + since_midnight
}