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};
8
9#[cfg(not(feature = "async"))]
10use embedded_hal as hal;
11#[cfg(feature = "async")]
12use embedded_hal_async as hal;
13
14use hal::i2c::{Operation, SevenBitAddress};
15
16pub use weather_utils::Temperature;
17use weather_utils::{Celsius, RelativeHumidity, TemperatureAndRelativeHumidity};
18
19pub const I2C_ADDRESS_LOGIC_LOW: SevenBitAddress = 0x44;
21pub const I2C_ADDRESS_LOGIC_HIGH: SevenBitAddress = 0x45;
23pub const DEFAULT_I2C_ADDRESS: SevenBitAddress = I2C_ADDRESS_LOGIC_LOW;
25
26const CLEAR_STATUS_COMMAND: &[u8] = &[0x30, 0x41];
27const DISABLE_HEATER_COMMAND: &[u8] = &[0x30, 0x66];
28const ENABLE_HEATER_COMMAND: &[u8] = &[0x30, 0x6d];
29const GET_STATUS_COMMAND: &[u8] = &[0xf3, 0x2d];
30const MEASUREMENT_HIGH_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x06];
31const MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x0d];
32const MEASUREMENT_LOW_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x10];
33const RESET_COMMAND: &[u8] = &[0x30, 0xa2];
34
35#[derive(Debug)]
37pub enum Error<I2cE>
38where
39 I2cE: hal::i2c::Error,
40{
41 I2c(I2cE),
43 BadCrc,
45}
46
47impl<I2cE> From<I2cE> for Error<I2cE>
48where
49 I2cE: hal::i2c::Error,
50{
51 fn from(value: I2cE) -> Self {
52 Error::I2c(value)
53 }
54}
55
56#[derive(Debug)]
69pub enum Repeatability {
70 High,
72 Medium,
74 Low,
76}
77
78bitflags! {
79 #[derive(Debug)]
85 pub struct Status: u16 {
86 const WRITE_DATA_CHECKSUM = 1 << 0;
91 const COMMAND = 1 << 1;
97 const RESET = 1 << 4;
104 const T_TRACKING_ALERT = 1 << 10;
109 const RH_TRACKING_ALERT = 1 << 11;
114 const HEATER = 1 << 13;
119 const ALERT_PENDING = 1 << 15;
124 }
125}
126
127impl Display for Status {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 bitflags::parser::to_writer(self, f)
130 }
131}
132
133#[derive(Clone, Copy, Debug, Default)]
134struct SensorMeasurement {
135 humidity: f32,
136 temperature: f32,
137}
138
139impl From<SensorMeasurement> for TemperatureAndRelativeHumidity<Celsius> {
140 fn from(value: SensorMeasurement) -> Self {
141 TemperatureAndRelativeHumidity {
142 temperature: Celsius(value.temperature),
143 relative_humidity: RelativeHumidity::new(value.humidity).unwrap(),
144 }
145 }
146}
147
148#[derive(Debug)]
150pub struct Sht3x<I2C, D> {
151 address: SevenBitAddress,
152 delay: D,
153 i2c: I2C,
154 pub repeatability: Repeatability,
156}
157
158impl<I2C, D> Sht3x<I2C, D>
159where
160 I2C: hal::i2c::I2c,
161 D: hal::delay::DelayNs,
162{
163 #[maybe_async_cfg::maybe(
167 sync(not(feature = "async"), keep_self),
168 async(feature = "async", keep_self)
169 )]
170 pub async fn clear_status(&mut self) -> Result<(), Error<I2C::Error>> {
171 self.i2c.write(self.address, CLEAR_STATUS_COMMAND).await?;
172 Ok(())
173 }
174
175 #[maybe_async_cfg::maybe(
177 sync(not(feature = "async"), keep_self),
178 async(feature = "async", keep_self)
179 )]
180 pub async fn disable_heater(&mut self) -> Result<(), Error<I2C::Error>> {
181 self.i2c.write(self.address, DISABLE_HEATER_COMMAND).await?;
182 Ok(())
183 }
184
185 #[maybe_async_cfg::maybe(
187 sync(not(feature = "async"), keep_self),
188 async(feature = "async", keep_self)
189 )]
190 pub async fn enable_heater(&mut self) -> Result<(), Error<I2C::Error>> {
191 self.i2c.write(self.address, ENABLE_HEATER_COMMAND).await?;
192 Ok(())
193 }
194
195 #[maybe_async_cfg::maybe(
197 sync(not(feature = "async"), keep_self),
198 async(feature = "async", keep_self)
199 )]
200 pub async fn get_status(&mut self) -> Result<Status, Error<I2C::Error>> {
201 let mut data = [0u8; 3];
202 let mut operations = [
203 Operation::Write(GET_STATUS_COMMAND),
204 Operation::Read(&mut data),
205 ];
206 self.i2c.transaction(self.address, &mut operations).await?;
207 let status: &[u8; 2] = &data[0..2].try_into().unwrap();
208 let status_crc = data[2];
209 Self::check_crc(status, status_crc)?;
210 Ok(Status::from_bits_retain(Self::get_u16_value(status)))
211 }
212
213 #[maybe_async_cfg::maybe(
220 sync(not(feature = "async"), keep_self),
221 async(feature = "async", keep_self)
222 )]
223 pub async fn single_measurement(
224 &mut self,
225 ) -> Result<TemperatureAndRelativeHumidity<Celsius>, Error<I2C::Error>> {
226 let command = match self.repeatability {
227 Repeatability::High => MEASUREMENT_HIGH_REPEATIBILITY_COMMAND,
228 Repeatability::Medium => MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND,
229 Repeatability::Low => MEASUREMENT_LOW_REPEATIBILITY_COMMAND,
230 };
231 let mut data = [0u8; 6];
232 let mut operations = [Operation::Write(command), Operation::Read(&mut data)];
233 self.i2c.transaction(self.address, &mut operations).await?;
234 let temperature: &[u8; 2] = &data[0..2].try_into().unwrap();
235 let temperature_crc = data[2];
236 let humidity: &[u8; 2] = &data[3..5].try_into().unwrap();
237 let humidity_crc = data[5];
238 Self::check_crc(temperature, temperature_crc)?;
239 Self::check_crc(humidity, humidity_crc)?;
240 let temperature = Self::get_u16_value(temperature);
241 let humidity = Self::get_u16_value(humidity);
242
243 let measurement = SensorMeasurement {
244 temperature: ((temperature as f32 * 175.0) / 65535.0) - 45.0,
245 humidity: (humidity as f32 * 100.0) / 65535.0,
246 };
247 Ok(measurement.into())
248 }
249
250 #[maybe_async_cfg::maybe(
252 sync(not(feature = "async"), keep_self),
253 async(feature = "async", keep_self)
254 )]
255 pub async fn new(
256 i2c: I2C,
257 address: SevenBitAddress,
258 delay: D,
259 ) -> Result<Self, Error<I2C::Error>> {
260 let mut dev = Self {
261 address,
262 delay,
263 i2c,
264 repeatability: Repeatability::Medium,
265 };
266
267 dev.reset().await?;
268
269 Ok(dev)
270 }
271
272 #[maybe_async_cfg::maybe(
275 sync(not(feature = "async"), keep_self),
276 async(feature = "async", keep_self)
277 )]
278 pub async fn reset(&mut self) -> Result<(), Error<I2C::Error>> {
279 self.i2c.write(self.address, RESET_COMMAND).await?;
280 self.delay.delay_us(1500).await; Ok(())
282 }
283
284 fn calc_crc(data: &[u8; 2]) -> u8 {
285 let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
286 let mut digest = crc.digest();
287 digest.update(data);
288 digest.finalize()
289 }
290
291 fn check_crc(data: &[u8; 2], expected_crc: u8) -> Result<(), Error<I2C::Error>> {
292 if Self::calc_crc(data) != expected_crc {
293 Err(Error::BadCrc)
294 } else {
295 Ok(())
296 }
297 }
298
299 #[inline]
300 fn get_u16_value(data: &[u8; 2]) -> u16 {
301 (data[0] as u16) << 8 | (data[1] as u16)
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use core::fmt::Write;
308
309 use embedded_hal::i2c::ErrorKind;
310 use embedded_hal_mock::eh1::delay::StdSleep as Delay;
311 use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
312 use heapless::String;
313
314 use super::*;
315
316 fn create_device() -> Sht3x<I2cMock, Delay> {
317 let expectations = [I2cTransaction::write(
318 DEFAULT_I2C_ADDRESS,
319 RESET_COMMAND.to_vec(),
320 )];
321 let i2c = I2cMock::new(&expectations);
322 let mut device = Sht3x::new(i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
323 device.i2c.done();
324 device
325 }
326
327 #[test]
328 fn clear_status() {
329 let expectations = [
330 I2cTransaction::write(DEFAULT_I2C_ADDRESS, CLEAR_STATUS_COMMAND.to_vec()),
331 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
332 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
333 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00, 0x81].to_vec()),
334 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
335 ];
336 let mut device = create_device();
337 device.i2c.update_expectations(&expectations);
338 assert!(matches!(device.clear_status(), Ok(())));
339 let status = device.get_status();
340 let status = match status {
341 Ok(s) => s,
342 Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
343 };
344 assert!(!status.contains(Status::WRITE_DATA_CHECKSUM));
345 assert!(!status.contains(Status::COMMAND));
346 assert!(!status.contains(Status::RESET));
347 assert!(!status.contains(Status::T_TRACKING_ALERT));
348 assert!(!status.contains(Status::RH_TRACKING_ALERT));
349 assert!(!status.contains(Status::HEATER));
350 assert!(!status.contains(Status::ALERT_PENDING));
351 let mut buffer: String<64> = String::new();
352 write!(&mut buffer, "{status}").unwrap();
353 assert_eq!(buffer, "");
354 device.i2c.done();
355 }
356
357 #[test]
358 fn get_status() {
359 let expectations = [
360 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
361 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
362 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00, 0x81].to_vec()),
363 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
364 ];
365 let mut device = create_device();
366 device.i2c.update_expectations(&expectations);
367 let status = device.get_status();
368 let status = match status {
369 Ok(s) => s,
370 Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
371 };
372 assert!(!status.contains(Status::WRITE_DATA_CHECKSUM));
373 assert!(!status.contains(Status::COMMAND));
374 assert!(!status.contains(Status::RESET));
375 assert!(!status.contains(Status::T_TRACKING_ALERT));
376 assert!(!status.contains(Status::RH_TRACKING_ALERT));
377 assert!(!status.contains(Status::HEATER));
378 assert!(!status.contains(Status::ALERT_PENDING));
379 let mut buffer: String<64> = String::new();
380 write!(&mut buffer, "{status}").unwrap();
381 assert_eq!(buffer, "");
382 device.i2c.done();
383 }
384
385 #[test]
386 fn get_status_bad_crc() {
387 let expectations = [
388 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
389 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
390 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00, 0x73].to_vec()),
391 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
392 ];
393 let mut device = create_device();
394 device.i2c.update_expectations(&expectations);
395 let err = device.get_status().expect_err("Bad CRC");
396 assert!(matches!(err, Error::BadCrc));
397 device.i2c.done();
398 }
399
400 #[test]
401 fn heater() {
402 let expectations = [
403 I2cTransaction::write(DEFAULT_I2C_ADDRESS, ENABLE_HEATER_COMMAND.to_vec()),
404 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
405 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
406 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x20, 0x03, 0x0e].to_vec()),
407 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
408 I2cTransaction::write(DEFAULT_I2C_ADDRESS, DISABLE_HEATER_COMMAND.to_vec()),
409 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
410 I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
411 I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x03, 0xd2].to_vec()),
412 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
413 ];
414 let mut device = create_device();
415 device.i2c.update_expectations(&expectations);
416 assert!(matches!(device.enable_heater(), Ok(())));
417 let status = device.get_status();
418 let status = match status {
419 Ok(s) => s,
420 Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
421 };
422 assert!(status.contains(Status::WRITE_DATA_CHECKSUM));
423 assert!(status.contains(Status::COMMAND));
424 assert!(!status.contains(Status::RESET));
425 assert!(!status.contains(Status::T_TRACKING_ALERT));
426 assert!(!status.contains(Status::RH_TRACKING_ALERT));
427 assert!(status.contains(Status::HEATER));
428 assert!(!status.contains(Status::ALERT_PENDING));
429 let mut buffer: String<64> = String::new();
430 write!(&mut buffer, "{status}").unwrap();
431 assert_eq!(buffer, "WRITE_DATA_CHECKSUM | COMMAND | HEATER");
432 assert!(matches!(device.disable_heater(), Ok(())));
433 let status = device.get_status();
434 let status = match status {
435 Ok(s) => s,
436 Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
437 };
438 assert!(status.contains(Status::WRITE_DATA_CHECKSUM));
439 assert!(status.contains(Status::COMMAND));
440 assert!(!status.contains(Status::RESET));
441 assert!(!status.contains(Status::T_TRACKING_ALERT));
442 assert!(!status.contains(Status::RH_TRACKING_ALERT));
443 assert!(!status.contains(Status::HEATER));
444 assert!(!status.contains(Status::ALERT_PENDING));
445 let mut buffer: String<64> = String::new();
446 write!(&mut buffer, "{status}").unwrap();
447 assert_eq!(buffer, "WRITE_DATA_CHECKSUM | COMMAND");
448 device.i2c.done();
449 }
450
451 #[test]
452 fn reset() {
453 let expectations = [I2cTransaction::write(
454 DEFAULT_I2C_ADDRESS,
455 RESET_COMMAND.to_vec(),
456 )];
457 let mut device = create_device();
458 device.i2c.update_expectations(&expectations);
459 assert!(matches!(device.reset(), Ok(())));
460 device.i2c.done();
461 }
462
463 #[test]
464 fn reset_with_arbitration_loss_error() {
465 let expectations = [
466 I2cTransaction::write(DEFAULT_I2C_ADDRESS, RESET_COMMAND.to_vec())
467 .with_error(ErrorKind::ArbitrationLoss),
468 ];
469 let mut device = create_device();
470 device.i2c.update_expectations(&expectations);
471 assert!(matches!(device.reset(), Err(Error::I2c(_))));
472 device.i2c.done();
473 }
474
475 #[test]
476 fn single_measurement_high_repeatability() {
477 let expectations = [
478 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
479 I2cTransaction::write(
480 DEFAULT_I2C_ADDRESS,
481 MEASUREMENT_HIGH_REPEATIBILITY_COMMAND.to_vec(),
482 ),
483 I2cTransaction::read(
484 DEFAULT_I2C_ADDRESS,
485 [0x5f, 0x58, 0x38, 0x7b, 0xb2, 0x7d].to_vec(),
486 ),
487 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
488 ];
489 let mut device = create_device();
490 device.repeatability = Repeatability::High;
491 device.i2c.update_expectations(&expectations);
492 let measurement = device.single_measurement();
493 let measurement = match measurement {
494 Ok(m) => m,
495 Err(e) => panic!("Expected Ok(measurement), got Err({e:?})"),
496 };
497 assert_eq!(
498 measurement,
499 TemperatureAndRelativeHumidity {
500 temperature: Celsius(20.18),
501 relative_humidity: RelativeHumidity::new(48.32).unwrap()
502 }
503 );
504 device.i2c.done();
505 }
506
507 #[test]
508 fn single_measurement_low_repeatability() {
509 let expectations = [
510 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
511 I2cTransaction::write(
512 DEFAULT_I2C_ADDRESS,
513 MEASUREMENT_LOW_REPEATIBILITY_COMMAND.to_vec(),
514 ),
515 I2cTransaction::read(
516 DEFAULT_I2C_ADDRESS,
517 [0x5f, 0x58, 0x38, 0x7b, 0xb2, 0x7d].to_vec(),
518 ),
519 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
520 ];
521 let mut device = create_device();
522 device.repeatability = Repeatability::Low;
523 device.i2c.update_expectations(&expectations);
524 let measurement = device.single_measurement();
525 let measurement = match measurement {
526 Ok(m) => m,
527 Err(e) => panic!("Expected Ok(measurement), got Err({e:?})"),
528 };
529 assert_eq!(
530 measurement,
531 TemperatureAndRelativeHumidity {
532 temperature: Celsius(20.18),
533 relative_humidity: RelativeHumidity::new(48.32).unwrap()
534 }
535 );
536 device.i2c.done();
537 }
538
539 #[test]
540 fn single_measurement_medium_repeatability() {
541 let expectations = [
542 I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
543 I2cTransaction::write(
544 DEFAULT_I2C_ADDRESS,
545 MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND.to_vec(),
546 ),
547 I2cTransaction::read(
548 DEFAULT_I2C_ADDRESS,
549 [0x71, 0x17, 0x9a, 0xcb, 0x91, 0x39].to_vec(),
550 ),
551 I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
552 ];
553 let mut device = create_device();
554 device.i2c.update_expectations(&expectations);
555 let measurement = device.single_measurement();
556 let measurement = match measurement {
557 Ok(m) => m,
558 Err(e) => panic!("Expected Ok(measurement), got Err({e:?})"),
559 };
560 assert_eq!(
561 measurement,
562 TemperatureAndRelativeHumidity {
563 temperature: Celsius(32.31),
564 relative_humidity: RelativeHumidity::new(79.52).unwrap()
565 }
566 );
567 device.i2c.done();
568 }
569}