1#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4pub mod error;
34
35use embedded_hal::blocking::{
36 delay::DelayMs,
37 i2c::{Read, Write},
38};
39use error::PacketParseError;
40
41const MICS_VZ_89TE_ADDR: u8 = 0x70;
42
43const MICS_VZ_89TE_ADDR_CMD_GETSTATUS: u8 = 0x0C;
44const MICS_VZ_89TE_DATE_CODE: u8 = 0x0D;
45#[cfg(any(feature = "unproven", test))]
46const MICS_VZ_89TE_GET_CALIBR_VAL: u8 = 0x10;
47#[cfg(any(feature = "unproven", test))]
48const MICS_VZ_89TE_SET_CALIBR_PPM: u8 = 0x08;
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct RevisionDate {
53 pub year: u16,
54 pub month: u8,
55 pub day: u8,
56}
57
58#[cfg(feature = "time")]
59impl TryFrom<time::Date> for RevisionDate {
60 type Error = time::Error;
61
62 fn try_from(d: time::Date) -> Result<Self, Self::Error> {
63 Ok(RevisionDate {
64 year: u16::try_from(d.year())
65 .map_err(|_| time::Error::ConversionRange(time::error::ConversionRange))?,
66 month: u8::from(d.month()),
67 day: u8::from(d.day()),
68 })
69 }
70}
71
72#[cfg(feature = "time")]
73impl TryFrom<RevisionDate> for time::Date {
74 type Error = time::Error;
75
76 fn try_from(rd: RevisionDate) -> Result<Self, Self::Error> {
77 time::Date::from_calendar_date(i32::from(rd.year), time::Month::try_from(rd.month)?, rd.day)
78 .map_err(|e| time::Error::ComponentRange(e))
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
84pub struct Measurements {
85 pub co2: f32,
86 pub voc: f32,
87}
88
89impl Measurements {
90 fn from_response(response: &[u8; 7]) -> Self {
91 let co2 = f32::from(response[1] - 13) * (1600.0 / 229.0) + 400.0; let voc = f32::from(response[0] - 13) * (1000.0 / 229.0); Self { co2, voc }
94 }
95}
96
97pub struct MicsVz89Te<I2C> {
99 i2c: I2C,
100}
101
102impl<I2C, E> MicsVz89Te<I2C>
103where
104 I2C: Read<Error = E> + Write<Error = E>,
105{
106 pub const WAIT_ON_RESPONSE_TIME: u16 = 100;
108
109 pub fn new(i2c: I2C) -> Self {
111 Self { i2c }
112 }
113
114 pub fn read_measurements(
118 &mut self,
119 delay: &mut impl DelayMs<u16>,
120 ) -> Result<Measurements, PacketParseError<E>> {
121 let response =
122 self.request_data(&[MICS_VZ_89TE_ADDR_CMD_GETSTATUS, 0, 0, 0, 0, 0xF3], delay)?;
123 Ok(Measurements::from_response(&response))
124 }
125
126 pub fn start_measurement(&mut self) -> Result<(), PacketParseError<E>> {
140 self.send_request(&[MICS_VZ_89TE_ADDR_CMD_GETSTATUS, 0, 0, 0, 0, 0xF3])
141 }
142
143 pub fn get_measurement_result(&mut self) -> Result<Measurements, PacketParseError<E>> {
145 let response = self.receive_response()?;
146 Ok(Measurements::from_response(&response))
147 }
148
149 pub fn read_revision(
153 &mut self,
154 delay: &mut impl DelayMs<u16>,
155 ) -> Result<RevisionDate, PacketParseError<E>> {
156 let response = self.request_data(&[MICS_VZ_89TE_DATE_CODE, 0, 0, 0, 0, 0xF2], delay)?;
157 let date = RevisionDate {
158 year: u16::from(response[0]) + 2000,
159 month: response[1],
160 day: response[2],
161 };
162 Ok(date)
163 }
164
165 #[cfg(any(feature = "unproven", doc, test))]
166 #[cfg_attr(docsrs, doc(cfg(feature = "unproven")))]
167 pub fn read_calibration_r0(
171 &mut self,
172 delay: &mut impl DelayMs<u16>,
173 ) -> Result<u16, PacketParseError<E>> {
174 let response =
175 self.request_data(&[MICS_VZ_89TE_GET_CALIBR_VAL, 0, 0, 0, 0, 0xEF], delay)?;
176 Ok(u16::from_le_bytes([response[0], response[1]]))
177 }
178
179 #[cfg(any(feature = "unproven", doc, test))]
180 #[cfg_attr(docsrs, doc(cfg(feature = "unproven")))]
181 pub fn write_calibration_ppm(&mut self, ppm: f32) -> Result<(), PacketParseError<E>> {
183 debug_assert!(
184 ppm > 400.0 && ppm < 2000.0,
185 "ppm must be in range from 400 to 2000"
186 );
187 let send_ppm = ((ppm - 400.0) / (1600.0 / 229.0) + 13.0) as u8;
188 let mut cmd_array = [MICS_VZ_89TE_SET_CALIBR_PPM, send_ppm, 0, 0, 0, 0];
189 cmd_array[5] = gen_checksum(&cmd_array[..5]);
190 self.i2c
191 .write(MICS_VZ_89TE_ADDR, &cmd_array)
192 .map_err(PacketParseError::from)
193 }
194
195 fn request_data(
196 &mut self,
197 cmd_buffer: &[u8; 6],
198 delay: &mut impl DelayMs<u16>,
199 ) -> Result<[u8; 7], PacketParseError<E>> {
200 self.send_request(cmd_buffer)?;
201 delay.delay_ms(Self::WAIT_ON_RESPONSE_TIME);
202 self.receive_response()
203 }
204
205 fn send_request(&mut self, cmd_buffer: &[u8; 6]) -> Result<(), PacketParseError<E>> {
206 self.i2c
207 .write(MICS_VZ_89TE_ADDR, cmd_buffer)
208 .map_err(PacketParseError::from)
209 }
210
211 fn receive_response(&mut self) -> Result<[u8; 7], PacketParseError<E>> {
212 let mut buffer = [0u8; 7];
213 self.i2c.read(MICS_VZ_89TE_ADDR, &mut buffer)?;
214
215 let check = gen_checksum(&buffer[..5]);
216 if buffer[6].ne(&check) {
217 return Err(PacketParseError::WrongChecksum);
218 }
219
220 Ok(buffer)
221 }
222}
223
224impl<I2C> MicsVz89Te<I2C> {
225 pub fn release(self) -> I2C {
235 self.i2c
236 }
237}
238
239fn gen_checksum(byte_array: &[u8]) -> u8 {
240 let sum = byte_array.iter().fold(0u16, |a, v| a + (*v as u16));
241 0xFF - (sum as u8 + (sum / 0x0100) as u8)
242}
243
244#[cfg(test)]
245mod test {
246
247 use crate::{error::PacketParseError, RevisionDate};
248
249 use super::MicsVz89Te;
250 use assert_matches::assert_matches;
251 use core::assert_eq;
252 use embedded_hal_mock::{
253 delay::MockNoop as DelayMock,
254 i2c::{Mock as I2cMock, Transaction as I2cTransaction},
255 };
256 use std::vec;
257
258 #[test]
259 fn test_read_measurements() {
260 let expectations = [
261 I2cTransaction::write(0x70, vec![0x0C, 0, 0, 0, 0, 0xF3]),
262 I2cTransaction::read(0x70, vec![0x27, 0x3C, 0, 0xBA, 0xBA, 0, 0x27]),
263 ];
264 let i2c = I2cMock::new(&expectations);
265 let mut delay = DelayMock::new();
266
267 let mut device = MicsVz89Te::new(i2c);
268 let measurements = device.read_measurements(&mut delay);
269
270 assert!(measurements.is_ok());
271 let measurements = measurements.unwrap();
272
273 assert_eq!(measurements.co2 as u32, 728);
274 assert_eq!(measurements.voc as u32, 113);
275 }
276
277 #[test]
278 fn test_read_measurements_wrong_checksum() {
279 let expectations = [
280 I2cTransaction::write(0x70, vec![0x0C, 0, 0, 0, 0, 0xF3]),
281 I2cTransaction::read(0x70, vec![0x27, 0x3C, 0, 0xBA, 0xBA, 0, 0x26]),
282 ];
283 let i2c = I2cMock::new(&expectations);
284 let mut delay = DelayMock::new();
285
286 let mut device = MicsVz89Te::new(i2c);
287 let measurements = device.read_measurements(&mut delay);
288
289 assert_matches!(measurements, Err(PacketParseError::WrongChecksum));
290 }
291
292 #[test]
293 fn test_read_revision_date() {
294 let expectations = [
295 I2cTransaction::write(0x70, vec![0x0D, 0, 0, 0, 0, 0xF2]),
296 I2cTransaction::read(0x70, vec![0x10, 0x03, 0x11, 0x48, 00, 0, 0x93]),
297 ];
298 let i2c = I2cMock::new(&expectations);
299 let mut delay = DelayMock::new();
300
301 let mut device = MicsVz89Te::new(i2c);
302 let revision = device.read_revision(&mut delay);
303
304 assert_matches!(
305 revision, Ok(r) if r == RevisionDate { year: 2016, month: 3, day: 17 }
306 );
307 }
308
309 #[test]
310 fn test_write_calibration_ppm() {
311 let expectations = [I2cTransaction::write(0x70, vec![0x08, 0x62, 0, 0, 0, 0x95])];
312 let i2c = I2cMock::new(&expectations);
313
314 let mut device = MicsVz89Te::new(i2c);
315 let res = device.write_calibration_ppm(1000.0);
316
317 assert!(res.is_ok());
318 }
319
320 #[test]
321 fn test_read_calibration_r0() {
322 let expectations = [
323 I2cTransaction::write(0x70, vec![0x10, 0, 0, 0, 0, 0xEF]),
324 I2cTransaction::read(0x70, vec![0xFB, 0x01, 0, 0, 0, 0, 0x03]),
325 ];
326 let i2c = I2cMock::new(&expectations);
327 let mut delay = DelayMock::new();
328
329 let mut device = MicsVz89Te::new(i2c);
330 let value = device.read_calibration_r0(&mut delay);
331
332 assert_matches!(value, Ok(v) if v == 507);
333 }
334}