#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
use core::fmt;
#[cfg(feature = "std")]
use std::fmt;
enum Command {
ReadGasConcentration,
CalibrateZero,
CalibrateSpan,
SetAutomaticBaselineCorrection,
SetSensorDetectionRange,
}
impl Command {
fn get_command_value(&self) -> u8 {
use Command::*;
match self {
ReadGasConcentration => 0x86,
CalibrateZero => 0x87,
CalibrateSpan => 0x88,
SetAutomaticBaselineCorrection => 0x79,
SetSensorDetectionRange => 0x99,
}
}
}
pub type Packet = [u8; 9];
fn get_command_with_bytes34(command: Command, device_number: u8, byte3: u8, byte4: u8) -> Packet {
let mut ret: Packet = [
0xFF,
device_number,
command.get_command_value(),
byte3,
byte4,
0x00,
0x00,
0x00,
0x00,
];
ret[8] = checksum(&ret[1..8]);
ret
}
pub fn read_gas_concentration(device_number: u8) -> Packet {
get_command_with_bytes34(Command::ReadGasConcentration, device_number, 0x00, 0x00)
}
pub fn set_automatic_baseline_correction(device_number: u8, enabled: bool) -> Packet {
get_command_with_bytes34(
Command::SetAutomaticBaselineCorrection,
device_number,
if enabled { 0xA0 } else { 0x00 },
0x00,
)
}
pub fn calibrate_span_point(device_number: u8, value: u16) -> Packet {
get_command_with_bytes34(
Command::CalibrateSpan,
device_number,
((value & 0xff00) >> 8) as u8,
(value & 0xff) as u8,
)
}
pub fn set_detection_range(device_number: u8, value: u16) -> Packet {
get_command_with_bytes34(
Command::SetSensorDetectionRange,
device_number,
((value & 0xff00) >> 8) as u8,
(value & 0xff) as u8,
)
}
pub fn calibrate_zero_point(device_number: u8) -> Packet {
get_command_with_bytes34(Command::CalibrateZero, device_number, 0x00, 0x00)
}
fn checksum(payload: &[u8]) -> u8 {
1u8.wrapping_add(0xff - payload.iter().fold(0u8, |sum, c| sum.wrapping_add(*c)))
}
pub fn parse_payload(packet: &[u8]) -> Result<&[u8], MHZ19Error> {
use MHZ19Error::*;
if packet.len() != 9 {
return Err(WrongPacketLength(packet.len()));
}
let header = packet[0];
if header != 0xFF {
return Err(WrongStartByte(header));
}
let payload = &packet[1..8];
let found_checksum = packet[8];
let payload_checksum = checksum(payload);
if found_checksum != payload_checksum {
return Err(WrongChecksum(payload_checksum, found_checksum));
}
Ok(payload)
}
pub fn parse_gas_concentration_ppm(packet: &[u8]) -> Result<u32, MHZ19Error> {
let payload = parse_payload(packet)?;
if payload[0] != Command::ReadGasConcentration.get_command_value() {
Err(MHZ19Error::WrongPacketType(
Command::ReadGasConcentration.get_command_value(),
payload[0],
))
} else {
Ok(256 * (payload[1] as u32) + (payload[2] as u32))
}
}
#[deprecated = "Please use `parse_gas_concentration_ppm` instead"]
pub fn parse_gas_contentration_ppm(packet: &[u8]) -> Result<u32, MHZ19Error> {
parse_gas_concentration_ppm(packet)
}
#[derive(Debug, PartialEq)]
pub enum MHZ19Error {
WrongPacketLength(usize),
WrongChecksum(u8, u8),
WrongStartByte(u8),
WrongPacketType(u8, u8),
}
#[cfg(feature = "std")]
impl std::error::Error for MHZ19Error {}
impl fmt::Display for MHZ19Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use MHZ19Error::*;
match self {
WrongChecksum(expected, found) => write!(
f,
"Invalid checksum, expected {:X}, found {:X}",
expected, found
),
WrongPacketLength(found) => {
write!(f, "Wrong packet length, expected 9, found {}", found)
}
WrongStartByte(found) => {
write!(f, "Wrong start byte, expected 0xFF, found {:X}", found)
}
WrongPacketType(expected, found) => write!(
f,
"Wrong packet type, expected {}, found {:X}",
expected, found
),
}
}
}
#[cfg(test)]
mod test {
use super::*;
static READ_GAS_CONCENTRATION_COMMAND_ON_DEV1_PACKET: &'static [u8] =
&[0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79];
#[test]
fn test_checksum() {
assert_eq!(0x79, checksum(&[0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00]));
assert_eq!(0xA0, checksum(&[0x01, 0x88, 0x07, 0xD0, 0x00, 0x00, 0x00]));
assert_eq!(0xD1, checksum(&[0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00]));
}
#[test]
fn test_get_payload() {
assert_eq!(Err(MHZ19Error::WrongPacketLength(0)), parse_payload(&[]));
assert_eq!(Err(MHZ19Error::WrongPacketLength(1)), parse_payload(&[12]));
assert_eq!(
Err(MHZ19Error::WrongPacketLength(12)),
parse_payload(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
);
assert_eq!(
Err(MHZ19Error::WrongStartByte(10)),
parse_payload(&[10, 2, 3, 4, 5, 6, 7, 8, 9])
);
assert_eq!(
Err(MHZ19Error::WrongChecksum(221, 9)),
parse_payload(&[0xFF, 2, 3, 4, 5, 6, 7, 8, 9])
);
assert_eq!(
Err(MHZ19Error::WrongChecksum(0xD1, 0x10)),
parse_payload(&[0xFF, 0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00, 0x10])
);
assert_eq!(
Ok(&[0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00][..]),
parse_payload(&[0xFF, 0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00, 0xD1])
);
}
#[test]
fn test_get_command_packet() {
assert_eq!(
[0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79],
get_command_with_bytes34(Command::ReadGasConcentration, 1, 0, 0)
);
assert_eq!(
Ok(&[0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00][..]),
parse_payload(&get_command_with_bytes34(
Command::ReadGasConcentration,
1,
0,
0
))
);
assert_eq!(
READ_GAS_CONCENTRATION_COMMAND_ON_DEV1_PACKET,
get_command_with_bytes34(Command::ReadGasConcentration, 1, 0, 0)
);
assert_eq!(
READ_GAS_CONCENTRATION_COMMAND_ON_DEV1_PACKET,
read_gas_concentration(1)
);
assert_eq!(
super::Command::SetSensorDetectionRange.get_command_value(),
set_detection_range(1, 1)[2]
);
assert_eq!(
super::Command::CalibrateZero.get_command_value(),
calibrate_zero_point(1)[2]
);
assert_eq!(
super::Command::CalibrateSpan.get_command_value(),
calibrate_span_point(1, 1)[2]
);
assert_eq!(
super::Command::ReadGasConcentration.get_command_value(),
read_gas_concentration(1)[2]
);
}
#[test]
fn issue_3_op_precedence() {
let p = set_detection_range(1, 0x07D0);
assert_eq!(0x07, p[3]);
assert_eq!(0xD0, p[4]);
let p = calibrate_span_point(1, 0x07D0);
assert_eq!(0x07, p[3]);
assert_eq!(0xD0, p[4]);
}
}