use byteorder::ReadBytesExt;
use chrono::{NaiveDate, NaiveDateTime};
use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
use std::io::{Read, Write};
use std::thread::sleep;
use std::time::Duration;
use thiserror::Error;
const STX: u8 = 0x02;
const ETX: u8 = 0x03;
const ENQ: u8 = 0x05;
const DATA_BYTES: usize = 3000;
#[derive(Error, Debug)]
pub enum BpmError {
#[error("Serial port error: {0}")]
SerialPort(#[from] serialport::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("CSV error: {0}")]
Csv(#[from] csv::Error),
#[error("UTF-8 conversion error: {0}")]
Utf8(#[from] std::string::FromUtf8Error),
#[error("CSV writer error: {0}")]
CsvWriter(#[from] csv::IntoInnerError<csv::Writer<Vec<u8>>>),
#[error("Data parsing error: {0}")]
Parse(String),
#[error("Device communication error: {0}")]
Communication(String),
#[error("Insufficient data received: expected {expected}, got {actual}")]
InsufficientData { expected: usize, actual: usize },
}
pub type Result<T> = std::result::Result<T, BpmError>;
pub struct Device {
device_path: String,
serial_port: Box<dyn SerialPort>,
}
impl Device {
pub fn new(device_path: &str) -> Result<Device> {
let s = serialport::new(device_path, 9600)
.data_bits(DataBits::Eight)
.parity(Parity::None)
.stop_bits(StopBits::One)
.flow_control(FlowControl::None)
.timeout(Duration::from_secs(10));
let port = s.open()?;
Ok(Device {
device_path: device_path.to_string(),
serial_port: port,
})
}
pub fn count(&mut self, user: u8) -> Result<u32> {
let response = self.send_command(&format!("?MRN{}", user))?;
if response.len() < 8 {
return Err(BpmError::InsufficientData {
expected: 8,
actual: response.len(),
});
}
let count_bytes = &response[5..8];
let count_str = std::str::from_utf8(count_bytes)
.map_err(|e| BpmError::Parse(format!("Invalid UTF-8 in count: {}", e)))?;
let count = count_str.parse::<u32>()
.map_err(|e| BpmError::Parse(format!("Failed to parse count: {}", e)))?;
Ok(count)
}
pub fn observations(&mut self, user: u8) -> Result<Vec<Observation>> {
let expected_observation_count = self.count(user)?;
let raw_response = self.send_command(&format!("?MDR{}A", user))?;
if raw_response.len() < 5 {
return Err(BpmError::InsufficientData {
expected: 5,
actual: raw_response.len(),
});
}
let mut observations = vec![];
let mut rdr = &raw_response[5..];
for _ in 0..expected_observation_count {
if rdr.len() < 20 {
break;
}
match Observation::read(&mut rdr) {
Ok(obs) => observations.push(obs),
Err(e) => {
log::warn!("Error reading observation: {}", e);
break;
}
}
}
Ok(observations)
}
fn read_from_port(&mut self, length: usize) -> Result<Vec<u8>> {
let mut buf = vec![0; length];
let bytes_read = self.serial_port.read(&mut buf)?;
Ok(buf[..bytes_read].to_vec())
}
fn send_command(&mut self, command: &str) -> Result<Vec<u8>> {
self.serial_port.write(&[STX])?;
self.serial_port.write(command.as_bytes())?;
self.serial_port.write(&[ETX])?;
sleep(Duration::from_secs(1));
let ack_response = self.read_from_port(1)?;
if ack_response.is_empty() {
return Err(BpmError::Communication("No acknowledgement received".to_string()));
}
let acknowledgement = ack_response[0];
self.serial_port.write(&[ENQ])?;
sleep(Duration::from_millis(500));
if acknowledgement == 6 {
let response = self.read_from_port(DATA_BYTES)?;
Ok(response)
} else {
Err(BpmError::Communication(format!(
"Negative acknowledgement received: {}",
acknowledgement
)))
}
}
pub fn device_path(&self) -> &str {
&self.device_path
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Observation {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub regular_heart_beat: u8,
pub systolic: u16,
pub diastolic: u16,
pub pulse: u16,
pub body_movement: u8,
pub incorrect_cuff_wrapping: u8,
pub unsuitable_temperature: u8,
pub usable: u8,
}
impl Observation {
pub fn read<R: Read>(rdr: &mut R) -> Result<Observation> {
let mut buf = [0; 2];
rdr.read_exact(&mut buf)?;
let year = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse year: {}", e)))?
.parse::<u16>()
.map_err(|e| BpmError::Parse(format!("Failed to parse year: {}", e)))?;
rdr.read_exact(&mut buf)?;
let month = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse month: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse month: {}", e)))?;
rdr.read_exact(&mut buf)?;
let day = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse day: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse day: {}", e)))?;
rdr.read_exact(&mut buf)?;
let hour = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse hour: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse hour: {}", e)))?;
rdr.read_exact(&mut buf)?;
let minute = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse minute: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse minute: {}", e)))?;
let mut buf = [0; 1];
rdr.read_exact(&mut buf)?;
let regular_heart_beat = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse regular_heart_beat: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse regular_heart_beat: {}", e)))?;
let mut buf = [0; 3];
rdr.read_exact(&mut buf)?;
let systolic = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse systolic: {}", e)))?
.parse::<u16>()
.map_err(|e| BpmError::Parse(format!("Failed to parse systolic: {}", e)))?;
rdr.read_exact(&mut buf)?;
let diastolic = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse diastolic: {}", e)))?
.parse::<u16>()
.map_err(|e| BpmError::Parse(format!("Failed to parse diastolic: {}", e)))?;
rdr.read_exact(&mut buf)?;
let pulse = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse pulse: {}", e)))?
.parse::<u16>()
.map_err(|e| BpmError::Parse(format!("Failed to parse pulse: {}", e)))?;
rdr.read_exact(&mut buf[..1])?;
let body_movement = std::str::from_utf8(&buf[..1])
.map_err(|e| BpmError::Parse(format!("Failed to parse body_movement: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse body_movement: {}", e)))?;
rdr.read_exact(&mut buf[..1])?;
let incorrect_cuff_wrapping = std::str::from_utf8(&buf[..1])
.map_err(|e| BpmError::Parse(format!("Failed to parse incorrect_cuff_wrapping: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse incorrect_cuff_wrapping: {}", e)))?;
rdr.read_exact(&mut buf[..1])?;
let unsuitable_temperature = std::str::from_utf8(&buf[..1])
.map_err(|e| BpmError::Parse(format!("Failed to parse unsuitable_temperature: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse unsuitable_temperature: {}", e)))?;
let _ = rdr.read_u8()?;
let mut buf = [0; 1];
rdr.read_exact(&mut buf)?;
let usable = std::str::from_utf8(&buf)
.map_err(|e| BpmError::Parse(format!("Failed to parse usable: {}", e)))?
.parse::<u8>()
.map_err(|e| BpmError::Parse(format!("Failed to parse usable: {}", e)))?;
Ok(Observation {
year,
month,
day,
hour,
minute,
regular_heart_beat,
systolic,
diastolic,
pulse,
body_movement,
incorrect_cuff_wrapping,
unsuitable_temperature,
usable,
})
}
pub fn date(&self) -> Option<NaiveDate> {
if self.year == 0 || self.month == 0 || self.day == 0 {
None
} else {
NaiveDate::from_ymd_opt(self.year as i32 + 2000, self.month as u32, self.day as u32)
}
}
pub fn datetime(&self) -> Option<NaiveDateTime> {
if let Some(date) = self.date() {
date.and_hms_opt(self.hour as u32, self.minute as u32, 0)
} else {
None
}
}
pub fn is_usable(&self) -> bool {
self.usable == 1
}
pub fn has_body_movement(&self) -> bool {
self.body_movement == 1
}
pub fn has_incorrect_cuff_wrapping(&self) -> bool {
self.incorrect_cuff_wrapping == 1
}
pub fn has_unsuitable_temperature(&self) -> bool {
self.unsuitable_temperature == 1
}
pub fn has_regular_heart_beat(&self) -> bool {
self.regular_heart_beat == 1
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Datelike, Timelike};
#[test]
fn test_observation_date() {
let obs = Observation {
year: 23,
month: 12,
day: 25,
hour: 14,
minute: 30,
regular_heart_beat: 1,
systolic: 120,
diastolic: 80,
pulse: 72,
body_movement: 0,
incorrect_cuff_wrapping: 0,
unsuitable_temperature: 0,
usable: 1,
};
let date = obs.date().unwrap();
assert_eq!(date.year(), 2023);
assert_eq!(date.month(), 12);
assert_eq!(date.day(), 25);
let datetime = obs.datetime().unwrap();
assert_eq!(datetime.hour(), 14);
assert_eq!(datetime.minute(), 30);
}
#[test]
fn test_observation_flags() {
let obs = Observation {
year: 23,
month: 1,
day: 1,
hour: 0,
minute: 0,
regular_heart_beat: 1,
systolic: 120,
diastolic: 80,
pulse: 72,
body_movement: 1,
incorrect_cuff_wrapping: 0,
unsuitable_temperature: 1,
usable: 1,
};
assert!(obs.is_usable());
assert!(obs.has_regular_heart_beat());
assert!(obs.has_body_movement());
assert!(!obs.has_incorrect_cuff_wrapping());
assert!(obs.has_unsuitable_temperature());
}
}