sensor_scd30/
base.rs

1//! Base communication implementation for interacting with Scd30 device
2//!
3//! Copyright 2019 Ryan Kurte
4
5use core::fmt::Debug;
6
7use embedded_hal::i2c;
8use log::trace;
9
10use crate::device::*;
11use crate::Error;
12
13/// Base API for reading and writing to the device
14/// This should not be required by consumers, but is exposed to support alternate use (or in future provide ModBus support)
15pub trait Base<Err> {
16    /// Write a command to the device with optional data
17    fn write_command(&mut self, command: Command, data: Option<u16>) -> Result<(), Error<Err>>;
18    /// Read information from the device
19    fn read_command(&mut self, command: Command, data: &mut [u8]) -> Result<(), Error<Err>>;
20}
21
22/// Helper for device CRC-8 calculation
23pub fn crc8(data: &[u8]) -> u8 {
24    let mut crc = CRC_INIT;
25
26    // For each byte
27    for v in data {
28        // XOR with current byte
29        crc ^= v;
30
31        // For each bit (in -ve order, but, doesn't actually matter here)
32        for _bit in 0..8 {
33            if crc & 0x80 != 0 {
34                crc = (crc << 1) ^ CRC_POLY;
35            } else {
36                crc = crc << 1;
37            }
38        }
39    }
40
41    // Apply final xor
42    crc ^ CRC_XOR
43}
44
45/// Base implementation for I2C devices
46impl<Conn, Err> Base<Err> for Conn
47where
48    Conn: i2c::I2c<Error = Err>,
49    Err: Debug,
50{
51    fn write_command(&mut self, command: Command, data: Option<u16>) -> Result<(), Error<Err>> {
52        let c = command as u16;
53
54        let mut buff: [u8; 5] = [(c >> 8) as u8, (c & 0xFF) as u8, 0, 0, 0];
55
56        let len = match data {
57            Some(d) => {
58                buff[2] = (d >> 8) as u8;
59                buff[3] = (d & 0xFF) as u8;
60                buff[4] = crc8(&buff[2..4]);
61                5
62            }
63            None => 2,
64        };
65
66        trace!("Writing command: {:?} data: {:?}", c, data);
67
68        self.write(DEFAULT_ADDRESS | I2C_WRITE_FLAG, &buff[..len])
69            .map_err(|e| Error::Conn(e))
70    }
71
72    fn read_command(&mut self, command: Command, data: &mut [u8]) -> Result<(), Error<Err>> {
73        // Write command to initialise read
74        let c = command as u16;
75        let cmd = [(c >> 8) as u8, (c & 0xFF) as u8];
76
77        trace!("Writing command: {:x?}", cmd);
78
79        // First write the read command
80        self.write(DEFAULT_ADDRESS | I2C_WRITE_FLAG, &cmd)
81            .map_err(|e| Error::Conn(e))?;
82
83        // Then, read the data back
84        self.read(DEFAULT_ADDRESS | I2C_READ_FLAG, data)
85            .map_err(|e| Error::Conn(e))?;
86
87        // Note: this two-phase approach is specified in the datasheet
88
89        trace!("Read data: {:x?}", data);
90
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use super::*;
98
99    #[test]
100    fn test_crc() {
101        // Test vectors from datasheet
102        let tests = &[
103            ([0xbe, 0xef], 0x92),
104            ([0x00, 0x00], 0x81),
105            ([0x43, 0xDB], 0xCB),
106        ];
107
108        for t in tests {
109            let v = crc8(&t.0);
110            assert_eq!(v, t.1);
111        }
112    }
113}