1use crate::SensorError;
15
16pub const I2C_ADDRESS: u8 = 0x40;
18pub const MANUFACTURER_ID: u16 = 0x5449;
20pub const DEVICE_ID: u16 = 0x1050;
22pub const CONFIGURATION_RESET: u16 = 0x1000;
25
26#[cfg(feature = "embedded-hal")]
27mod driver;
28#[cfg(feature = "embedded-hal")]
29pub mod sim;
30
31#[cfg(feature = "embedded-hal")]
32pub use driver::Hdc1080;
33
34pub mod register {
36 pub const TEMPERATURE: u8 = 0x00;
38 pub const HUMIDITY: u8 = 0x01;
40 pub const CONFIGURATION: u8 = 0x02;
42 pub const SERIAL_ID_HIGH: u8 = 0xFB;
44 pub const SERIAL_ID_MID: u8 = 0xFC;
46 pub const SERIAL_ID_LOW: u8 = 0xFD;
48 pub const MANUFACTURER_ID: u8 = 0xFE;
50 pub const DEVICE_ID: u8 = 0xFF;
52}
53
54const TEMPERATURE_SPAN_MILLI: i64 = 165_000;
55const TEMPERATURE_OFFSET_MILLI: i32 = 40_000;
56const HUMIDITY_SPAN_MILLI: u64 = 100_000;
57
58pub fn milli_celsius(raw: u16) -> i32 {
71 let scaled = (raw as i64 * TEMPERATURE_SPAN_MILLI + (1 << 15)) >> 16;
72 scaled as i32 - TEMPERATURE_OFFSET_MILLI
73}
74
75pub fn celsius(raw: u16) -> f32 {
87 raw as f32 / 65_536.0 * 165.0 - 40.0
88}
89
90pub fn milli_percent(raw: u16) -> u32 {
103 ((raw as u64 * HUMIDITY_SPAN_MILLI + (1 << 15)) >> 16) as u32
104}
105
106pub fn relative_humidity(raw: u16) -> f32 {
118 raw as f32 / 65_536.0 * 100.0
119}
120
121pub fn temperature_register(milli_celsius: i32) -> u16 {
136 let offset = (milli_celsius as i64 + TEMPERATURE_OFFSET_MILLI as i64).max(0);
137 let code = (offset * 16_384 + TEMPERATURE_SPAN_MILLI / 2) / TEMPERATURE_SPAN_MILLI;
138 (code.min(16_383) as u16) << 2
139}
140
141pub fn humidity_register(milli_percent: u32) -> u16 {
154 let code = (milli_percent as u64 * 16_384 + HUMIDITY_SPAN_MILLI / 2) / HUMIDITY_SPAN_MILLI;
155 (code.min(16_383) as u16) << 2
156}
157
158pub fn serial_id(high: u16, mid: u16, low: u16) -> u64 {
173 ((high as u64) << 25) | ((mid as u64) << 9) | ((low >> 7) as u64)
174}
175
176pub fn serial_id_registers(serial: u64) -> [u16; 3] {
188 [
189 (serial >> 25) as u16,
190 (serial >> 9) as u16,
191 ((serial & 0x1FF) as u16) << 7,
192 ]
193}
194
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum AcquisitionMode {
198 Single,
200 TemperatureThenHumidity,
202}
203
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206pub enum TemperatureResolution {
207 Bits14,
209 Bits11,
211}
212
213impl TemperatureResolution {
214 pub fn conversion_time_micros(self) -> u32 {
220 match self {
221 TemperatureResolution::Bits14 => 6_350,
222 TemperatureResolution::Bits11 => 3_650,
223 }
224 }
225}
226
227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229pub enum HumidityResolution {
230 Bits14,
232 Bits11,
234 Bits8,
236}
237
238impl HumidityResolution {
239 pub fn conversion_time_micros(self) -> u32 {
245 match self {
246 HumidityResolution::Bits14 => 6_500,
247 HumidityResolution::Bits11 => 3_850,
248 HumidityResolution::Bits8 => 2_500,
249 }
250 }
251}
252
253#[derive(Clone, Copy, Debug, PartialEq, Eq)]
257pub struct Configuration {
258 pub software_reset: bool,
260 pub heater: bool,
262 pub mode: AcquisitionMode,
264 pub battery_low: bool,
267 pub temperature_resolution: TemperatureResolution,
269 pub humidity_resolution: HumidityResolution,
271}
272
273impl Default for Configuration {
274 fn default() -> Configuration {
275 Configuration {
276 software_reset: false,
277 heater: false,
278 mode: AcquisitionMode::TemperatureThenHumidity,
279 battery_low: false,
280 temperature_resolution: TemperatureResolution::Bits14,
281 humidity_resolution: HumidityResolution::Bits14,
282 }
283 }
284}
285
286impl Configuration {
287 pub fn from_register(raw: u16) -> Result<Configuration, SensorError> {
304 let humidity_resolution = match (raw >> 8) & 0b11 {
305 0b00 => HumidityResolution::Bits14,
306 0b01 => HumidityResolution::Bits11,
307 0b10 => HumidityResolution::Bits8,
308 _ => return Err(SensorError::Invalid),
309 };
310 Ok(Configuration {
311 software_reset: raw & (1 << 15) != 0,
312 heater: raw & (1 << 13) != 0,
313 mode: if raw & (1 << 12) != 0 {
314 AcquisitionMode::TemperatureThenHumidity
315 } else {
316 AcquisitionMode::Single
317 },
318 battery_low: raw & (1 << 11) != 0,
319 temperature_resolution: if raw & (1 << 10) != 0 {
320 TemperatureResolution::Bits11
321 } else {
322 TemperatureResolution::Bits14
323 },
324 humidity_resolution,
325 })
326 }
327
328 pub fn to_register(&self) -> u16 {
334 let mut raw = 0;
335 if self.software_reset {
336 raw |= 1 << 15;
337 }
338 if self.heater {
339 raw |= 1 << 13;
340 }
341 if self.mode == AcquisitionMode::TemperatureThenHumidity {
342 raw |= 1 << 12;
343 }
344 if self.battery_low {
345 raw |= 1 << 11;
346 }
347 if self.temperature_resolution == TemperatureResolution::Bits11 {
348 raw |= 1 << 10;
349 }
350 raw |= match self.humidity_resolution {
351 HumidityResolution::Bits14 => 0b00,
352 HumidityResolution::Bits11 => 0b01,
353 HumidityResolution::Bits8 => 0b10,
354 } << 8;
355 raw
356 }
357
358 pub fn conversion_time_micros(&self) -> u32 {
366 let temperature = self.temperature_resolution.conversion_time_micros();
367 let humidity = self.humidity_resolution.conversion_time_micros();
368 match self.mode {
369 AcquisitionMode::TemperatureThenHumidity => temperature + humidity,
370 AcquisitionMode::Single => temperature.max(humidity),
371 }
372 }
373}
374
375#[derive(Clone, Copy, Debug, PartialEq, Eq)]
378pub struct Measurement {
379 pub temperature: u16,
381 pub humidity: u16,
383}
384
385impl Measurement {
386 pub fn parse(bytes: &[u8; 4]) -> Measurement {
396 Measurement {
397 temperature: u16::from_be_bytes([bytes[0], bytes[1]]),
398 humidity: u16::from_be_bytes([bytes[2], bytes[3]]),
399 }
400 }
401
402 pub fn to_bytes(&self) -> [u8; 4] {
410 let [t_hi, t_lo] = self.temperature.to_be_bytes();
411 let [h_hi, h_lo] = self.humidity.to_be_bytes();
412 [t_hi, t_lo, h_hi, h_lo]
413 }
414
415 pub fn from_physical(milli_celsius: i32, milli_percent: u32) -> Measurement {
427 Measurement {
428 temperature: temperature_register(milli_celsius),
429 humidity: humidity_register(milli_percent),
430 }
431 }
432
433 pub fn milli_celsius(&self) -> i32 {
439 milli_celsius(self.temperature)
440 }
441
442 pub fn celsius(&self) -> f32 {
448 celsius(self.temperature)
449 }
450
451 pub fn milli_percent(&self) -> u32 {
457 milli_percent(self.humidity)
458 }
459
460 pub fn relative_humidity(&self) -> f32 {
466 relative_humidity(self.humidity)
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 fn reference_celsius(raw: u16) -> f64 {
476 raw as f64 / 65_536.0 * 165.0 - 40.0
477 }
478
479 fn reference_humidity(raw: u16) -> f64 {
481 raw as f64 / 65_536.0 * 100.0
482 }
483
484 #[test]
485 fn temperature_formula_holds_at_the_ends_and_mid_scale() {
486 assert_eq!(milli_celsius(0x0000), -40_000);
489 assert_eq!(milli_celsius(0x8000), 42_500);
490 assert_eq!(milli_celsius(0xFFFF), 124_997);
491 assert_eq!(milli_celsius(0x6000), 21_875);
492 assert!((celsius(0x0000) + 40.0).abs() < 1e-5);
493 assert!((celsius(0x8000) - 42.5).abs() < 1e-5);
494 assert!((celsius(0xFFFF) - 124.9975).abs() < 1e-3);
495 }
496
497 #[test]
498 fn humidity_formula_holds_at_the_ends_and_mid_scale() {
499 assert_eq!(milli_percent(0x0000), 0);
502 assert_eq!(milli_percent(0x8000), 50_000);
503 assert_eq!(milli_percent(0xFFFF), 99_998);
504 assert_eq!(milli_percent(0x4000), 25_000);
505 assert!(relative_humidity(0x0000).abs() < 1e-5);
506 assert!((relative_humidity(0x8000) - 50.0).abs() < 1e-5);
507 assert!((relative_humidity(0xFFFF) - 99.9985).abs() < 1e-3);
508 }
509
510 #[test]
511 fn integer_decode_tracks_the_floating_point_reference() {
512 let codes = (0..=0xFFFF_u32).step_by(97).chain([0xFFFF]);
515 for raw in codes.map(|code| code as u16) {
516 let t_ref = reference_celsius(raw) * 1_000.0;
517 let t_int = milli_celsius(raw) as f64;
518 assert!(
519 (t_int - t_ref).abs() <= 0.5,
520 "temperature {raw:#06x}: {t_int} vs {t_ref}"
521 );
522
523 let h_ref = reference_humidity(raw) * 1_000.0;
524 let h_int = milli_percent(raw) as f64;
525 assert!(
526 (h_int - h_ref).abs() <= 0.5,
527 "humidity {raw:#06x}: {h_int} vs {h_ref}"
528 );
529 }
530 }
531
532 #[test]
533 fn register_builders_invert_the_decoders_to_within_a_14_bit_step() {
534 assert_eq!(temperature_register(42_500), 0x8000);
538 assert_eq!(temperature_register(-40_000), 0x0000);
539 assert_eq!(temperature_register(21_875), 0x6000);
540 assert_eq!(humidity_register(50_000), 0x8000);
541 assert_eq!(humidity_register(0), 0x0000);
542 assert_eq!(humidity_register(25_000), 0x4000);
543 for milli_celsius_in in (-40_000..=125_000).step_by(1_234) {
544 let raw = temperature_register(milli_celsius_in);
545 assert_eq!(raw & 0b11, 0);
546 let back = milli_celsius(raw);
547 assert!(
548 (back - milli_celsius_in.min(124_997)).abs() <= 6,
549 "{milli_celsius_in} -> {raw:#06x} -> {back}"
550 );
551 }
552 for milli_percent_in in (0..=100_000).step_by(789) {
553 let raw = humidity_register(milli_percent_in);
554 assert_eq!(raw & 0b11, 0);
555 let back = milli_percent(raw);
556 assert!(
557 (back as i64 - milli_percent_in.min(99_994) as i64).abs() <= 4,
558 "{milli_percent_in} -> {raw:#06x} -> {back}"
559 );
560 }
561 assert_eq!(temperature_register(-100_000), 0x0000);
562 assert_eq!(temperature_register(200_000), 0xFFFC);
563 assert_eq!(humidity_register(150_000), 0xFFFC);
564 }
565
566 #[test]
567 fn identification_registers_read_the_datasheet_values() {
568 assert_eq!(MANUFACTURER_ID, 0x5449);
570 assert_eq!(DEVICE_ID, 0x1050);
571 assert_eq!(I2C_ADDRESS, 0b100_0000);
572 assert_eq!(register::MANUFACTURER_ID, 0xFE);
573 assert_eq!(register::DEVICE_ID, 0xFF);
574 }
575
576 #[test]
577 fn serial_id_reassembles_from_its_three_registers() {
578 assert_eq!(serial_id(0xFFFF, 0xFFFF, 0xFF80), (1 << 41) - 1);
581 assert_eq!(serial_id(0x0001, 0x0000, 0x0000), 1 << 25);
582 assert_eq!(serial_id(0x0000, 0x0001, 0x0000), 1 << 9);
583 assert_eq!(serial_id(0x0000, 0x0000, 0x0080), 1);
584 let serial = 0x0123_4567_89AB;
585 let regs = serial_id_registers(serial);
586 assert_eq!(regs[2] & 0x7F, 0);
587 assert_eq!(serial_id(regs[0], regs[1], regs[2]), serial);
588 }
589
590 #[test]
591 fn configuration_reset_value_matches_the_register_map() {
592 assert_eq!(CONFIGURATION_RESET, 0x1000);
595 assert_eq!(Configuration::default().to_register(), CONFIGURATION_RESET);
596 assert_eq!(
597 Configuration::from_register(CONFIGURATION_RESET),
598 Ok(Configuration::default())
599 );
600 let reset = Configuration::default();
601 assert_eq!(reset.mode, AcquisitionMode::TemperatureThenHumidity);
602 assert_eq!(reset.temperature_resolution, TemperatureResolution::Bits14);
603 assert_eq!(reset.humidity_resolution, HumidityResolution::Bits14);
604 assert!(!reset.heater && !reset.software_reset && !reset.battery_low);
605 }
606
607 #[test]
608 fn configuration_fields_sit_at_the_bits_table_4_gives_them() {
609 let all = Configuration {
612 software_reset: true,
613 heater: true,
614 mode: AcquisitionMode::TemperatureThenHumidity,
615 battery_low: true,
616 temperature_resolution: TemperatureResolution::Bits11,
617 humidity_resolution: HumidityResolution::Bits8,
618 };
619 assert_eq!(all.to_register(), 0xBE00);
620 assert_eq!(Configuration::from_register(0xBE00), Ok(all));
621
622 let eleven = Configuration {
623 humidity_resolution: HumidityResolution::Bits11,
624 ..Configuration::default()
625 };
626 assert_eq!(eleven.to_register(), 0x1100);
627 assert_eq!(Configuration::from_register(0x1100), Ok(eleven));
628
629 let single = Configuration {
630 mode: AcquisitionMode::Single,
631 ..Configuration::default()
632 };
633 assert_eq!(single.to_register(), 0x0000);
634
635 assert_eq!(
637 Configuration::from_register(0x50FF).map(|c| c.to_register()),
638 Ok(0x1000)
639 );
640 }
641
642 #[test]
643 fn an_undefined_humidity_resolution_code_is_rejected() {
644 assert_eq!(
646 Configuration::from_register(0x1300),
647 Err(SensorError::Invalid)
648 );
649 }
650
651 #[test]
652 fn conversion_times_follow_the_electrical_characteristics() {
653 assert_eq!(HumidityResolution::Bits8.conversion_time_micros(), 2_500);
656 assert_eq!(HumidityResolution::Bits11.conversion_time_micros(), 3_850);
657 assert_eq!(HumidityResolution::Bits14.conversion_time_micros(), 6_500);
658 assert_eq!(
659 TemperatureResolution::Bits11.conversion_time_micros(),
660 3_650
661 );
662 assert_eq!(
663 TemperatureResolution::Bits14.conversion_time_micros(),
664 6_350
665 );
666 assert_eq!(Configuration::default().conversion_time_micros(), 12_850);
667 let single = Configuration {
668 mode: AcquisitionMode::Single,
669 humidity_resolution: HumidityResolution::Bits8,
670 ..Configuration::default()
671 };
672 assert_eq!(single.conversion_time_micros(), 6_350);
673 }
674
675 #[test]
676 fn the_combined_frame_is_temperature_then_humidity_msb_first() {
677 let frame = [0x60, 0x00, 0x40, 0x00];
680 let m = Measurement::parse(&frame);
681 assert_eq!(m.temperature, 0x6000);
682 assert_eq!(m.humidity, 0x4000);
683 assert_eq!(m.milli_celsius(), 21_875);
684 assert_eq!(m.milli_percent(), 25_000);
685 assert!((m.celsius() - 21.875).abs() < 1e-5);
686 assert!((m.relative_humidity() - 25.0).abs() < 1e-5);
687 assert_eq!(m.to_bytes(), frame);
688
689 let top = Measurement::parse(&[0xFF, 0xFF, 0xFF, 0xFF]);
690 assert_eq!(
691 (top.milli_celsius(), top.milli_percent()),
692 (124_997, 99_998)
693 );
694
695 let built = Measurement::from_physical(42_500, 50_000);
696 assert_eq!(built.to_bytes(), [0x80, 0x00, 0x80, 0x00]);
697 assert_eq!(Measurement::parse(&built.to_bytes()), built);
698 }
699}