1use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use pamoja_core::Sensor;
6
7use super::{
8 command, single_shot, Measurement, Repeatability, Status, MIN_COMMAND_GAP_MICROS,
9 SOFT_RESET_MICROS,
10};
11use crate::error::DriverError;
12
13#[derive(Debug)]
48pub struct Sht3x<I2C, D> {
49 bus: I2C,
50 address: u8,
51 delay: D,
52 repeatability: Repeatability,
53 status: Option<Status>,
54}
55
56impl<I2C, D> Sht3x<I2C, D> {
57 pub fn new(bus: I2C, address: u8, delay: D) -> Self {
71 Sht3x {
72 bus,
73 address,
74 delay,
75 repeatability: Repeatability::High,
76 status: None,
77 }
78 }
79
80 pub fn with_repeatability(mut self, repeatability: Repeatability) -> Self {
91 self.repeatability = repeatability;
92 self
93 }
94
95 pub fn address(&self) -> u8 {
97 self.address
98 }
99
100 pub fn status(&self) -> Option<Status> {
102 self.status
103 }
104
105 pub fn release(self) -> (I2C, D) {
111 (self.bus, self.delay)
112 }
113}
114
115impl<I2C: I2c, D: DelayNs> Sht3x<I2C, D> {
116 pub fn init(&mut self) -> Result<(), DriverError<I2C::Error>> {
129 self.send(command::SOFT_RESET)?;
130 self.delay.delay_us(SOFT_RESET_MICROS);
131 self.read_status()?;
132 Ok(())
133 }
134
135 pub fn measure(&mut self) -> Result<Measurement, DriverError<I2C::Error>> {
155 self.initialize_once()?;
156 self.send(single_shot(self.repeatability, false))?;
157 let wait = self.repeatability.max_measurement_micros();
158 self.delay.delay_us(wait);
159 let mut bytes = [0u8; 6];
160 self.bus
161 .read(self.address, &mut bytes)
162 .map_err(DriverError::Bus)?;
163 Measurement::parse(&bytes).map_err(DriverError::Sensor)
164 }
165
166 pub fn read_status(&mut self) -> Result<Status, DriverError<I2C::Error>> {
181 self.send(command::READ_STATUS)?;
182 let mut bytes = [0u8; 3];
183 self.bus
184 .read(self.address, &mut bytes)
185 .map_err(DriverError::Bus)?;
186 self.delay.delay_us(MIN_COMMAND_GAP_MICROS);
187 let status = Status::parse(&bytes).map_err(DriverError::Sensor)?;
188 self.status = Some(status);
189 Ok(status)
190 }
191
192 pub fn heater_on(&mut self) -> Result<(), DriverError<I2C::Error>> {
205 self.initialize_once()?;
206 self.send(command::HEATER_ENABLE)?;
207 self.delay.delay_us(MIN_COMMAND_GAP_MICROS);
208 Ok(())
209 }
210
211 pub fn heater_off(&mut self) -> Result<(), DriverError<I2C::Error>> {
222 self.initialize_once()?;
223 self.send(command::HEATER_DISABLE)?;
224 self.delay.delay_us(MIN_COMMAND_GAP_MICROS);
225 Ok(())
226 }
227
228 fn initialize_once(&mut self) -> Result<(), DriverError<I2C::Error>> {
229 if self.status.is_none() {
230 self.init()?;
231 }
232 Ok(())
233 }
234
235 fn send(&mut self, word: u16) -> Result<(), DriverError<I2C::Error>> {
236 self.bus
237 .write(self.address, &word.to_be_bytes())
238 .map_err(DriverError::Bus)
239 }
240}
241
242impl<I2C, D> Sensor for Sht3x<I2C, D>
243where
244 I2C: I2c + Send,
245 I2C::Error: core::fmt::Debug,
246 D: DelayNs + Send,
247{
248 type Reading = Measurement;
249
250 async fn read(&mut self) -> pamoja_core::Result<Measurement> {
251 self.measure().map_err(pamoja_core::Error::from)
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::sht3x::{
259 humidity_raw_from_relative_humidity, temperature_raw_from_celsius, I2C_ADDRESS_A,
260 I2C_ADDRESS_B,
261 };
262 use crate::SensorError;
263 use alloc::vec;
264 use alloc::vec::Vec;
265 use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
266 use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep};
267
268 const SOFT_RESET: [u8; 2] = [0x30, 0xA2];
269 const READ_STATUS: [u8; 2] = [0xF3, 0x2D];
270 const SINGLE_SHOT_HIGH: [u8; 2] = [0x24, 0x00];
271 const HEATER_ENABLE: [u8; 2] = [0x30, 0x6D];
272 const HEATER_DISABLE: [u8; 2] = [0x30, 0x66];
273
274 fn part_at(celsius: f32, percent: f32) -> Measurement {
275 Measurement {
276 temperature_raw: temperature_raw_from_celsius(celsius),
277 humidity_raw: humidity_raw_from_relative_humidity(percent),
278 }
279 }
280
281 fn init_steps(address: u8) -> Vec<I2cStep> {
282 vec![
283 I2cStep::write(address, SOFT_RESET),
284 I2cStep::write(address, READ_STATUS),
285 I2cStep::read(address, Status::from_bits(Status::DEFAULT).to_bytes()),
286 ]
287 }
288
289 fn measure_steps(address: u8, command: [u8; 2], reply: [u8; 6]) -> Vec<I2cStep> {
290 vec![
291 I2cStep::write(address, command),
292 I2cStep::read(address, reply),
293 ]
294 }
295
296 #[test]
297 fn init_resets_and_confirms_the_part_and_measure_follows_the_single_shot_sequence() {
298 let part = part_at(25.0, 60.0);
299 let mut steps = init_steps(I2C_ADDRESS_A);
300 steps.extend(measure_steps(
301 I2C_ADDRESS_A,
302 SINGLE_SHOT_HIGH,
303 part.to_bytes(),
304 ));
305 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
306 assert_eq!(sensor.address(), I2C_ADDRESS_A);
307 assert_eq!(sensor.status(), None);
308 sensor.init().unwrap();
309 assert_eq!(sensor.status(), Some(Status::from_bits(Status::DEFAULT)));
310
311 let measurement = sensor.measure().unwrap();
312 assert_eq!(measurement, part);
313 assert_eq!(measurement.temperature_milli_celsius(), 25_000);
314 assert_eq!(measurement.humidity_milli_percent(), 60_000);
315
316 let (bus, delay) = sensor.release();
317 assert!(bus.done());
318 assert_eq!(
319 delay.waits_ns(),
320 [1_500_000, 1_000_000, 15_000_000],
321 "the 1.5 ms soft-reset time, the 1 ms command gap, then 15 ms at high repeatability"
322 );
323 }
324
325 #[test]
326 fn the_repeatability_picks_the_table_9_command_and_the_table_4_wait() {
327 let part = part_at(-10.5, 12.5);
328 let table = [
329 (Repeatability::Low, [0x24, 0x16], 4_000_000),
330 (Repeatability::Medium, [0x24, 0x0B], 6_000_000),
331 (Repeatability::High, [0x24, 0x00], 15_000_000),
332 ];
333 for (repeatability, command, wait_ns) in table {
334 let mut steps = init_steps(I2C_ADDRESS_B);
335 steps.extend(measure_steps(I2C_ADDRESS_B, command, part.to_bytes()));
336 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_B, DelayLog::new())
337 .with_repeatability(repeatability);
338 assert_eq!(sensor.measure().unwrap(), part, "{repeatability:?}");
339 let (bus, delay) = sensor.release();
340 assert!(bus.done(), "{repeatability:?}");
341 assert_eq!(delay.waits_ns()[2], wait_ns, "{repeatability:?}");
342 }
343 }
344
345 #[test]
346 fn a_corrupted_word_is_a_crc_error() {
347 let mut data = part_at(25.0, 60.0).to_bytes();
348 data[4] ^= 0x80;
349 let mut steps = init_steps(I2C_ADDRESS_A);
350 steps.extend(measure_steps(I2C_ADDRESS_A, SINGLE_SHOT_HIGH, data));
351 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
352 assert_eq!(sensor.measure(), Err(DriverError::Sensor(SensorError::Crc)));
353 assert!(sensor.release().0.done());
354
355 let mut status = Status::from_bits(Status::DEFAULT).to_bytes();
356 status[2] ^= 0x01;
357 let steps = [
358 I2cStep::write(I2C_ADDRESS_A, SOFT_RESET),
359 I2cStep::write(I2C_ADDRESS_A, READ_STATUS),
360 I2cStep::read(I2C_ADDRESS_A, status),
361 ];
362 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
363 assert_eq!(sensor.init(), Err(DriverError::Sensor(SensorError::Crc)));
364 assert_eq!(
365 sensor.status(),
366 None,
367 "a word that fails its CRC is not kept"
368 );
369 assert!(sensor.release().0.done());
370 }
371
372 #[test]
373 fn a_bus_fault_surfaces_as_a_bus_error_and_maps_to_the_core_io_error() {
374 let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
375 let steps = [I2cStep::fault(I2C_ADDRESS_A, kind)];
376 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
377 let error = sensor.init().unwrap_err();
378 assert!(matches!(error, DriverError::Bus(_)));
379 let core: pamoja_core::Error = error.into();
380 assert!(matches!(core, pamoja_core::Error::Io(_)));
381
382 let mut steps = init_steps(I2C_ADDRESS_A);
383 steps.push(I2cStep::write(I2C_ADDRESS_A, SINGLE_SHOT_HIGH));
384 steps.push(I2cStep::fault(I2C_ADDRESS_A, kind));
385 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
386 assert!(
387 matches!(sensor.measure(), Err(DriverError::Bus(_))),
388 "the NACK the part answers a read header with before data is ready is a bus error"
389 );
390 assert!(sensor.release().0.done());
391 }
392
393 #[test]
394 fn as_a_sensor_it_reads_and_maps_to_one_channel() {
395 let part = part_at(23.73, 63.37);
396 let mut steps = init_steps(I2C_ADDRESS_A);
397 steps.extend(measure_steps(
398 I2C_ADDRESS_A,
399 SINGLE_SHOT_HIGH,
400 part.to_bytes(),
401 ));
402 let sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
403 let mut celsius = sensor.map(|measurement| measurement.temperature_celsius());
404 let reading = block_on(celsius.read()).unwrap();
405 assert!((reading - 23.73).abs() < 0.002);
406 assert!(celsius.into_inner().release().0.done());
407 }
408
409 #[test]
410 fn the_heater_commands_are_table_16_and_the_status_shows_the_heater() {
411 let heating = Status::from_bits(Status::HEATER_ON);
412 let mut steps = init_steps(I2C_ADDRESS_A);
413 steps.extend([
414 I2cStep::write(I2C_ADDRESS_A, HEATER_ENABLE),
415 I2cStep::write(I2C_ADDRESS_A, READ_STATUS),
416 I2cStep::read(I2C_ADDRESS_A, heating.to_bytes()),
417 I2cStep::write(I2C_ADDRESS_A, HEATER_DISABLE),
418 ]);
419 let mut sensor = Sht3x::new(I2cScript::new(steps), I2C_ADDRESS_A, DelayLog::new());
420 sensor.heater_on().unwrap();
421 assert!(
422 !sensor.status().unwrap().heater_on(),
423 "the reset leaves the heater off"
424 );
425 assert_eq!(sensor.read_status().unwrap(), heating);
426 assert!(sensor.status().unwrap().heater_on());
427 sensor.heater_off().unwrap();
428 let (bus, delay) = sensor.release();
429 assert!(bus.done());
430 assert_eq!(
431 delay.waits_ns(),
432 [1_500_000, 1_000_000, 1_000_000, 1_000_000, 1_000_000],
433 "the reset time, then the 1 ms gap after each of the four commands"
434 );
435 }
436
437 #[test]
438 fn a_reset_and_one_measurement_make_the_transfers_the_datasheet_gives() {
439 use crate::sht3x::{
440 humidity_raw_from_relative_humidity, temperature_raw_from_celsius, Measurement, Status,
441 I2C_ADDRESS_A,
442 };
443
444 const PART: u8 = I2C_ADDRESS_A;
445 let status = Status::from_bits(Status::DEFAULT).to_bytes();
446 let data = Measurement {
447 temperature_raw: temperature_raw_from_celsius(25.0),
448 humidity_raw: humidity_raw_from_relative_humidity(60.0),
449 }
450 .to_bytes();
451 let bus = I2cScript::new([
452 I2cStep::write(PART, [0x30, 0xA2]),
453 I2cStep::write(PART, [0xF3, 0x2D]),
454 I2cStep::read(PART, status),
455 I2cStep::write(PART, [0x24, 0x00]),
456 I2cStep::read(PART, data),
457 ]);
458
459 let mut sensor = Sht3x::new(bus, PART, DelayLog::new());
460 let measurement = block_on(Sensor::read(&mut sensor)).expect("the scripted part answers");
461 assert_eq!(measurement.temperature_milli_celsius(), 25_000);
462 assert_eq!(measurement.humidity_milli_percent(), 60_000);
463
464 let (bus, delay) = sensor.release();
465 assert!(bus.done());
466 assert_eq!(delay.total_micros(), 1_500 + 1_000 + 15_000);
467 }
468}