1#![no_std]
2#![feature(int_roundings)]
3use core::fmt::{Debug, Display};
4use core::marker::PhantomData;
5#[cfg(feature = "crc")]
6use crc::{Crc, CRC_8_NRSC_5};
7use embedded_hal_async::delay::DelayNs;
8use embedded_hal_async::i2c::I2c;
9
10pub const I2C_ADDRESS: u8 = 0x38;
11
12pub const I2C_TRIGGER_MEASURE: u8 = 0xAC;
13
14pub const MEASURE_TIME_MS: u32 = 80;
15
16#[derive(Debug, Copy, Clone, Eq, PartialEq)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub struct HumidityData(u32);
19
20impl HumidityData {
21 pub fn from_bytes(bytes: [u8; 3]) -> Self {
22 let mut me = Self(0);
24 me.0 = bytes[0] as u32;
25 me.0 <<= 8;
26 me.0 |= bytes[1] as u32;
27 me.0 <<= 8;
28 me.0 |= (bytes[2] & 0b11110000u8) as u32;
29 me.0 >>= 4;
30 me
31 }
32 pub fn relative_mille(&self) -> u32 {
33 ((self.0 as u64 * 1000) / (2 << 20)) as _
34 }
35 pub fn relative(&self) -> (u16, u16) {
36 let mille = self.relative_mille();
37 let div = 10;
38 let percent = mille.div_floor(div);
39 let frac = mille % div;
40 (percent as _, frac as _)
41 }
42}
43
44impl Display for HumidityData {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 let (percent, frac) = self.relative();
47 write!(f, "{}.{}%", percent, frac)
48 }
49}
50
51#[derive(Debug, Copy, Clone, Eq, PartialEq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53pub struct TemperatureData(u32);
54impl TemperatureData {
55 pub fn from_bytes(bytes: [u8; 3]) -> Self {
56 let mut me = Self(0);
58 me.0 = (bytes[0] & 0b00001111u8) as u32;
59 me.0 <<= 8;
60 me.0 |= bytes[1] as u32;
61 me.0 <<= 8;
62 me.0 |= bytes[2] as u32;
63 me
64 }
65 pub fn milli_celsius(&self) -> i32 {
66 ((self.0 as i64 * 200 * 1000) / ((1u64 << 20) as i64) - 50 * 1000) as _
67 }
68 pub fn celsius(&self) -> (i16, u16) {
69 let milli = self.milli_celsius();
70 let int = milli.div_floor(1000);
71 let frac = milli % 1000;
72 (int as _, frac as _)
73 }
74}
75
76impl Display for TemperatureData {
77 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78 let (int, frac) = self.celsius();
79 write!(f, "{}.{}°C", int, frac)
80 }
81}
82
83#[repr(u8)]
84enum StatusBits {
85 Busy = 0x01,
86 Calibrated = 0x10,
87 CalibrationOn = 0x08,
88 FifoOn = 0x04,
89 FifoFull = 0x02,
90}
91
92#[derive(Copy, Clone, Eq, PartialEq)]
93#[cfg_attr(feature = "defmt", derive(defmt::Format))]
94pub struct Status(u8);
95
96impl Status {
97 pub fn is_busy(&self) -> bool {
98 self.0 & (StatusBits::Busy as u8) > 0
99 }
100 pub fn is_calibrated(&self) -> bool {
101 self.0 & (StatusBits::Calibrated as u8) > 0
102 }
103 pub fn is_calibration_on(&self) -> bool {
104 self.0 & (StatusBits::CalibrationOn as u8) > 0
105 }
106 pub fn is_fifo_on(&self) -> bool {
107 self.0 & (StatusBits::FifoOn as u8) > 0
108 }
109 pub fn is_fido_full(&self) -> bool {
110 self.0 & (StatusBits::FifoFull as u8) > 0
111 }
112}
113
114impl Debug for Status {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 f.debug_struct("Status")
117 .field("busy", &self.is_busy())
118 .field("calibrated", &self.is_calibrated())
119 .field("calibration on", &self.is_calibration_on())
120 .field("fifo on", &self.is_fifo_on())
121 .field("fifo full", &self.is_fido_full())
122 .finish()
123 }
124}
125
126#[derive(Debug, Copy, Clone)]
127#[repr(u8)]
128pub enum Aht2xCommand {
129 Init = 0xBE,
130 Status = 0x71,
131 Measure = 0xAC,
132 Reset = 0xBA,
133}
134
135pub struct Aht2X<T> {
136 _marker: PhantomData<T>,
137}
138
139impl<T: I2c> Aht2X<T> {
140 async fn write(
141 &mut self,
142 interface: &mut T,
143 cmd: Aht2xCommand,
144 bytes: &[u8],
145 ) -> Result<(), T::Error> {
146 let mut payload = [0u8; 12];
147 payload[0] = cmd as _;
148 payload[1..(bytes.len() + 1)].copy_from_slice(bytes);
149 #[cfg(feature = "defmt")]
150 defmt::debug!("cmd: {:?} {:?}", cmd, bytes);
151 let res = interface
152 .write(I2C_ADDRESS, &payload[..(bytes.len() + 1)])
153 .await;
154 res
155 }
156
157 async fn read(&mut self, interface: &mut T, bytes: &mut [u8]) -> Result<(), T::Error> {
158 let res = interface.read(I2C_ADDRESS, bytes).await;
159 #[cfg(feature = "defmt")]
160 defmt::debug!("read {:?}", bytes);
161 res
162 }
163
164 pub async fn setup<'a>(interface: &mut T, delay: &mut impl DelayNs) -> Result<Self, T::Error> {
165 let mut me = Self {
166 _marker: Default::default(),
167 };
168 delay.delay_ms(100).await;
169 me.write(interface, Aht2xCommand::Reset, &[]).await?;
170 delay.delay_ms(100).await;
171 me.write(interface, Aht2xCommand::Init, &[0b10101100, 0x00])
172 .await?;
173 while me.status(interface).await?.is_busy() {
174 delay.delay_ms(100).await;
175 }
176 let status = me.status(interface).await?;
177 #[cfg(feature = "defmt")]
178 defmt::debug!("setup status: {:?}", status);
179 if !status.is_calibrated() {
180 #[cfg(feature = "defmt")]
181 defmt::warn!("calibration failed!");
182 }
183 Ok(me)
184 }
185
186 pub async fn status<'a>(&mut self, interface: &mut T) -> Result<Status, T::Error> {
187 self.write(interface, Aht2xCommand::Status, &[]).await?;
188 let mut status = Status(0);
189 self.read(interface, core::slice::from_mut(&mut status.0))
190 .await?;
191 Ok(status)
192 }
193 pub async fn measure<'a>(
194 &mut self,
195 interface: &mut T,
196 delay: &mut impl DelayNs,
197 ) -> Result<(HumidityData, TemperatureData), T::Error> {
198 delay.delay_ms(100).await;
199 let mut resp = [0u8; if cfg!(feature = "crc") { 7 } else { 6 }];
200 self.write(interface, Aht2xCommand::Measure, &[0x33, 0x00])
201 .await?;
202 loop {
203 delay.delay_ms(MEASURE_TIME_MS).await;
204 self.read(interface, &mut resp).await?;
205 let full_resp = &resp[..];
206 let resp = full_resp;
207 let state = Status(resp[0]);
208 let resp = &resp[1..];
209 #[cfg(feature = "defmt")]
210 defmt::debug!("{:?} {}", state, state.is_busy());
211 if state.is_busy() {
212 continue;
213 }
214 let humidity = HumidityData::from_bytes(resp[..3].try_into().unwrap());
215 let resp = &resp[2..];
216 let temperature = TemperatureData::from_bytes(resp[..3].try_into().unwrap());
217 #[allow(unused)]
218 let resp = &resp[3..];
219 #[cfg(feature = "crc")]
220 let _ = {
221 let digest = calc_crc(&full_resp[..6]);
222 match digest {
223 digest if digest == resp[0] => {}
224 #[allow(unused)]
225 digest => {
226 #[cfg(feature = "defmt")]
227 defmt::warn!("crc mismatch, expected {}, got {}", resp[0], digest);
228 continue;
229 }
230 }
231 &resp[1..]
232 };
233 #[cfg(feature = "defmt")]
234 defmt::debug!("temperature: {:?}, humidity: {:?}", temperature, humidity);
235 break Ok((humidity, temperature));
236 }
237 }
238}
239
240#[cfg(feature = "crc")]
241fn calc_crc(resp: &[u8]) -> u8 {
242 let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
243 let mut digest = crc.digest_with_initial(0xFF);
244 digest.update(resp);
245 digest.finalize()
246}
247
248#[cfg(test)]
249extern crate std;
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn status() {
257 assert!(Status(158).is_busy())
258 }
259
260 #[test]
261 fn parse_measurement() {
262 let resp: [u8; 5] = [111, 65, 133, 170, 130];
263 let hum = HumidityData::from_bytes(resp[..3].try_into().unwrap());
264 let temp = TemperatureData::from_bytes(resp[3..].try_into().unwrap());
265 }
266}