Skip to main content

usb_bpm_exporter/
lib.rs

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
36/// Represents a USB Blood Pressure Monitor device
37pub struct Device {
38    device_path: String,
39    serial_port: Box<dyn SerialPort>,
40}
41
42impl Device {
43    /// Create a new device connection
44    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    /// Get the number of observations for a specific user
61    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    /// Get all observations for a specific user
81    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        // Send command wrapped in STX/ETX
119        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        // Read acknowledgement
125        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        // Send ENQ to request data
133        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    /// Get the device path
148    pub fn device_path(&self) -> &str {
149        &self.device_path
150    }
151}
152
153/// Represents a blood pressure observation/measurement
154#[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    /// Read an observation from binary data
173    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()?; // Skip padding byte
251        
252        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    /// Get the date of this observation
277    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    /// Get the datetime of this observation
286    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    /// Check if this observation is marked as usable
295    pub fn is_usable(&self) -> bool {
296        self.usable == 1
297    }
298
299    /// Check if there was body movement during measurement
300    pub fn has_body_movement(&self) -> bool {
301        self.body_movement == 1
302    }
303
304    /// Check if cuff wrapping was incorrect
305    pub fn has_incorrect_cuff_wrapping(&self) -> bool {
306        self.incorrect_cuff_wrapping == 1
307    }
308
309    /// Check if temperature was unsuitable
310    pub fn has_unsuitable_temperature(&self) -> bool {
311        self.unsuitable_temperature == 1
312    }
313
314    /// Check if heart beat was regular
315    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}