1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs)]
3#![no_std]
4
5use bitflags::bitflags;
6use core::fmt::Display;
7use crc::{Crc, CRC_8_NRSC_5};
8use embedded_hal::i2c::{Operation, SevenBitAddress};
9#[allow(unused_imports)]
10use micromath::F32Ext;
11
12pub const I2C_ADDRESS_LOGIC_LOW: SevenBitAddress = 0x44;
14pub const I2C_ADDRESS_LOGIC_HIGH: SevenBitAddress = 0x45;
16pub const DEFAULT_I2C_ADDRESS: SevenBitAddress = I2C_ADDRESS_LOGIC_LOW;
18
19const CLEAR_STATUS_COMMAND: &[u8] = &[0x30, 0x41];
20const DISABLE_HEATER_COMMAND: &[u8] = &[0x30, 0x66];
21const ENABLE_HEATER_COMMAND: &[u8] = &[0x30, 0x6d];
22const GET_STATUS_COMMAND: &[u8] = &[0xf3, 0x2d];
23const MEASUREMENT_HIGH_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x06];
24const MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x0d];
25const MEASUREMENT_LOW_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x10];
26const RESET_COMMAND: &[u8] = &[0x30, 0xa2];
27
28#[derive(Debug)]
30pub enum Error<I2cE>
31where
32 I2cE: embedded_hal::i2c::Error,
33{
34 I2c(I2cE),
36 BadCrc,
38}
39
40impl<I2cE> From<I2cE> for Error<I2cE>
41where
42 I2cE: embedded_hal::i2c::Error,
43{
44 fn from(value: I2cE) -> Self {
45 Error::I2c(value)
46 }
47}
48
49#[derive(Debug)]
62pub enum Repeatability {
63 High,
65 Medium,
67 Low,
69}
70
71bitflags! {
72 #[derive(Debug)]
78 pub struct Status: u16 {
79 const WRITE_DATA_CHECKSUM = 1 << 0;
84 const COMMAND = 1 << 1;
90 const RESET = 1 << 4;
97 const T_TRACKING_ALERT = 1 << 10;
102 const RH_TRACKING_ALERT = 1 << 11;
107 const HEATER = 1 << 13;
112 const ALERT_PENDING = 1 << 15;
117 }
118}
119
120impl Display for Status {
121 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122 bitflags::parser::to_writer(self, f)
123 }
124}
125
126#[derive(Clone, Copy, Debug, Default, PartialEq)]
128pub enum TemperatureUnit {
129 #[default]
130 Celcius,
132 Farenheit,
134}
135
136#[derive(Clone, Copy, Debug, Default)]
140pub struct Measurement {
141 pub humidity: f32,
143 pub temperature: f32,
145 pub unit: TemperatureUnit,
147}
148
149#[derive(Debug)]
151pub struct Sht3x<I2C, D> {
152 address: SevenBitAddress,
153 delay: D,
154 i2c: I2C,
155 pub repeatability: Repeatability,
157 pub unit: TemperatureUnit,
159}
160
161impl<I2C, D> Sht3x<I2C, D>
162where
163 I2C: embedded_hal::i2c::I2c,
164 D: embedded_hal::delay::DelayNs,
165{
166 pub fn clear_status(&mut self) -> Result<(), Error<I2C::Error>> {
170 self.i2c.write(self.address, CLEAR_STATUS_COMMAND)?;
171 Ok(())
172 }
173
174 pub fn disable_heater(&mut self) -> Result<(), Error<I2C::Error>> {
176 self.i2c.write(self.address, DISABLE_HEATER_COMMAND)?;
177 Ok(())
178 }
179
180 pub fn enable_heater(&mut self) -> Result<(), Error<I2C::Error>> {
182 self.i2c.write(self.address, ENABLE_HEATER_COMMAND)?;
183 Ok(())
184 }
185
186 pub fn get_status(&mut self) -> Result<Status, Error<I2C::Error>> {
188 let mut status = [0u8; 2];
189 let mut status_crc = [0u8; 1];
190 let mut operations = [
191 Operation::Write(GET_STATUS_COMMAND),
192 Operation::Read(&mut status),
193 Operation::Read(&mut status_crc),
194 ];
195 self.i2c.transaction(self.address, &mut operations)?;
196 Self::check_crc(&status, status_crc[0])?;
197 Ok(Status::from_bits_retain(Self::get_u16_value(&status)))
198 }
199
200 pub fn single_measurement(&mut self) -> Result<Measurement, Error<I2C::Error>> {
207 let command = match self.repeatability {
208 Repeatability::High => MEASUREMENT_HIGH_REPEATIBILITY_COMMAND,
209 Repeatability::Medium => MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND,
210 Repeatability::Low => MEASUREMENT_LOW_REPEATIBILITY_COMMAND,
211 };
212 let mut temperature = [0u8; 2];
213 let mut humidity = [0u8; 2];
214 let mut temperature_crc = [0u8; 1];
215 let mut humidity_crc = [0u8; 1];
216 let mut operations = [
217 Operation::Write(command),
218 Operation::Read(&mut temperature),
219 Operation::Read(&mut temperature_crc),
220 Operation::Read(&mut humidity),
221 Operation::Read(&mut humidity_crc),
222 ];
223 self.i2c.transaction(self.address, &mut operations)?;
224 Self::check_crc(&temperature, temperature_crc[0])?;
225 Self::check_crc(&humidity, humidity_crc[0])?;
226 let temperature = Self::get_u16_value(&temperature);
227 let humidity = Self::get_u16_value(&humidity);
228
229 Ok(Measurement {
230 temperature: match self.unit {
231 TemperatureUnit::Celcius => ((temperature as f32 * 175.0) / 65535.0) - 45.0,
232 TemperatureUnit::Farenheit => ((temperature as f32 * 315.0) / 65535.0) - 49.0,
233 },
234 humidity: (humidity as f32 * 100.0) / 65535.0,
235 unit: self.unit,
236 })
237 }
238
239 pub fn new(i2c: I2C, address: SevenBitAddress, delay: D) -> Self {
241 Self {
242 address,
243 delay,
244 i2c,
245 repeatability: Repeatability::Medium,
246 unit: TemperatureUnit::Celcius,
247 }
248 }
249
250 pub fn reset(&mut self) -> Result<(), Error<I2C::Error>> {
253 self.i2c.write(self.address, RESET_COMMAND)?;
254 self.delay.delay_us(1500); Ok(())
256 }
257
258 fn calc_crc(data: &[u8; 2]) -> u8 {
259 let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
260 let mut digest = crc.digest();
261 digest.update(data);
262 digest.finalize()
263 }
264
265 fn check_crc(data: &[u8; 2], expected_crc: u8) -> Result<(), Error<I2C::Error>> {
266 if Self::calc_crc(data) != expected_crc {
267 Err(Error::BadCrc)
268 } else {
269 Ok(())
270 }
271 }
272
273 #[inline]
274 fn get_u16_value(data: &[u8; 2]) -> u16 {
275 (data[0] as u16) << 8 | (data[1] as u16)
276 }
277}
278
279pub fn calculate_absolute_humidity(measurement: Measurement) -> f32 {
282 let temperature = match measurement.unit {
283 TemperatureUnit::Celcius => measurement.temperature,
284 TemperatureUnit::Farenheit => convert_farenheit_to_celcius(measurement.temperature),
285 };
286 (6.112 * ((17.67 * temperature) / (temperature + 243.5)).exp() * measurement.humidity * 2.1674)
287 / (273.15 + temperature)
288}
289
290pub fn convert_celcius_to_farenheit(temperature: f32) -> f32 {
292 temperature * 1.8 + 32.0
293}
294
295pub fn convert_farenheit_to_celcius(temperature: f32) -> f32 {
297 (temperature - 32.0) * 0.55555
298}
299
300#[cfg(test)]
301mod tests {
302 use crate::*;
303 use embedded_hal_mock::eh1::delay::StdSleep as Delay;
304 use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
305
306 #[test]
307 fn calculate_absolute_humidity() {
308 assert!(
309 (crate::calculate_absolute_humidity(Measurement {
310 humidity: 45.59,
311 temperature: 21.18,
312 unit: TemperatureUnit::Celcius
313 }) - 8.43)
314 .abs()
315 < 0.01
316 );
317 assert!(
318 (crate::calculate_absolute_humidity(Measurement {
319 humidity: 45.59,
320 temperature: 70.12,
321 unit: TemperatureUnit::Farenheit
322 }) - 8.43)
323 .abs()
324 < 0.01
325 );
326 assert!(
327 (crate::calculate_absolute_humidity(Measurement {
328 humidity: 34.71,
329 temperature: 2.93,
330 unit: TemperatureUnit::Celcius
331 }) - 2.06)
332 .abs()
333 < 0.01
334 );
335 assert!(
336 (crate::calculate_absolute_humidity(Measurement {
337 humidity: 74.91,
338 temperature: 107.7,
339 unit: TemperatureUnit::Farenheit
340 }) - 42.49)
341 .abs()
342 < 0.01
343 );
344 }
345
346 #[test]
347 fn clear_status() {
348 let expectations = [
349 I2cTransaction::write(DEFAULT_I2C_ADDRESS, CLEAR_STATUS_COMMAND.to_vec()),
350 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
351 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
352 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00].to_vec()),
353 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x81].to_vec()),
354 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
355 ];
356 let mut i2c = I2cMock::new(&expectations);
357 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
358 device.clear_status().unwrap();
359 let status = device.get_status().unwrap();
360 assert!(!status.contains(Status::WRITE_DATA_CHECKSUM));
361 assert!(!status.contains(Status::COMMAND));
362 assert!(!status.contains(Status::RESET));
363 assert!(!status.contains(Status::T_TRACKING_ALERT));
364 assert!(!status.contains(Status::RH_TRACKING_ALERT));
365 assert!(!status.contains(Status::HEATER));
366 assert!(!status.contains(Status::ALERT_PENDING));
367 i2c.done();
368 }
369
370 #[test]
371 fn convert_celcius_to_farenheit() {
372 assert!((crate::convert_celcius_to_farenheit(0.0) - 32.0).abs() < 0.01);
373 assert!((crate::convert_celcius_to_farenheit(15.73) - 60.31).abs() < 0.01);
374 assert!((crate::convert_celcius_to_farenheit(-7.49) - 18.52).abs() < 0.01);
375 assert!((crate::convert_celcius_to_farenheit(37.5) - 99.5).abs() < 0.01);
376 }
377
378 #[test]
379 fn convert_farenheit_to_celcius() {
380 assert!((crate::convert_farenheit_to_celcius(32.0) - 0.0).abs() < 0.01);
381 assert!((crate::convert_farenheit_to_celcius(60.31) - 15.73).abs() < 0.01);
382 assert!((crate::convert_farenheit_to_celcius(18.52) - -7.49).abs() < 0.01);
383 assert!((crate::convert_farenheit_to_celcius(99.5) - 37.5).abs() < 0.01);
384 }
385
386 #[test]
387 fn get_status() {
388 let expectations = [
389 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
390 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
391 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00].to_vec()),
392 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x81].to_vec()),
393 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
394 ];
395 let mut i2c = I2cMock::new(&expectations);
396 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
397 let status = device.get_status().unwrap();
398 assert!(!status.contains(Status::WRITE_DATA_CHECKSUM));
399 assert!(!status.contains(Status::COMMAND));
400 assert!(!status.contains(Status::RESET));
401 assert!(!status.contains(Status::T_TRACKING_ALERT));
402 assert!(!status.contains(Status::RH_TRACKING_ALERT));
403 assert!(!status.contains(Status::HEATER));
404 assert!(!status.contains(Status::ALERT_PENDING));
405 i2c.done();
406 }
407
408 #[test]
409 fn heater() {
410 let expectations = [
411 I2cTransaction::write(DEFAULT_I2C_ADDRESS, ENABLE_HEATER_COMMAND.to_vec()),
412 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
413 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
414 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x20, 0x03].to_vec()),
415 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x0e].to_vec()),
416 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
417 I2cTransaction::write(DEFAULT_I2C_ADDRESS, DISABLE_HEATER_COMMAND.to_vec()),
418 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
419 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
420 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x03].to_vec()),
421 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0xd2].to_vec()),
422 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
423 ];
424 let mut i2c = I2cMock::new(&expectations);
425 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
426 device.enable_heater().unwrap();
427 let status = device.get_status().unwrap();
428 assert!(status.contains(Status::WRITE_DATA_CHECKSUM));
429 assert!(status.contains(Status::COMMAND));
430 assert!(!status.contains(Status::RESET));
431 assert!(!status.contains(Status::T_TRACKING_ALERT));
432 assert!(!status.contains(Status::RH_TRACKING_ALERT));
433 assert!(status.contains(Status::HEATER));
434 assert!(!status.contains(Status::ALERT_PENDING));
435 device.disable_heater().unwrap();
436 let status = device.get_status().unwrap();
437 assert!(status.contains(Status::WRITE_DATA_CHECKSUM));
438 assert!(status.contains(Status::COMMAND));
439 assert!(!status.contains(Status::RESET));
440 assert!(!status.contains(Status::T_TRACKING_ALERT));
441 assert!(!status.contains(Status::RH_TRACKING_ALERT));
442 assert!(!status.contains(Status::HEATER));
443 assert!(!status.contains(Status::ALERT_PENDING));
444 i2c.done();
445 }
446
447 #[test]
448 fn reset() {
449 let expectations = [I2cTransaction::write(
450 DEFAULT_I2C_ADDRESS,
451 RESET_COMMAND.to_vec(),
452 )];
453 let mut i2c = I2cMock::new(&expectations);
454 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
455 device.reset().unwrap();
456 i2c.done();
457 }
458
459 #[test]
460 fn single_measurement_farenheit() {
461 let expectations = [
462 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
463 I2cTransaction::write(
464 DEFAULT_I2C_ADDRESS,
465 MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND.to_vec(),
466 ),
467 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x71, 0x17].to_vec()),
468 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x9a].to_vec()),
469 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0xcb, 0x91].to_vec()),
470 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x39].to_vec()),
471 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
472 ];
473 let mut i2c = I2cMock::new(&expectations);
474 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
475 device.unit = TemperatureUnit::Farenheit;
476 let measurement = device.single_measurement().unwrap();
477 assert!((measurement.temperature - 90.16).abs() < 0.01);
478 assert!((measurement.humidity - 79.52).abs() < 0.01);
479 i2c.done();
480 }
481
482 #[test]
483 fn single_measurement_high_repeatability() {
484 let expectations = [
485 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
486 I2cTransaction::write(
487 DEFAULT_I2C_ADDRESS,
488 MEASUREMENT_HIGH_REPEATIBILITY_COMMAND.to_vec(),
489 ),
490 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x5f, 0x58].to_vec()),
491 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x38].to_vec()),
492 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x7b, 0xb2].to_vec()),
493 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x7d].to_vec()),
494 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
495 ];
496 let mut i2c = I2cMock::new(&expectations);
497 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
498 device.repeatability = Repeatability::High;
499 let measurement = device.single_measurement().unwrap();
500 assert!((measurement.temperature - 20.18).abs() < 0.01);
501 assert!((measurement.humidity - 48.32).abs() < 0.01);
502 i2c.done();
503 }
504
505 #[test]
506 fn single_measurement_low_repeatability() {
507 let expectations = [
508 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
509 I2cTransaction::write(
510 DEFAULT_I2C_ADDRESS,
511 MEASUREMENT_LOW_REPEATIBILITY_COMMAND.to_vec(),
512 ),
513 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x5f, 0x58].to_vec()),
514 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x38].to_vec()),
515 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x7b, 0xb2].to_vec()),
516 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x7d].to_vec()),
517 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
518 ];
519 let mut i2c = I2cMock::new(&expectations);
520 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
521 device.repeatability = Repeatability::Low;
522 let measurement = device.single_measurement().unwrap();
523 assert!((measurement.temperature - 20.18).abs() < 0.01);
524 assert!((measurement.humidity - 48.32).abs() < 0.01);
525 i2c.done();
526 }
527
528 #[test]
529 fn single_measurement_medium_repeatability() {
530 let expectations = [
531 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
532 I2cTransaction::write(
533 DEFAULT_I2C_ADDRESS,
534 MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND.to_vec(),
535 ),
536 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x71, 0x17].to_vec()),
537 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x9a].to_vec()),
538 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0xcb, 0x91].to_vec()),
539 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x39].to_vec()),
540 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
541 ];
542 let mut i2c = I2cMock::new(&expectations);
543 let mut device = Sht3x::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
544 let measurement = device.single_measurement().unwrap();
545 assert!((measurement.temperature - 32.31).abs() < 0.01);
546 assert!((measurement.humidity - 79.52).abs() < 0.01);
547 i2c.done();
548 }
549}