use serial::{SystemPort, open as serial_open};
use serial_core::{BaudRate, CharSize, Error as SerialError, ErrorKind as SerialErrorKind, FlowControl, Parity, Result as SerialResult, SerialPort, StopBits};
use std::error::Error;
use std::fmt;
use std::io;
use std::io::Read;
use std::path::Path;
use std::time::Duration;
const BAUD_RATE: BaudRate = BaudRate::Baud9600;
const CHAR_SIZE: CharSize = CharSize::Bits8;
const PARITY: Parity = Parity::ParityNone;
const STOP_BITS: StopBits = StopBits::Stop1;
const FLOW_CONTROL: FlowControl = FlowControl::FlowNone;
const MAGIC_BYTE_0: u8 = 0x42;
const MAGIC_BYTE_1: u8 = 0x4d;
const PAYLOAD_LEN: usize = 30;
#[derive(Debug, Clone, Copy)]
pub struct Reading {
pm1: f64,
pm2_5: f64,
pm10: f64,
}
impl Reading {
pub fn pm1(&self) -> f64 {
self.pm1
}
pub fn pm2_5(&self) -> f64 {
self.pm2_5
}
pub fn pm10(&self) -> f64 {
self.pm10
}
}
#[derive(Debug)]
pub enum Sen0177Error {
DeviceNotFound,
DeviceUnavailable,
InvalidData(String),
ChecksumMismatch,
IoError(io::Error),
}
impl fmt::Display for Sen0177Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Sen0177Error::*;
match self {
DeviceNotFound => write!(f, "Device not found"),
DeviceUnavailable => write!(f, "Device unavailable or does not support required parameters"),
InvalidData(reason) => write!(f, "Invalid data: {}", reason),
ChecksumMismatch => write!(f, "Data read was corrupt"),
IoError(err) => write!(f, "IO error: {}", err),
}
}
}
impl Error for Sen0177Error {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Sen0177Error::IoError(ioerr) => Some(ioerr),
_ => None,
}
}
}
impl From<SerialError> for Sen0177Error {
fn from(err: SerialError) -> Self {
use Sen0177Error::*;
match err.kind() {
SerialErrorKind::NoDevice => DeviceNotFound,
SerialErrorKind::InvalidInput => DeviceUnavailable,
SerialErrorKind::Io(kind) => IoError(io::Error::new(kind, format!("{:?}", kind))),
}
}
}
impl From<io::Error> for Sen0177Error {
fn from(err: io::Error) -> Self {
Sen0177Error::IoError(err)
}
}
pub struct Sen0177 {
serial_port: SystemPort,
}
impl Sen0177 {
pub fn open<P: AsRef<Path>>(serial_dev: P) -> Result<Sen0177, Sen0177Error> {
let mut serial_port = serial_open(serial_dev.as_ref())?;
Sen0177::configure_serial_port(&mut serial_port)?;
Ok(Sen0177 {
serial_port,
})
}
pub fn read(&mut self) -> Result<Reading, Sen0177Error> {
let mut attempts_left = 10;
let mut buf: [u8; 1] = [0; 1];
while buf[0] != MAGIC_BYTE_1 && attempts_left > 0 && find_byte(&mut self.serial_port, MAGIC_BYTE_0, PAYLOAD_LEN as u32 * 4)? {
self.serial_port.read_exact(&mut buf)?;
attempts_left -= 1;
}
if buf[0] == MAGIC_BYTE_1 {
let mut buf: [u8; PAYLOAD_LEN] = [0; PAYLOAD_LEN];
self.serial_port.read_exact(&mut buf)?;
validate_checksum(&buf)?;
Ok(Reading {
pm1: (((buf[2] as u16) << 8) | (buf[3] as u16)) as f64,
pm2_5: (((buf[4] as u16) << 8) | (buf[5] as u16)) as f64,
pm10: (((buf[6] as u16) << 8) | (buf[7] as u16)) as f64,
})
} else {
Err(Sen0177Error::InvalidData(format!("Unable to find start magic 0x{:2x}{:2x}", MAGIC_BYTE_0, MAGIC_BYTE_1)))
}
}
fn configure_serial_port(serial_port: &mut SystemPort) -> SerialResult<()> {
serial_port.set_timeout(Duration::from_millis(1500))?;
serial_port.reconfigure(&|settings| {
settings.set_char_size(CHAR_SIZE);
settings.set_parity(PARITY);
settings.set_stop_bits(STOP_BITS);
settings.set_flow_control(FLOW_CONTROL);
settings.set_baud_rate(BAUD_RATE)
})
}
}
fn find_byte(serial_port: &mut SystemPort, byte: u8, attempts: u32) -> io::Result<bool> {
let mut attempts_left = attempts;
let mut buf: [u8; 1] = [0; 1];
while buf[0] != byte && attempts_left > 0 {
serial_port.read_exact(&mut buf)?;
attempts_left -= 1;
}
Ok(buf[0] == byte)
}
fn validate_checksum(buf: &[u8; PAYLOAD_LEN]) -> Result<(), Sen0177Error> {
let init: u16 = (MAGIC_BYTE_0 as u16) + (MAGIC_BYTE_1 as u16);
let sum = buf[0..PAYLOAD_LEN-2].iter().fold(init, |accum, next| accum + *next as u16);
let expected_sum: u16 = ((buf[PAYLOAD_LEN-2] as u16) << 8) | (buf[PAYLOAD_LEN-1] as u16);
if expected_sum == sum {
Ok(())
} else {
Err(Sen0177Error::ChecksumMismatch)
}
}