1#![cfg_attr(not(feature = "std"), no_std)]
15
16#[cfg(not(feature = "std"))]
17use core::fmt;
18#[cfg(feature = "std")]
19use std::fmt;
20
21enum Command {
23 ReadGasConcentration,
25 CalibrateZero,
29 CalibrateSpan,
31 SetAutomaticBaselineCorrection,
33 SetSensorDetectionRange,
35}
36
37impl Command {
38 fn get_command_value(&self) -> u8 {
39 use Command::*;
40 match self {
41 ReadGasConcentration => 0x86,
42 CalibrateZero => 0x87,
43 CalibrateSpan => 0x88,
44 SetAutomaticBaselineCorrection => 0x79,
45 SetSensorDetectionRange => 0x99,
46 }
47 }
48}
49
50pub type Packet = [u8; 9];
52
53fn get_command_with_bytes34(command: Command, device_number: u8, byte3: u8, byte4: u8) -> Packet {
55 let mut ret: Packet = [
56 0xFF,
57 device_number,
58 command.get_command_value(),
59 byte3,
60 byte4,
61 0x00,
62 0x00,
63 0x00,
64 0x00,
65 ];
66 ret[8] = checksum(&ret[1..8]);
67 ret
68}
69
70pub fn read_gas_concentration(device_number: u8) -> Packet {
72 get_command_with_bytes34(Command::ReadGasConcentration, device_number, 0x00, 0x00)
73}
74
75pub fn set_automatic_baseline_correction(device_number: u8, enabled: bool) -> Packet {
77 get_command_with_bytes34(
78 Command::SetAutomaticBaselineCorrection,
79 device_number,
80 if enabled { 0xA0 } else { 0x00 },
81 0x00,
82 )
83}
84
85pub fn calibrate_span_point(device_number: u8, value: u16) -> Packet {
92 get_command_with_bytes34(
93 Command::CalibrateSpan,
94 device_number,
95 ((value & 0xff00) >> 8) as u8,
96 (value & 0xff) as u8,
97 )
98}
99
100pub fn set_detection_range(device_number: u8, value: u16) -> Packet {
104 get_command_with_bytes34(
105 Command::SetSensorDetectionRange,
106 device_number,
107 ((value & 0xff00) >> 8) as u8,
108 (value & 0xff) as u8,
109 )
110}
111
112pub fn calibrate_zero_point(device_number: u8) -> Packet {
117 get_command_with_bytes34(Command::CalibrateZero, device_number, 0x00, 0x00)
118}
119
120fn checksum(payload: &[u8]) -> u8 {
122 1u8.wrapping_add(0xff - payload.iter().fold(0u8, |sum, c| sum.wrapping_add(*c)))
123}
124
125pub fn parse_payload(packet: &[u8]) -> Result<&[u8], MHZ19Error> {
127 use MHZ19Error::*;
128 if packet.len() != 9 {
129 return Err(WrongPacketLength(packet.len()));
130 }
131 let header = packet[0];
132 if header != 0xFF {
133 return Err(WrongStartByte(header));
134 }
135 let payload = &packet[1..8];
136 let found_checksum = packet[8];
137 let payload_checksum = checksum(payload);
138 if found_checksum != payload_checksum {
139 return Err(WrongChecksum(payload_checksum, found_checksum));
140 }
141
142 Ok(payload)
143}
144
145pub fn parse_gas_concentration_ppm(packet: &[u8]) -> Result<u32, MHZ19Error> {
149 let payload = parse_payload(packet)?;
150 if payload[0] != Command::ReadGasConcentration.get_command_value() {
151 Err(MHZ19Error::WrongPacketType(
152 Command::ReadGasConcentration.get_command_value(),
153 payload[0],
154 ))
155 } else {
156 Ok(256 * (payload[1] as u32) + (payload[2] as u32))
157 }
158}
159
160#[deprecated = "Please use `parse_gas_concentration_ppm` instead"]
164pub fn parse_gas_contentration_ppm(packet: &[u8]) -> Result<u32, MHZ19Error> {
165 parse_gas_concentration_ppm(packet)
166}
167
168#[derive(Debug, PartialEq)]
169pub enum MHZ19Error {
170 WrongPacketLength(usize),
172 WrongChecksum(u8, u8),
174 WrongStartByte(u8),
176 WrongPacketType(u8, u8),
178}
179
180#[cfg(feature = "std")]
181impl std::error::Error for MHZ19Error {}
182
183impl fmt::Display for MHZ19Error {
184 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185 use MHZ19Error::*;
186 match self {
187 WrongChecksum(expected, found) => write!(
188 f,
189 "Invalid checksum, expected {:X}, found {:X}",
190 expected, found
191 ),
192 WrongPacketLength(found) => {
193 write!(f, "Wrong packet length, expected 9, found {}", found)
194 }
195 WrongStartByte(found) => {
196 write!(f, "Wrong start byte, expected 0xFF, found {:X}", found)
197 }
198 WrongPacketType(expected, found) => write!(
199 f,
200 "Wrong packet type, expected {}, found {:X}",
201 expected, found
202 ),
203 }
204 }
205}
206
207#[cfg(test)]
208mod test {
209 use super::*;
210
211 static READ_GAS_CONCENTRATION_COMMAND_ON_DEV1_PACKET: &'static [u8] =
217 &[0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79];
218
219 #[test]
220 fn test_checksum() {
221 assert_eq!(0x79, checksum(&[0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00]));
222 assert_eq!(0xA0, checksum(&[0x01, 0x88, 0x07, 0xD0, 0x00, 0x00, 0x00]));
223 assert_eq!(0xD1, checksum(&[0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00]));
224 }
225
226 #[test]
227 fn test_get_payload() {
228 assert_eq!(Err(MHZ19Error::WrongPacketLength(0)), parse_payload(&[]));
229 assert_eq!(Err(MHZ19Error::WrongPacketLength(1)), parse_payload(&[12]));
230 assert_eq!(
231 Err(MHZ19Error::WrongPacketLength(12)),
232 parse_payload(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
233 );
234 assert_eq!(
235 Err(MHZ19Error::WrongStartByte(10)),
236 parse_payload(&[10, 2, 3, 4, 5, 6, 7, 8, 9])
237 );
238 assert_eq!(
239 Err(MHZ19Error::WrongChecksum(221, 9)),
240 parse_payload(&[0xFF, 2, 3, 4, 5, 6, 7, 8, 9])
241 );
242 assert_eq!(
243 Err(MHZ19Error::WrongChecksum(0xD1, 0x10)),
244 parse_payload(&[0xFF, 0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00, 0x10])
245 );
246 assert_eq!(
247 Ok(&[0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00][..]),
248 parse_payload(&[0xFF, 0x86, 0x02, 0x60, 0x47, 0x00, 0x00, 0x00, 0xD1])
249 );
250 }
251
252 #[test]
253 fn test_get_command_packet() {
254 assert_eq!(
255 [0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79],
256 get_command_with_bytes34(Command::ReadGasConcentration, 1, 0, 0)
257 );
258 assert_eq!(
259 Ok(&[0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00][..]),
260 parse_payload(&get_command_with_bytes34(
261 Command::ReadGasConcentration,
262 1,
263 0,
264 0
265 ))
266 );
267 assert_eq!(
268 READ_GAS_CONCENTRATION_COMMAND_ON_DEV1_PACKET,
269 get_command_with_bytes34(Command::ReadGasConcentration, 1, 0, 0)
270 );
271 assert_eq!(
272 READ_GAS_CONCENTRATION_COMMAND_ON_DEV1_PACKET,
273 read_gas_concentration(1)
274 );
275
276 assert_eq!(
278 super::Command::SetSensorDetectionRange.get_command_value(),
279 set_detection_range(1, 1)[2]
280 );
281 assert_eq!(
282 super::Command::CalibrateZero.get_command_value(),
283 calibrate_zero_point(1)[2]
284 );
285 assert_eq!(
286 super::Command::CalibrateSpan.get_command_value(),
287 calibrate_span_point(1, 1)[2]
288 );
289 assert_eq!(
290 super::Command::ReadGasConcentration.get_command_value(),
291 read_gas_concentration(1)[2]
292 );
293 }
294
295 #[test]
296 fn issue_3_op_precedence() {
297 let p = set_detection_range(1, 0x07D0);
298 assert_eq!(0x07, p[3]);
299 assert_eq!(0xD0, p[4]);
300
301 let p = calibrate_span_point(1, 0x07D0);
302 assert_eq!(0x07, p[3]);
303 assert_eq!(0xD0, p[4]);
304 }
305}