1use byteorder::ReadBytesExt;
2use chrono::{NaiveDate, NaiveDateTime};
3use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
4use std::io::{Read, Write};
5use std::thread::sleep;
6use std::time::Duration;
7use thiserror::Error;
8
9const STX: u8 = 0x02;
10const ETX: u8 = 0x03;
11const ENQ: u8 = 0x05;
12const DATA_BYTES: usize = 3000;
13
14#[derive(Error, Debug)]
15pub enum BpmError {
16 #[error("Serial port error: {0}")]
17 SerialPort(#[from] serialport::Error),
18 #[error("IO error: {0}")]
19 Io(#[from] std::io::Error),
20 #[error("CSV error: {0}")]
21 Csv(#[from] csv::Error),
22 #[error("UTF-8 conversion error: {0}")]
23 Utf8(#[from] std::string::FromUtf8Error),
24 #[error("CSV writer error: {0}")]
25 CsvWriter(#[from] csv::IntoInnerError<csv::Writer<Vec<u8>>>),
26 #[error("Data parsing error: {0}")]
27 Parse(String),
28 #[error("Device communication error: {0}")]
29 Communication(String),
30 #[error("Insufficient data received: expected {expected}, got {actual}")]
31 InsufficientData { expected: usize, actual: usize },
32}
33
34pub type Result<T> = std::result::Result<T, BpmError>;
35
36pub struct Device {
38 device_path: String,
39 serial_port: Box<dyn SerialPort>,
40}
41
42impl Device {
43 pub fn new(device_path: &str) -> Result<Device> {
45 let s = serialport::new(device_path, 9600)
46 .data_bits(DataBits::Eight)
47 .parity(Parity::None)
48 .stop_bits(StopBits::One)
49 .flow_control(FlowControl::None)
50 .timeout(Duration::from_secs(10));
51
52 let port = s.open()?;
53
54 Ok(Device {
55 device_path: device_path.to_string(),
56 serial_port: port,
57 })
58 }
59
60 pub fn count(&mut self, user: u8) -> Result<u32> {
62 let response = self.send_command(&format!("?MRN{}", user))?;
63
64 if response.len() < 8 {
65 return Err(BpmError::InsufficientData {
66 expected: 8,
67 actual: response.len(),
68 });
69 }
70
71 let count_bytes = &response[5..8];
72 let count_str = std::str::from_utf8(count_bytes)
73 .map_err(|e| BpmError::Parse(format!("Invalid UTF-8 in count: {}", e)))?;
74 let count = count_str.parse::<u32>()
75 .map_err(|e| BpmError::Parse(format!("Failed to parse count: {}", e)))?;
76
77 Ok(count)
78 }
79
80 pub fn observations(&mut self, user: u8) -> Result<Vec<Observation>> {
82 let expected_observation_count = self.count(user)?;
83 let raw_response = self.send_command(&format!("?MDR{}A", user))?;
84
85 if raw_response.len() < 5 {
86 return Err(BpmError::InsufficientData {
87 expected: 5,
88 actual: raw_response.len(),
89 });
90 }
91
92 let mut observations = vec![];
93 let mut rdr = &raw_response[5..];
94
95 for _ in 0..expected_observation_count {
96 if rdr.len() < 20 {
97 break;
98 }
99 match Observation::read(&mut rdr) {
100 Ok(obs) => observations.push(obs),
101 Err(e) => {
102 log::warn!("Error reading observation: {}", e);
103 break;
104 }
105 }
106 }
107
108 Ok(observations)
109 }
110
111 fn read_from_port(&mut self, length: usize) -> Result<Vec<u8>> {
112 let mut buf = vec![0; length];
113 let bytes_read = self.serial_port.read(&mut buf)?;
114 Ok(buf[..bytes_read].to_vec())
115 }
116
117 fn send_command(&mut self, command: &str) -> Result<Vec<u8>> {
118 self.serial_port.write(&[STX])?;
120 self.serial_port.write(command.as_bytes())?;
121 self.serial_port.write(&[ETX])?;
122 sleep(Duration::from_secs(1));
123
124 let ack_response = self.read_from_port(1)?;
126 if ack_response.is_empty() {
127 return Err(BpmError::Communication("No acknowledgement received".to_string()));
128 }
129
130 let acknowledgement = ack_response[0];
131
132 self.serial_port.write(&[ENQ])?;
134 sleep(Duration::from_millis(500));
135
136 if acknowledgement == 6 {
137 let response = self.read_from_port(DATA_BYTES)?;
138 Ok(response)
139 } else {
140 Err(BpmError::Communication(format!(
141 "Negative acknowledgement received: {}",
142 acknowledgement
143 )))
144 }
145 }
146
147 pub fn device_path(&self) -> &str {
149 &self.device_path
150 }
151}
152
153#[derive(Debug, Clone, PartialEq)]
155pub struct Observation {
156 pub year: u16,
157 pub month: u8,
158 pub day: u8,
159 pub hour: u8,
160 pub minute: u8,
161 pub regular_heart_beat: u8,
162 pub systolic: u16,
163 pub diastolic: u16,
164 pub pulse: u16,
165 pub body_movement: u8,
166 pub incorrect_cuff_wrapping: u8,
167 pub unsuitable_temperature: u8,
168 pub usable: u8,
169}
170
171impl Observation {
172 pub fn read<R: Read>(rdr: &mut R) -> Result<Observation> {
174 let mut buf = [0; 2];
175
176 rdr.read_exact(&mut buf)?;
177 let year = std::str::from_utf8(&buf)
178 .map_err(|e| BpmError::Parse(format!("Failed to parse year: {}", e)))?
179 .parse::<u16>()
180 .map_err(|e| BpmError::Parse(format!("Failed to parse year: {}", e)))?;
181
182 rdr.read_exact(&mut buf)?;
183 let month = std::str::from_utf8(&buf)
184 .map_err(|e| BpmError::Parse(format!("Failed to parse month: {}", e)))?
185 .parse::<u8>()
186 .map_err(|e| BpmError::Parse(format!("Failed to parse month: {}", e)))?;
187
188 rdr.read_exact(&mut buf)?;
189 let day = std::str::from_utf8(&buf)
190 .map_err(|e| BpmError::Parse(format!("Failed to parse day: {}", e)))?
191 .parse::<u8>()
192 .map_err(|e| BpmError::Parse(format!("Failed to parse day: {}", e)))?;
193
194 rdr.read_exact(&mut buf)?;
195 let hour = std::str::from_utf8(&buf)
196 .map_err(|e| BpmError::Parse(format!("Failed to parse hour: {}", e)))?
197 .parse::<u8>()
198 .map_err(|e| BpmError::Parse(format!("Failed to parse hour: {}", e)))?;
199
200 rdr.read_exact(&mut buf)?;
201 let minute = std::str::from_utf8(&buf)
202 .map_err(|e| BpmError::Parse(format!("Failed to parse minute: {}", e)))?
203 .parse::<u8>()
204 .map_err(|e| BpmError::Parse(format!("Failed to parse minute: {}", e)))?;
205
206 let mut buf = [0; 1];
207 rdr.read_exact(&mut buf)?;
208 let regular_heart_beat = std::str::from_utf8(&buf)
209 .map_err(|e| BpmError::Parse(format!("Failed to parse regular_heart_beat: {}", e)))?
210 .parse::<u8>()
211 .map_err(|e| BpmError::Parse(format!("Failed to parse regular_heart_beat: {}", e)))?;
212
213 let mut buf = [0; 3];
214 rdr.read_exact(&mut buf)?;
215 let systolic = std::str::from_utf8(&buf)
216 .map_err(|e| BpmError::Parse(format!("Failed to parse systolic: {}", e)))?
217 .parse::<u16>()
218 .map_err(|e| BpmError::Parse(format!("Failed to parse systolic: {}", e)))?;
219
220 rdr.read_exact(&mut buf)?;
221 let diastolic = std::str::from_utf8(&buf)
222 .map_err(|e| BpmError::Parse(format!("Failed to parse diastolic: {}", e)))?
223 .parse::<u16>()
224 .map_err(|e| BpmError::Parse(format!("Failed to parse diastolic: {}", e)))?;
225
226 rdr.read_exact(&mut buf)?;
227 let pulse = std::str::from_utf8(&buf)
228 .map_err(|e| BpmError::Parse(format!("Failed to parse pulse: {}", e)))?
229 .parse::<u16>()
230 .map_err(|e| BpmError::Parse(format!("Failed to parse pulse: {}", e)))?;
231
232 rdr.read_exact(&mut buf[..1])?;
233 let body_movement = std::str::from_utf8(&buf[..1])
234 .map_err(|e| BpmError::Parse(format!("Failed to parse body_movement: {}", e)))?
235 .parse::<u8>()
236 .map_err(|e| BpmError::Parse(format!("Failed to parse body_movement: {}", e)))?;
237
238 rdr.read_exact(&mut buf[..1])?;
239 let incorrect_cuff_wrapping = std::str::from_utf8(&buf[..1])
240 .map_err(|e| BpmError::Parse(format!("Failed to parse incorrect_cuff_wrapping: {}", e)))?
241 .parse::<u8>()
242 .map_err(|e| BpmError::Parse(format!("Failed to parse incorrect_cuff_wrapping: {}", e)))?;
243
244 rdr.read_exact(&mut buf[..1])?;
245 let unsuitable_temperature = std::str::from_utf8(&buf[..1])
246 .map_err(|e| BpmError::Parse(format!("Failed to parse unsuitable_temperature: {}", e)))?
247 .parse::<u8>()
248 .map_err(|e| BpmError::Parse(format!("Failed to parse unsuitable_temperature: {}", e)))?;
249
250 let _ = rdr.read_u8()?; let mut buf = [0; 1];
253 rdr.read_exact(&mut buf)?;
254 let usable = std::str::from_utf8(&buf)
255 .map_err(|e| BpmError::Parse(format!("Failed to parse usable: {}", e)))?
256 .parse::<u8>()
257 .map_err(|e| BpmError::Parse(format!("Failed to parse usable: {}", e)))?;
258
259 Ok(Observation {
260 year,
261 month,
262 day,
263 hour,
264 minute,
265 regular_heart_beat,
266 systolic,
267 diastolic,
268 pulse,
269 body_movement,
270 incorrect_cuff_wrapping,
271 unsuitable_temperature,
272 usable,
273 })
274 }
275
276 pub fn date(&self) -> Option<NaiveDate> {
278 if self.year == 0 || self.month == 0 || self.day == 0 {
279 None
280 } else {
281 NaiveDate::from_ymd_opt(self.year as i32 + 2000, self.month as u32, self.day as u32)
282 }
283 }
284
285 pub fn datetime(&self) -> Option<NaiveDateTime> {
287 if let Some(date) = self.date() {
288 date.and_hms_opt(self.hour as u32, self.minute as u32, 0)
289 } else {
290 None
291 }
292 }
293
294 pub fn is_usable(&self) -> bool {
296 self.usable == 1
297 }
298
299 pub fn has_body_movement(&self) -> bool {
301 self.body_movement == 1
302 }
303
304 pub fn has_incorrect_cuff_wrapping(&self) -> bool {
306 self.incorrect_cuff_wrapping == 1
307 }
308
309 pub fn has_unsuitable_temperature(&self) -> bool {
311 self.unsuitable_temperature == 1
312 }
313
314 pub fn has_regular_heart_beat(&self) -> bool {
316 self.regular_heart_beat == 1
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use chrono::{Datelike, Timelike};
324
325 #[test]
326 fn test_observation_date() {
327 let obs = Observation {
328 year: 23,
329 month: 12,
330 day: 25,
331 hour: 14,
332 minute: 30,
333 regular_heart_beat: 1,
334 systolic: 120,
335 diastolic: 80,
336 pulse: 72,
337 body_movement: 0,
338 incorrect_cuff_wrapping: 0,
339 unsuitable_temperature: 0,
340 usable: 1,
341 };
342
343 let date = obs.date().unwrap();
344 assert_eq!(date.year(), 2023);
345 assert_eq!(date.month(), 12);
346 assert_eq!(date.day(), 25);
347
348 let datetime = obs.datetime().unwrap();
349 assert_eq!(datetime.hour(), 14);
350 assert_eq!(datetime.minute(), 30);
351 }
352
353 #[test]
354 fn test_observation_flags() {
355 let obs = Observation {
356 year: 23,
357 month: 1,
358 day: 1,
359 hour: 0,
360 minute: 0,
361 regular_heart_beat: 1,
362 systolic: 120,
363 diastolic: 80,
364 pulse: 72,
365 body_movement: 1,
366 incorrect_cuff_wrapping: 0,
367 unsuitable_temperature: 1,
368 usable: 1,
369 };
370
371 assert!(obs.is_usable());
372 assert!(obs.has_regular_heart_beat());
373 assert!(obs.has_body_movement());
374 assert!(!obs.has_incorrect_cuff_wrapping());
375 assert!(obs.has_unsuitable_temperature());
376 }
377}