1use pamoja_sensors::{
22 ads1115, bme280, bmp280, ds18b20, hdc1080, ina219, ina226, opt3001, scd4x, sht3x, tmp117,
23 SensorError,
24};
25
26use crate::{read_bytes, set_last_error, PamojaStatus};
27
28pub const PAMOJA_BME280_CALIBRATION_TEMP_PRESS_LEN: usize = 26;
30
31pub const PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN: usize = 7;
33
34pub const PAMOJA_BME280_MEASUREMENT_LEN: usize = 8;
36
37pub const PAMOJA_DS18B20_SCRATCHPAD_LEN: usize = 9;
39
40pub struct PamojaBme280Calibration {
46 calibration: bme280::Calibration,
47}
48
49#[repr(C)]
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct PamojaBme280Measurement {
53 pub celsius: f32,
55 pub pascals: u32,
57 pub hectopascals: f32,
59 pub relative_humidity_percent: f32,
61}
62
63#[repr(C)]
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct PamojaDs18b20Reading {
67 pub raw_temperature: i16,
69 pub micro_celsius: i32,
71 pub alarm_high: i8,
73 pub alarm_low: i8,
75 pub resolution_bits: u8,
77}
78
79#[repr(C)]
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub struct PamojaAds1115Config {
87 pub start_conversion: u8,
89 pub mux: u8,
91 pub pga: u8,
93 pub single_shot: u8,
95 pub data_rate: u8,
97 pub window_comparator: u8,
99 pub comparator_active_high: u8,
101 pub comparator_latching: u8,
103 pub comparator_queue: u8,
105}
106
107#[no_mangle]
121pub unsafe extern "C" fn pamoja_bme280_calibration_new(
122 temp_press: *const u8,
123 temp_press_len: usize,
124 humidity: *const u8,
125 humidity_len: usize,
126 out_calibration: *mut *mut PamojaBme280Calibration,
127) -> PamojaStatus {
128 if out_calibration.is_null() {
129 set_last_error("out_calibration must not be null".to_owned());
130 return PamojaStatus::InvalidArgument;
131 }
132 let slot = &mut *out_calibration;
133 *slot = std::ptr::null_mut();
134
135 let temp_press = match read_bytes(temp_press, temp_press_len) {
136 Ok(bytes) => bytes,
137 Err(status) => return status,
138 };
139 let humidity = match read_bytes(humidity, humidity_len) {
140 Ok(bytes) => bytes,
141 Err(status) => return status,
142 };
143
144 let Ok(temp_press) =
145 <[u8; PAMOJA_BME280_CALIBRATION_TEMP_PRESS_LEN]>::try_from(&temp_press[..])
146 else {
147 return wrong_length("temperature and pressure calibration", 26);
148 };
149 let Ok(humidity) = <[u8; PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN]>::try_from(&humidity[..])
150 else {
151 return wrong_length("humidity calibration", 7);
152 };
153
154 *slot = Box::into_raw(Box::new(PamojaBme280Calibration {
155 calibration: bme280::Calibration::from_registers(&temp_press, &humidity),
156 }));
157 PamojaStatus::Ok
158}
159
160#[no_mangle]
174pub unsafe extern "C" fn pamoja_bme280_compensate(
175 calibration: *const PamojaBme280Calibration,
176 measurement: *const u8,
177 measurement_len: usize,
178 out_measurement: *mut PamojaBme280Measurement,
179) -> PamojaStatus {
180 if calibration.is_null() || out_measurement.is_null() {
181 set_last_error("calibration and out_measurement must not be null".to_owned());
182 return PamojaStatus::InvalidArgument;
183 }
184 let measurement = match read_bytes(measurement, measurement_len) {
185 Ok(bytes) => bytes,
186 Err(status) => return status,
187 };
188 let Ok(registers) = <[u8; PAMOJA_BME280_MEASUREMENT_LEN]>::try_from(&measurement[..]) else {
189 return wrong_length("measurement", 8);
190 };
191
192 let reading = (*calibration)
193 .calibration
194 .compensate(&bme280::RawMeasurement::from_registers(®isters));
195 *out_measurement = PamojaBme280Measurement {
196 celsius: reading.celsius(),
197 pascals: reading.pascals(),
198 hectopascals: reading.hectopascals(),
199 relative_humidity_percent: reading.relative_humidity_percent(),
200 };
201 PamojaStatus::Ok
202}
203
204#[no_mangle]
213pub unsafe extern "C" fn pamoja_bme280_calibration_free(calibration: *mut PamojaBme280Calibration) {
214 if !calibration.is_null() {
215 drop(Box::from_raw(calibration));
216 }
217}
218
219#[no_mangle]
232pub unsafe extern "C" fn pamoja_ds18b20_parse_scratchpad(
233 bytes: *const u8,
234 bytes_len: usize,
235 out_reading: *mut PamojaDs18b20Reading,
236) -> PamojaStatus {
237 if out_reading.is_null() {
238 set_last_error("out_reading must not be null".to_owned());
239 return PamojaStatus::InvalidArgument;
240 }
241 let bytes = match read_bytes(bytes, bytes_len) {
242 Ok(bytes) => bytes,
243 Err(status) => return status,
244 };
245 let Ok(scratchpad) = <[u8; PAMOJA_DS18B20_SCRATCHPAD_LEN]>::try_from(&bytes[..]) else {
246 return wrong_length("scratchpad", 9);
247 };
248
249 match ds18b20::Scratchpad::parse(&scratchpad) {
250 Ok(reading) => {
251 *out_reading = PamojaDs18b20Reading {
252 raw_temperature: reading.raw_temperature(),
253 micro_celsius: reading.temperature_micro_celsius(),
254 alarm_high: reading.alarm_high(),
255 alarm_low: reading.alarm_low(),
256 resolution_bits: reading.resolution().bits(),
257 };
258 PamojaStatus::Ok
259 }
260 Err(error) => failed(error),
261 }
262}
263
264#[no_mangle]
278pub unsafe extern "C" fn pamoja_ds18b20_build_scratchpad(
279 celsius: f32,
280 bits: u8,
281 alarm_high: i8,
282 alarm_low: i8,
283 out_bytes: *mut u8,
284) -> PamojaStatus {
285 if out_bytes.is_null() {
286 set_last_error("out_bytes must not be null".to_owned());
287 return PamojaStatus::InvalidArgument;
288 }
289 let Some(resolution) = resolution(bits) else {
290 return bad_resolution();
291 };
292 let raw = ds18b20::temperature_from_celsius(celsius, resolution);
293 let scratchpad = ds18b20::Scratchpad::new(raw, resolution, alarm_high, alarm_low);
294 core::ptr::copy_nonoverlapping(scratchpad.to_bytes().as_ptr(), out_bytes, 9);
295 PamojaStatus::Ok
296}
297
298#[no_mangle]
309pub unsafe extern "C" fn pamoja_ds18b20_crc8(data: *const u8, data_len: usize) -> u8 {
310 match read_bytes(data, data_len) {
311 Ok(data) => ds18b20::crc8(&data),
312 Err(_) => 0,
313 }
314}
315
316#[no_mangle]
322pub extern "C" fn pamoja_ds18b20_micro_celsius(raw: i16) -> i32 {
323 ds18b20::temperature_to_micro_celsius(raw)
324}
325
326#[no_mangle]
332pub extern "C" fn pamoja_ds18b20_celsius(raw: i16) -> f32 {
333 ds18b20::temperature_to_celsius(raw)
334}
335
336#[no_mangle]
347pub unsafe extern "C" fn pamoja_ds18b20_config_byte(bits: u8, out_byte: *mut u8) -> PamojaStatus {
348 if out_byte.is_null() {
349 set_last_error("out_byte must not be null".to_owned());
350 return PamojaStatus::InvalidArgument;
351 }
352 match resolution(bits) {
353 Some(resolution) => {
354 *out_byte = resolution.config_byte();
355 PamojaStatus::Ok
356 }
357 None => bad_resolution(),
358 }
359}
360
361#[no_mangle]
368pub extern "C" fn pamoja_ds18b20_resolution_bits(config_byte: u8) -> u8 {
369 ds18b20::Resolution::from_config_byte(config_byte).bits()
370}
371
372#[no_mangle]
383pub unsafe extern "C" fn pamoja_ds18b20_step_micro_celsius(
384 bits: u8,
385 out_micro_celsius: *mut u32,
386) -> PamojaStatus {
387 if out_micro_celsius.is_null() {
388 set_last_error("out_micro_celsius must not be null".to_owned());
389 return PamojaStatus::InvalidArgument;
390 }
391 match resolution(bits) {
392 Some(resolution) => {
393 *out_micro_celsius = resolution.step_micro_celsius();
394 PamojaStatus::Ok
395 }
396 None => bad_resolution(),
397 }
398}
399
400#[no_mangle]
412pub unsafe extern "C" fn pamoja_ds18b20_max_conversion_micros(
413 bits: u8,
414 out_micros: *mut u32,
415) -> PamojaStatus {
416 if out_micros.is_null() {
417 set_last_error("out_micros must not be null".to_owned());
418 return PamojaStatus::InvalidArgument;
419 }
420 match resolution(bits) {
421 Some(resolution) => {
422 *out_micros = resolution.max_conversion_micros();
423 PamojaStatus::Ok
424 }
425 None => bad_resolution(),
426 }
427}
428
429#[no_mangle]
435pub extern "C" fn pamoja_ina219_calibration(
436 current_lsb_microamps: u32,
437 shunt_milliohms: u32,
438) -> u16 {
439 ina219::calibration(current_lsb_microamps, shunt_milliohms)
440}
441
442#[no_mangle]
448pub extern "C" fn pamoja_ina219_minimum_current_lsb_microamps(max_expected_microamps: u32) -> u32 {
449 ina219::minimum_current_lsb_microamps(max_expected_microamps)
450}
451
452#[no_mangle]
458pub extern "C" fn pamoja_ina219_shunt_register(microvolts: i32) -> i16 {
459 ina219::shunt_register(microvolts)
460}
461
462#[no_mangle]
468pub extern "C" fn pamoja_ina219_bus_register(millivolts: u32) -> u16 {
469 ina219::bus_register(millivolts)
470}
471
472#[no_mangle]
478pub extern "C" fn pamoja_ina219_current_register(
479 microamps: i32,
480 current_lsb_microamps: u32,
481) -> i16 {
482 ina219::current_register(microamps, current_lsb_microamps)
483}
484
485#[no_mangle]
491pub extern "C" fn pamoja_ina219_power_register(microwatts: u32, current_lsb_microamps: u32) -> u16 {
492 ina219::power_register(microwatts, current_lsb_microamps)
493}
494
495#[no_mangle]
501pub extern "C" fn pamoja_ina219_shunt_microvolts(raw: i16) -> i32 {
502 ina219::shunt_microvolts(raw)
503}
504
505#[no_mangle]
511pub extern "C" fn pamoja_ina219_bus_millivolts(raw: u16) -> u32 {
512 ina219::bus_millivolts(raw)
513}
514
515#[no_mangle]
521pub extern "C" fn pamoja_ina219_conversion_ready(raw: u16) -> bool {
522 ina219::conversion_ready(raw)
523}
524
525#[no_mangle]
532pub extern "C" fn pamoja_ina219_math_overflow(raw: u16) -> bool {
533 ina219::math_overflow(raw)
534}
535
536#[no_mangle]
542pub extern "C" fn pamoja_ina219_current_microamps(raw: i16, current_lsb_microamps: u32) -> i32 {
543 ina219::current_microamps(raw, current_lsb_microamps)
544}
545
546#[no_mangle]
552pub extern "C" fn pamoja_ina219_power_microwatts(raw: u16, current_lsb_microamps: u32) -> u32 {
553 ina219::power_microwatts(raw, current_lsb_microamps)
554}
555
556#[no_mangle]
562pub extern "C" fn pamoja_ads1115_config_bits(config: PamojaAds1115Config) -> u16 {
563 ads1115::Config::from(config).bits()
564}
565
566#[no_mangle]
577pub unsafe extern "C" fn pamoja_ads1115_config_from_bits(
578 bits: u16,
579 out_config: *mut PamojaAds1115Config,
580) -> PamojaStatus {
581 if out_config.is_null() {
582 set_last_error("out_config must not be null".to_owned());
583 return PamojaStatus::InvalidArgument;
584 }
585 *out_config = ads1115::Config::from_bits(bits).into();
586 PamojaStatus::Ok
587}
588
589#[no_mangle]
595pub extern "C" fn pamoja_ads1115_full_scale_microvolts(pga: u8) -> u32 {
596 ads1115::Pga::from_code(pga).full_scale_microvolts()
597}
598
599#[no_mangle]
605pub extern "C" fn pamoja_ads1115_samples_per_second(data_rate: u8) -> u16 {
606 ads1115::DataRate::from_code(data_rate).samples_per_second()
607}
608
609#[no_mangle]
615pub extern "C" fn pamoja_ads1115_to_nanovolts(pga: u8, raw: i16) -> i64 {
616 ads1115::to_nanovolts(ads1115::Pga::from_code(pga), raw)
617}
618
619#[no_mangle]
625pub extern "C" fn pamoja_ads1115_to_volts(pga: u8, raw: i16) -> f32 {
626 ads1115::to_volts(ads1115::Pga::from_code(pga), raw)
627}
628
629pub const PAMOJA_BMP280_CALIBRATION_LEN: usize = 24;
631
632pub const PAMOJA_BMP280_DATA_LEN: usize = 6;
634
635pub const PAMOJA_BMP280_I2C_ADDRESS_PRIMARY: u8 = 0x76;
637
638pub const PAMOJA_BMP280_I2C_ADDRESS_SECONDARY: u8 = 0x77;
640
641pub const PAMOJA_BMP280_CHIP_ID: u8 = 0x58;
643
644pub const PAMOJA_BMP280_RESET_WORD: u8 = 0xB6;
646
647pub const PAMOJA_BMP280_SKIPPED_OUTPUT: u32 = 0x80000;
649
650pub const PAMOJA_BMP280_REGISTER_CALIBRATION: u8 = 0x88;
652
653pub const PAMOJA_BMP280_REGISTER_CHIP_ID: u8 = 0xD0;
655
656pub const PAMOJA_BMP280_REGISTER_RESET: u8 = 0xE0;
658
659pub const PAMOJA_BMP280_REGISTER_STATUS: u8 = 0xF3;
661
662pub const PAMOJA_BMP280_REGISTER_CTRL_MEAS: u8 = 0xF4;
664
665pub const PAMOJA_BMP280_REGISTER_CONFIG: u8 = 0xF5;
667
668pub const PAMOJA_BMP280_REGISTER_DATA: u8 = 0xF7;
670
671const _: () = assert!(PAMOJA_BMP280_CALIBRATION_LEN == bmp280::CALIBRATION_LEN);
674const _: () = assert!(PAMOJA_BMP280_DATA_LEN == bmp280::DATA_LEN);
675const _: () = assert!(PAMOJA_BMP280_I2C_ADDRESS_PRIMARY == bmp280::I2C_ADDRESS_PRIMARY);
676const _: () = assert!(PAMOJA_BMP280_I2C_ADDRESS_SECONDARY == bmp280::I2C_ADDRESS_SECONDARY);
677const _: () = assert!(PAMOJA_BMP280_CHIP_ID == bmp280::CHIP_ID);
678const _: () = assert!(PAMOJA_BMP280_RESET_WORD == bmp280::RESET_WORD);
679const _: () = assert!(PAMOJA_BMP280_SKIPPED_OUTPUT == bmp280::SKIPPED_OUTPUT);
680const _: () = assert!(PAMOJA_BMP280_REGISTER_CALIBRATION == bmp280::register::CALIBRATION);
681const _: () = assert!(PAMOJA_BMP280_REGISTER_CHIP_ID == bmp280::register::CHIP_ID);
682const _: () = assert!(PAMOJA_BMP280_REGISTER_RESET == bmp280::register::RESET);
683const _: () = assert!(PAMOJA_BMP280_REGISTER_STATUS == bmp280::register::STATUS);
684const _: () = assert!(PAMOJA_BMP280_REGISTER_CTRL_MEAS == bmp280::register::CTRL_MEAS);
685const _: () = assert!(PAMOJA_BMP280_REGISTER_CONFIG == bmp280::register::CONFIG);
686const _: () = assert!(PAMOJA_BMP280_REGISTER_DATA == bmp280::register::DATA);
687
688pub struct PamojaBmp280Calibration {
694 calibration: bmp280::Calibration,
695}
696
697#[repr(C)]
699#[derive(Clone, Copy, Debug, PartialEq, Eq)]
700pub struct PamojaBmp280Coefficients {
701 pub dig_t1: u16,
703 pub dig_t2: i16,
705 pub dig_t3: i16,
707 pub dig_p1: u16,
709 pub dig_p2: i16,
711 pub dig_p3: i16,
713 pub dig_p4: i16,
715 pub dig_p5: i16,
717 pub dig_p6: i16,
719 pub dig_p7: i16,
721 pub dig_p8: i16,
723 pub dig_p9: i16,
725}
726
727#[repr(C)]
729#[derive(Clone, Copy, Debug, PartialEq)]
730pub struct PamojaBmp280Reading {
731 pub celsius: f32,
733 pub pascals: u32,
735 pub hectopascals: f32,
737}
738
739#[repr(C)]
741#[derive(Clone, Copy, Debug, PartialEq, Eq)]
742pub struct PamojaBmp280Measurement {
743 pub pressure: u32,
745 pub temperature: u32,
747 pub pressure_skipped: u8,
749 pub temperature_skipped: u8,
751}
752
753#[repr(C)]
755#[derive(Clone, Copy, Debug, PartialEq, Eq)]
756pub struct PamojaBmp280CtrlMeas {
757 pub temperature: u8,
759 pub pressure: u8,
761 pub mode: u8,
763}
764
765#[repr(C)]
767#[derive(Clone, Copy, Debug, PartialEq, Eq)]
768pub struct PamojaBmp280Config {
769 pub standby: u8,
771 pub filter: u8,
773 pub spi_3wire: u8,
775}
776
777#[no_mangle]
790pub unsafe extern "C" fn pamoja_bmp280_calibration_new(
791 bytes: *const u8,
792 bytes_len: usize,
793 out_calibration: *mut *mut PamojaBmp280Calibration,
794) -> PamojaStatus {
795 if out_calibration.is_null() {
796 set_last_error("out_calibration must not be null".to_owned());
797 return PamojaStatus::InvalidArgument;
798 }
799 let slot = &mut *out_calibration;
800 *slot = std::ptr::null_mut();
801
802 let bytes = match read_bytes(bytes, bytes_len) {
803 Ok(bytes) => bytes,
804 Err(status) => return status,
805 };
806 let Ok(registers) = <[u8; PAMOJA_BMP280_CALIBRATION_LEN]>::try_from(&bytes[..]) else {
807 return wrong_length("calibration", PAMOJA_BMP280_CALIBRATION_LEN);
808 };
809
810 *slot = Box::into_raw(Box::new(PamojaBmp280Calibration {
811 calibration: bmp280::Calibration::parse(®isters),
812 }));
813 PamojaStatus::Ok
814}
815
816#[no_mangle]
830pub unsafe extern "C" fn pamoja_bmp280_compensate(
831 calibration: *const PamojaBmp280Calibration,
832 measurement: *const u8,
833 measurement_len: usize,
834 out_reading: *mut PamojaBmp280Reading,
835) -> PamojaStatus {
836 if calibration.is_null() || out_reading.is_null() {
837 set_last_error("calibration and out_reading must not be null".to_owned());
838 return PamojaStatus::InvalidArgument;
839 }
840 let measurement = match read_bytes(measurement, measurement_len) {
841 Ok(bytes) => bytes,
842 Err(status) => return status,
843 };
844 let Ok(registers) = <[u8; PAMOJA_BMP280_DATA_LEN]>::try_from(&measurement[..]) else {
845 return wrong_length("measurement", PAMOJA_BMP280_DATA_LEN);
846 };
847
848 let reading = (*calibration)
849 .calibration
850 .compensate(&bmp280::Measurement::parse(®isters));
851 *out_reading = PamojaBmp280Reading {
852 celsius: reading.celsius(),
853 pascals: reading.pascals(),
854 hectopascals: reading.hectopascals(),
855 };
856 PamojaStatus::Ok
857}
858
859#[no_mangle]
871pub unsafe extern "C" fn pamoja_bmp280_calibration_to_bytes(
872 calibration: *const PamojaBmp280Calibration,
873 out_bytes: *mut u8,
874) -> PamojaStatus {
875 if calibration.is_null() || out_bytes.is_null() {
876 set_last_error("calibration and out_bytes must not be null".to_owned());
877 return PamojaStatus::InvalidArgument;
878 }
879 let bytes = (*calibration).calibration.to_bytes();
880 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_BMP280_CALIBRATION_LEN);
881 PamojaStatus::Ok
882}
883
884#[no_mangle]
896pub unsafe extern "C" fn pamoja_bmp280_calibration_coefficients(
897 calibration: *const PamojaBmp280Calibration,
898 out_coefficients: *mut PamojaBmp280Coefficients,
899) -> PamojaStatus {
900 if calibration.is_null() || out_coefficients.is_null() {
901 set_last_error("calibration and out_coefficients must not be null".to_owned());
902 return PamojaStatus::InvalidArgument;
903 }
904 *out_coefficients = (*calibration).calibration.into();
905 PamojaStatus::Ok
906}
907
908#[no_mangle]
917pub unsafe extern "C" fn pamoja_bmp280_calibration_free(calibration: *mut PamojaBmp280Calibration) {
918 if !calibration.is_null() {
919 drop(Box::from_raw(calibration));
920 }
921}
922
923#[no_mangle]
935pub unsafe extern "C" fn pamoja_bmp280_parse_measurement(
936 data: *const u8,
937 data_len: usize,
938 out_measurement: *mut PamojaBmp280Measurement,
939) -> PamojaStatus {
940 if out_measurement.is_null() {
941 set_last_error("out_measurement must not be null".to_owned());
942 return PamojaStatus::InvalidArgument;
943 }
944 let data = match read_bytes(data, data_len) {
945 Ok(bytes) => bytes,
946 Err(status) => return status,
947 };
948 let Ok(registers) = <[u8; PAMOJA_BMP280_DATA_LEN]>::try_from(&data[..]) else {
949 return wrong_length("measurement", PAMOJA_BMP280_DATA_LEN);
950 };
951 *out_measurement = bmp280::Measurement::parse(®isters).into();
952 PamojaStatus::Ok
953}
954
955#[no_mangle]
966pub unsafe extern "C" fn pamoja_bmp280_measurement_bytes(
967 pressure: u32,
968 temperature: u32,
969 out_bytes: *mut u8,
970) -> PamojaStatus {
971 if out_bytes.is_null() {
972 set_last_error("out_bytes must not be null".to_owned());
973 return PamojaStatus::InvalidArgument;
974 }
975 let measurement = bmp280::Measurement {
976 pressure,
977 temperature,
978 };
979 let bytes = measurement.to_bytes();
980 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_BMP280_DATA_LEN);
981 PamojaStatus::Ok
982}
983
984#[no_mangle]
990pub extern "C" fn pamoja_bmp280_measuring(status: u8) -> bool {
991 bmp280::measuring(status)
992}
993
994#[no_mangle]
1000pub extern "C" fn pamoja_bmp280_image_updating(status: u8) -> bool {
1001 bmp280::image_updating(status)
1002}
1003
1004#[no_mangle]
1010pub extern "C" fn pamoja_bmp280_ctrl_meas_bits(config: PamojaBmp280CtrlMeas) -> u8 {
1011 bmp280::CtrlMeas::from(config).bits()
1012}
1013
1014#[no_mangle]
1025pub unsafe extern "C" fn pamoja_bmp280_ctrl_meas_from_bits(
1026 bits: u8,
1027 out_config: *mut PamojaBmp280CtrlMeas,
1028) -> PamojaStatus {
1029 if out_config.is_null() {
1030 set_last_error("out_config must not be null".to_owned());
1031 return PamojaStatus::InvalidArgument;
1032 }
1033 *out_config = bmp280::CtrlMeas::from_bits(bits).into();
1034 PamojaStatus::Ok
1035}
1036
1037#[no_mangle]
1043pub extern "C" fn pamoja_bmp280_config_bits(config: PamojaBmp280Config) -> u8 {
1044 bmp280::Config::from(config).bits()
1045}
1046
1047#[no_mangle]
1058pub unsafe extern "C" fn pamoja_bmp280_config_from_bits(
1059 bits: u8,
1060 out_config: *mut PamojaBmp280Config,
1061) -> PamojaStatus {
1062 if out_config.is_null() {
1063 set_last_error("out_config must not be null".to_owned());
1064 return PamojaStatus::InvalidArgument;
1065 }
1066 *out_config = bmp280::Config::from_bits(bits).into();
1067 PamojaStatus::Ok
1068}
1069
1070#[no_mangle]
1076pub extern "C" fn pamoja_bmp280_oversampling_factor(code: u8) -> u8 {
1077 bmp280::Oversampling::from_code(code).factor()
1078}
1079
1080#[no_mangle]
1086pub extern "C" fn pamoja_bmp280_standby_micros(code: u8) -> u32 {
1087 bmp280::Standby::from_code(code).microseconds()
1088}
1089
1090pub const PAMOJA_SHT3X_MEASUREMENT_LEN: usize = 6;
1092
1093pub const PAMOJA_SHT3X_WORD_LEN: usize = 3;
1095
1096pub const PAMOJA_SHT3X_I2C_ADDRESS_A: u8 = 0x44;
1098
1099pub const PAMOJA_SHT3X_I2C_ADDRESS_B: u8 = 0x45;
1101
1102pub const PAMOJA_SHT3X_MIN_COMMAND_GAP_MICROS: u32 = 1_000;
1104
1105pub const PAMOJA_SHT3X_STATUS_DEFAULT: u16 = 0x8010;
1107
1108pub const PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_HIGH_STRETCH: u16 = 0x2C06;
1110
1111pub const PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_MEDIUM_STRETCH: u16 = 0x2C0D;
1113
1114pub const PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_LOW_STRETCH: u16 = 0x2C10;
1116
1117pub const PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_HIGH: u16 = 0x2400;
1119
1120pub const PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_MEDIUM: u16 = 0x240B;
1122
1123pub const PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_LOW: u16 = 0x2416;
1125
1126pub const PAMOJA_SHT3X_COMMAND_PERIODIC_HALF_MPS_HIGH: u16 = 0x2032;
1128
1129pub const PAMOJA_SHT3X_COMMAND_PERIODIC_HALF_MPS_MEDIUM: u16 = 0x2024;
1131
1132pub const PAMOJA_SHT3X_COMMAND_PERIODIC_HALF_MPS_LOW: u16 = 0x202F;
1134
1135pub const PAMOJA_SHT3X_COMMAND_PERIODIC_ONE_MPS_HIGH: u16 = 0x2130;
1137
1138pub const PAMOJA_SHT3X_COMMAND_PERIODIC_ONE_MPS_MEDIUM: u16 = 0x2126;
1140
1141pub const PAMOJA_SHT3X_COMMAND_PERIODIC_ONE_MPS_LOW: u16 = 0x212D;
1143
1144pub const PAMOJA_SHT3X_COMMAND_PERIODIC_TWO_MPS_HIGH: u16 = 0x2236;
1146
1147pub const PAMOJA_SHT3X_COMMAND_PERIODIC_TWO_MPS_MEDIUM: u16 = 0x2220;
1149
1150pub const PAMOJA_SHT3X_COMMAND_PERIODIC_TWO_MPS_LOW: u16 = 0x222B;
1152
1153pub const PAMOJA_SHT3X_COMMAND_PERIODIC_FOUR_MPS_HIGH: u16 = 0x2334;
1155
1156pub const PAMOJA_SHT3X_COMMAND_PERIODIC_FOUR_MPS_MEDIUM: u16 = 0x2322;
1158
1159pub const PAMOJA_SHT3X_COMMAND_PERIODIC_FOUR_MPS_LOW: u16 = 0x2329;
1161
1162pub const PAMOJA_SHT3X_COMMAND_PERIODIC_TEN_MPS_HIGH: u16 = 0x2737;
1164
1165pub const PAMOJA_SHT3X_COMMAND_PERIODIC_TEN_MPS_MEDIUM: u16 = 0x2721;
1167
1168pub const PAMOJA_SHT3X_COMMAND_PERIODIC_TEN_MPS_LOW: u16 = 0x272A;
1170
1171pub const PAMOJA_SHT3X_COMMAND_PERIODIC_ART: u16 = 0x2B32;
1173
1174pub const PAMOJA_SHT3X_COMMAND_FETCH_DATA: u16 = 0xE000;
1176
1177pub const PAMOJA_SHT3X_COMMAND_BREAK: u16 = 0x3093;
1179
1180pub const PAMOJA_SHT3X_COMMAND_SOFT_RESET: u16 = 0x30A2;
1182
1183pub const PAMOJA_SHT3X_COMMAND_GENERAL_CALL_RESET: u16 = 0x0006;
1185
1186pub const PAMOJA_SHT3X_COMMAND_HEATER_ENABLE: u16 = 0x306D;
1188
1189pub const PAMOJA_SHT3X_COMMAND_HEATER_DISABLE: u16 = 0x3066;
1191
1192pub const PAMOJA_SHT3X_COMMAND_READ_STATUS: u16 = 0xF32D;
1194
1195pub const PAMOJA_SHT3X_COMMAND_CLEAR_STATUS: u16 = 0x3041;
1197
1198const _: () = assert!(PAMOJA_SHT3X_I2C_ADDRESS_A == sht3x::I2C_ADDRESS_A);
1201const _: () = assert!(PAMOJA_SHT3X_I2C_ADDRESS_B == sht3x::I2C_ADDRESS_B);
1202const _: () = assert!(PAMOJA_SHT3X_MIN_COMMAND_GAP_MICROS == sht3x::MIN_COMMAND_GAP_MICROS);
1203const _: () = assert!(PAMOJA_SHT3X_STATUS_DEFAULT == sht3x::Status::DEFAULT);
1204const _: () = assert!(
1205 PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_HIGH_STRETCH == sht3x::command::SINGLE_SHOT_HIGH_STRETCH
1206);
1207const _: () = assert!(
1208 PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_MEDIUM_STRETCH == sht3x::command::SINGLE_SHOT_MEDIUM_STRETCH
1209);
1210const _: () = assert!(
1211 PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_LOW_STRETCH == sht3x::command::SINGLE_SHOT_LOW_STRETCH
1212);
1213const _: () = assert!(PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_HIGH == sht3x::command::SINGLE_SHOT_HIGH);
1214const _: () =
1215 assert!(PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_MEDIUM == sht3x::command::SINGLE_SHOT_MEDIUM);
1216const _: () = assert!(PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_LOW == sht3x::command::SINGLE_SHOT_LOW);
1217const _: () =
1218 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_HALF_MPS_HIGH == sht3x::command::PERIODIC_0_5_MPS_HIGH);
1219const _: () = assert!(
1220 PAMOJA_SHT3X_COMMAND_PERIODIC_HALF_MPS_MEDIUM == sht3x::command::PERIODIC_0_5_MPS_MEDIUM
1221);
1222const _: () =
1223 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_HALF_MPS_LOW == sht3x::command::PERIODIC_0_5_MPS_LOW);
1224const _: () =
1225 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_ONE_MPS_HIGH == sht3x::command::PERIODIC_1_MPS_HIGH);
1226const _: () =
1227 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_ONE_MPS_MEDIUM == sht3x::command::PERIODIC_1_MPS_MEDIUM);
1228const _: () =
1229 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_ONE_MPS_LOW == sht3x::command::PERIODIC_1_MPS_LOW);
1230const _: () =
1231 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_TWO_MPS_HIGH == sht3x::command::PERIODIC_2_MPS_HIGH);
1232const _: () =
1233 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_TWO_MPS_MEDIUM == sht3x::command::PERIODIC_2_MPS_MEDIUM);
1234const _: () =
1235 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_TWO_MPS_LOW == sht3x::command::PERIODIC_2_MPS_LOW);
1236const _: () =
1237 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_FOUR_MPS_HIGH == sht3x::command::PERIODIC_4_MPS_HIGH);
1238const _: () =
1239 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_FOUR_MPS_MEDIUM == sht3x::command::PERIODIC_4_MPS_MEDIUM);
1240const _: () =
1241 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_FOUR_MPS_LOW == sht3x::command::PERIODIC_4_MPS_LOW);
1242const _: () =
1243 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_TEN_MPS_HIGH == sht3x::command::PERIODIC_10_MPS_HIGH);
1244const _: () =
1245 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_TEN_MPS_MEDIUM == sht3x::command::PERIODIC_10_MPS_MEDIUM);
1246const _: () =
1247 assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_TEN_MPS_LOW == sht3x::command::PERIODIC_10_MPS_LOW);
1248const _: () = assert!(PAMOJA_SHT3X_COMMAND_PERIODIC_ART == sht3x::command::PERIODIC_ART);
1249const _: () = assert!(PAMOJA_SHT3X_COMMAND_FETCH_DATA == sht3x::command::FETCH_DATA);
1250const _: () = assert!(PAMOJA_SHT3X_COMMAND_BREAK == sht3x::command::BREAK);
1251const _: () = assert!(PAMOJA_SHT3X_COMMAND_SOFT_RESET == sht3x::command::SOFT_RESET);
1252const _: () =
1253 assert!(PAMOJA_SHT3X_COMMAND_GENERAL_CALL_RESET == sht3x::command::GENERAL_CALL_RESET);
1254const _: () = assert!(PAMOJA_SHT3X_COMMAND_HEATER_ENABLE == sht3x::command::HEATER_ENABLE);
1255const _: () = assert!(PAMOJA_SHT3X_COMMAND_HEATER_DISABLE == sht3x::command::HEATER_DISABLE);
1256const _: () = assert!(PAMOJA_SHT3X_COMMAND_READ_STATUS == sht3x::command::READ_STATUS);
1257const _: () = assert!(PAMOJA_SHT3X_COMMAND_CLEAR_STATUS == sht3x::command::CLEAR_STATUS);
1258
1259#[repr(C)]
1261#[derive(Clone, Copy, Debug, PartialEq)]
1262pub struct PamojaSht3xMeasurement {
1263 pub temperature_raw: u16,
1265 pub humidity_raw: u16,
1267 pub milli_celsius: i32,
1269 pub celsius: f32,
1271 pub milli_fahrenheit: i32,
1273 pub fahrenheit: f32,
1275 pub milli_percent: u32,
1277 pub relative_humidity: f32,
1279}
1280
1281#[repr(C)]
1283#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1284pub struct PamojaSht3xStatus {
1285 pub bits: u16,
1287 pub alert_pending: u8,
1289 pub heater_on: u8,
1291 pub humidity_tracking_alert: u8,
1293 pub temperature_tracking_alert: u8,
1295 pub reset_detected: u8,
1297 pub command_failed: u8,
1299 pub write_checksum_failed: u8,
1301}
1302
1303#[no_mangle]
1314pub unsafe extern "C" fn pamoja_sht3x_crc(data: *const u8, data_len: usize) -> u8 {
1315 match read_bytes(data, data_len) {
1316 Ok(data) => sht3x::crc(&data),
1317 Err(_) => 0,
1318 }
1319}
1320
1321#[no_mangle]
1333pub unsafe extern "C" fn pamoja_sht3x_word(
1334 frame: *const u8,
1335 frame_len: usize,
1336 out_word: *mut u16,
1337) -> PamojaStatus {
1338 if out_word.is_null() {
1339 set_last_error("out_word must not be null".to_owned());
1340 return PamojaStatus::InvalidArgument;
1341 }
1342 let frame = match read_bytes(frame, frame_len) {
1343 Ok(bytes) => bytes,
1344 Err(status) => return status,
1345 };
1346 let Ok(frame) = <[u8; PAMOJA_SHT3X_WORD_LEN]>::try_from(&frame[..]) else {
1347 return wrong_length("word frame", PAMOJA_SHT3X_WORD_LEN);
1348 };
1349 match sht3x::word(&frame) {
1350 Ok(word) => {
1351 *out_word = word;
1352 PamojaStatus::Ok
1353 }
1354 Err(error) => failed(error),
1355 }
1356}
1357
1358#[no_mangle]
1369pub unsafe extern "C" fn pamoja_sht3x_word_bytes(value: u16, out_bytes: *mut u8) -> PamojaStatus {
1370 if out_bytes.is_null() {
1371 set_last_error("out_bytes must not be null".to_owned());
1372 return PamojaStatus::InvalidArgument;
1373 }
1374 let bytes = sht3x::word_bytes(value);
1375 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SHT3X_WORD_LEN);
1376 PamojaStatus::Ok
1377}
1378
1379#[no_mangle]
1392pub unsafe extern "C" fn pamoja_sht3x_parse_measurement(
1393 frame: *const u8,
1394 frame_len: usize,
1395 out_measurement: *mut PamojaSht3xMeasurement,
1396) -> PamojaStatus {
1397 if out_measurement.is_null() {
1398 set_last_error("out_measurement must not be null".to_owned());
1399 return PamojaStatus::InvalidArgument;
1400 }
1401 let frame = match read_bytes(frame, frame_len) {
1402 Ok(bytes) => bytes,
1403 Err(status) => return status,
1404 };
1405 let Ok(frame) = <[u8; PAMOJA_SHT3X_MEASUREMENT_LEN]>::try_from(&frame[..]) else {
1406 return wrong_length("measurement frame", PAMOJA_SHT3X_MEASUREMENT_LEN);
1407 };
1408 match sht3x::Measurement::parse(&frame) {
1409 Ok(measurement) => {
1410 *out_measurement = measurement.into();
1411 PamojaStatus::Ok
1412 }
1413 Err(error) => failed(error),
1414 }
1415}
1416
1417#[no_mangle]
1428pub unsafe extern "C" fn pamoja_sht3x_measurement_bytes(
1429 temperature_raw: u16,
1430 humidity_raw: u16,
1431 out_bytes: *mut u8,
1432) -> PamojaStatus {
1433 if out_bytes.is_null() {
1434 set_last_error("out_bytes must not be null".to_owned());
1435 return PamojaStatus::InvalidArgument;
1436 }
1437 let measurement = sht3x::Measurement {
1438 temperature_raw,
1439 humidity_raw,
1440 };
1441 let bytes = measurement.to_bytes();
1442 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SHT3X_MEASUREMENT_LEN);
1443 PamojaStatus::Ok
1444}
1445
1446#[no_mangle]
1452pub extern "C" fn pamoja_sht3x_milli_celsius(raw: u16) -> i32 {
1453 sht3x::milli_celsius(raw)
1454}
1455
1456#[no_mangle]
1462pub extern "C" fn pamoja_sht3x_celsius(raw: u16) -> f32 {
1463 sht3x::celsius(raw)
1464}
1465
1466#[no_mangle]
1472pub extern "C" fn pamoja_sht3x_milli_fahrenheit(raw: u16) -> i32 {
1473 sht3x::milli_fahrenheit(raw)
1474}
1475
1476#[no_mangle]
1482pub extern "C" fn pamoja_sht3x_fahrenheit(raw: u16) -> f32 {
1483 sht3x::fahrenheit(raw)
1484}
1485
1486#[no_mangle]
1492pub extern "C" fn pamoja_sht3x_milli_percent(raw: u16) -> u32 {
1493 sht3x::milli_percent(raw)
1494}
1495
1496#[no_mangle]
1502pub extern "C" fn pamoja_sht3x_relative_humidity(raw: u16) -> f32 {
1503 sht3x::relative_humidity(raw)
1504}
1505
1506#[no_mangle]
1512pub extern "C" fn pamoja_sht3x_temperature_raw_from_milli_celsius(milli_celsius: i32) -> u16 {
1513 sht3x::temperature_raw_from_milli_celsius(milli_celsius)
1514}
1515
1516#[no_mangle]
1522pub extern "C" fn pamoja_sht3x_temperature_raw_from_celsius(celsius: f32) -> u16 {
1523 sht3x::temperature_raw_from_celsius(celsius)
1524}
1525
1526#[no_mangle]
1532pub extern "C" fn pamoja_sht3x_temperature_raw_from_milli_fahrenheit(milli_fahrenheit: i32) -> u16 {
1533 sht3x::temperature_raw_from_milli_fahrenheit(milli_fahrenheit)
1534}
1535
1536#[no_mangle]
1542pub extern "C" fn pamoja_sht3x_humidity_raw_from_milli_percent(milli_percent: u32) -> u16 {
1543 sht3x::humidity_raw_from_milli_percent(milli_percent)
1544}
1545
1546#[no_mangle]
1552pub extern "C" fn pamoja_sht3x_humidity_raw_from_relative_humidity(percent: f32) -> u16 {
1553 sht3x::humidity_raw_from_relative_humidity(percent)
1554}
1555
1556#[no_mangle]
1568pub unsafe extern "C" fn pamoja_sht3x_parse_status(
1569 frame: *const u8,
1570 frame_len: usize,
1571 out_status: *mut PamojaSht3xStatus,
1572) -> PamojaStatus {
1573 if out_status.is_null() {
1574 set_last_error("out_status must not be null".to_owned());
1575 return PamojaStatus::InvalidArgument;
1576 }
1577 let frame = match read_bytes(frame, frame_len) {
1578 Ok(bytes) => bytes,
1579 Err(status) => return status,
1580 };
1581 let Ok(frame) = <[u8; PAMOJA_SHT3X_WORD_LEN]>::try_from(&frame[..]) else {
1582 return wrong_length("status frame", PAMOJA_SHT3X_WORD_LEN);
1583 };
1584 match sht3x::Status::parse(&frame) {
1585 Ok(status) => {
1586 *out_status = status.into();
1587 PamojaStatus::Ok
1588 }
1589 Err(error) => failed(error),
1590 }
1591}
1592
1593#[no_mangle]
1604pub unsafe extern "C" fn pamoja_sht3x_status_from_bits(
1605 bits: u16,
1606 out_status: *mut PamojaSht3xStatus,
1607) -> PamojaStatus {
1608 if out_status.is_null() {
1609 set_last_error("out_status must not be null".to_owned());
1610 return PamojaStatus::InvalidArgument;
1611 }
1612 *out_status = sht3x::Status::from_bits(bits).into();
1613 PamojaStatus::Ok
1614}
1615
1616#[no_mangle]
1627pub unsafe extern "C" fn pamoja_sht3x_status_bytes(bits: u16, out_bytes: *mut u8) -> PamojaStatus {
1628 if out_bytes.is_null() {
1629 set_last_error("out_bytes must not be null".to_owned());
1630 return PamojaStatus::InvalidArgument;
1631 }
1632 let bytes = sht3x::Status::from_bits(bits).to_bytes();
1633 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SHT3X_WORD_LEN);
1634 PamojaStatus::Ok
1635}
1636
1637#[no_mangle]
1648pub unsafe extern "C" fn pamoja_sht3x_single_shot(
1649 repeatability: u8,
1650 clock_stretching: bool,
1651 out_command: *mut u16,
1652) -> PamojaStatus {
1653 if out_command.is_null() {
1654 set_last_error("out_command must not be null".to_owned());
1655 return PamojaStatus::InvalidArgument;
1656 }
1657 let Some(repeatability) = repeatability_from_code(repeatability) else {
1658 return bad_repeatability();
1659 };
1660 *out_command = sht3x::single_shot(repeatability, clock_stretching);
1661 PamojaStatus::Ok
1662}
1663
1664#[no_mangle]
1675pub unsafe extern "C" fn pamoja_sht3x_periodic(
1676 repeatability: u8,
1677 rate: u8,
1678 out_command: *mut u16,
1679) -> PamojaStatus {
1680 if out_command.is_null() {
1681 set_last_error("out_command must not be null".to_owned());
1682 return PamojaStatus::InvalidArgument;
1683 }
1684 let Some(repeatability) = repeatability_from_code(repeatability) else {
1685 return bad_repeatability();
1686 };
1687 let Some(rate) = rate_from_code(rate) else {
1688 return bad_rate();
1689 };
1690 *out_command = sht3x::periodic(repeatability, rate);
1691 PamojaStatus::Ok
1692}
1693
1694#[no_mangle]
1705pub unsafe extern "C" fn pamoja_sht3x_max_measurement_micros(
1706 repeatability: u8,
1707 out_micros: *mut u32,
1708) -> PamojaStatus {
1709 if out_micros.is_null() {
1710 set_last_error("out_micros must not be null".to_owned());
1711 return PamojaStatus::InvalidArgument;
1712 }
1713 match repeatability_from_code(repeatability) {
1714 Some(repeatability) => {
1715 *out_micros = repeatability.max_measurement_micros();
1716 PamojaStatus::Ok
1717 }
1718 None => bad_repeatability(),
1719 }
1720}
1721
1722#[no_mangle]
1733pub unsafe extern "C" fn pamoja_sht3x_typical_measurement_micros(
1734 repeatability: u8,
1735 out_micros: *mut u32,
1736) -> PamojaStatus {
1737 if out_micros.is_null() {
1738 set_last_error("out_micros must not be null".to_owned());
1739 return PamojaStatus::InvalidArgument;
1740 }
1741 match repeatability_from_code(repeatability) {
1742 Some(repeatability) => {
1743 *out_micros = repeatability.typical_measurement_micros();
1744 PamojaStatus::Ok
1745 }
1746 None => bad_repeatability(),
1747 }
1748}
1749
1750#[no_mangle]
1761pub unsafe extern "C" fn pamoja_sht3x_interval_micros(
1762 rate: u8,
1763 out_micros: *mut u32,
1764) -> PamojaStatus {
1765 if out_micros.is_null() {
1766 set_last_error("out_micros must not be null".to_owned());
1767 return PamojaStatus::InvalidArgument;
1768 }
1769 match rate_from_code(rate) {
1770 Some(rate) => {
1771 *out_micros = rate.interval_micros();
1772 PamojaStatus::Ok
1773 }
1774 None => bad_rate(),
1775 }
1776}
1777
1778pub const PAMOJA_SCD4X_MEASUREMENT_LEN: usize = 9;
1780
1781pub const PAMOJA_SCD4X_WORD_LEN: usize = 3;
1783
1784pub const PAMOJA_SCD4X_COMMAND_LEN: usize = 2;
1786
1787pub const PAMOJA_SCD4X_WRITE_LEN: usize = 5;
1789
1790pub const PAMOJA_SCD4X_I2C_ADDRESS: u8 = 0x62;
1792
1793pub const PAMOJA_SCD4X_CO2_MAX_PPM: u16 = 40_000;
1795
1796pub const PAMOJA_SCD4X_DEFAULT_TEMPERATURE_OFFSET_MILLI_CELSIUS: u32 = 4_000;
1798
1799pub const PAMOJA_SCD4X_PERIODIC_MEASUREMENT_INTERVAL_MS: u32 = 5_000;
1801
1802pub const PAMOJA_SCD4X_LOW_POWER_PERIODIC_MEASUREMENT_INTERVAL_MS: u32 = 30_000;
1804
1805pub const PAMOJA_SCD4X_POWER_UP_TIME_MS: u32 = 1_000;
1807
1808pub const PAMOJA_SCD4X_FORCED_RECALIBRATION_FAILED: u16 = 0xffff;
1810
1811pub const PAMOJA_SCD4X_COMMAND_START_PERIODIC_MEASUREMENT: u16 = 0x21b1;
1813
1814pub const PAMOJA_SCD4X_COMMAND_READ_MEASUREMENT: u16 = 0xec05;
1816
1817pub const PAMOJA_SCD4X_COMMAND_STOP_PERIODIC_MEASUREMENT: u16 = 0x3f86;
1819
1820pub const PAMOJA_SCD4X_COMMAND_SET_TEMPERATURE_OFFSET: u16 = 0x241d;
1822
1823pub const PAMOJA_SCD4X_COMMAND_GET_TEMPERATURE_OFFSET: u16 = 0x2318;
1825
1826pub const PAMOJA_SCD4X_COMMAND_SET_SENSOR_ALTITUDE: u16 = 0x2427;
1828
1829pub const PAMOJA_SCD4X_COMMAND_GET_SENSOR_ALTITUDE: u16 = 0x2322;
1831
1832pub const PAMOJA_SCD4X_COMMAND_SET_AMBIENT_PRESSURE: u16 = 0xe000;
1834
1835pub const PAMOJA_SCD4X_COMMAND_PERFORM_FORCED_RECALIBRATION: u16 = 0x362f;
1837
1838pub const PAMOJA_SCD4X_COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED: u16 = 0x2416;
1840
1841pub const PAMOJA_SCD4X_COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED: u16 = 0x2313;
1843
1844pub const PAMOJA_SCD4X_COMMAND_START_LOW_POWER_PERIODIC_MEASUREMENT: u16 = 0x21ac;
1846
1847pub const PAMOJA_SCD4X_COMMAND_GET_DATA_READY_STATUS: u16 = 0xe4b8;
1849
1850pub const PAMOJA_SCD4X_COMMAND_PERSIST_SETTINGS: u16 = 0x3615;
1852
1853pub const PAMOJA_SCD4X_COMMAND_GET_SERIAL_NUMBER: u16 = 0x3682;
1855
1856pub const PAMOJA_SCD4X_COMMAND_PERFORM_SELF_TEST: u16 = 0x3639;
1858
1859pub const PAMOJA_SCD4X_COMMAND_PERFORM_FACTORY_RESET: u16 = 0x3632;
1861
1862pub const PAMOJA_SCD4X_COMMAND_REINIT: u16 = 0x3646;
1864
1865pub const PAMOJA_SCD4X_COMMAND_MEASURE_SINGLE_SHOT: u16 = 0x219d;
1867
1868pub const PAMOJA_SCD4X_COMMAND_MEASURE_SINGLE_SHOT_RHT_ONLY: u16 = 0x2196;
1870
1871pub const PAMOJA_SCD4X_COMMAND_POWER_DOWN: u16 = 0x36e0;
1873
1874pub const PAMOJA_SCD4X_COMMAND_WAKE_UP: u16 = 0x36f6;
1876
1877const _: () = assert!(PAMOJA_SCD4X_I2C_ADDRESS == scd4x::I2C_ADDRESS);
1880const _: () = assert!(PAMOJA_SCD4X_CO2_MAX_PPM == scd4x::CO2_MAX_PPM);
1881const _: () = assert!(
1882 PAMOJA_SCD4X_DEFAULT_TEMPERATURE_OFFSET_MILLI_CELSIUS
1883 == scd4x::DEFAULT_TEMPERATURE_OFFSET_MILLI_CELSIUS
1884);
1885const _: () = assert!(
1886 PAMOJA_SCD4X_PERIODIC_MEASUREMENT_INTERVAL_MS == scd4x::PERIODIC_MEASUREMENT_INTERVAL_MS
1887);
1888const _: () = assert!(
1889 PAMOJA_SCD4X_LOW_POWER_PERIODIC_MEASUREMENT_INTERVAL_MS
1890 == scd4x::LOW_POWER_PERIODIC_MEASUREMENT_INTERVAL_MS
1891);
1892const _: () = assert!(PAMOJA_SCD4X_POWER_UP_TIME_MS == scd4x::POWER_UP_TIME_MS);
1893const _: () =
1894 assert!(PAMOJA_SCD4X_FORCED_RECALIBRATION_FAILED == scd4x::FORCED_RECALIBRATION_FAILED);
1895const _: () = assert!(
1896 PAMOJA_SCD4X_COMMAND_START_PERIODIC_MEASUREMENT == scd4x::command::START_PERIODIC_MEASUREMENT
1897);
1898const _: () = assert!(PAMOJA_SCD4X_COMMAND_READ_MEASUREMENT == scd4x::command::READ_MEASUREMENT);
1899const _: () = assert!(
1900 PAMOJA_SCD4X_COMMAND_STOP_PERIODIC_MEASUREMENT == scd4x::command::STOP_PERIODIC_MEASUREMENT
1901);
1902const _: () =
1903 assert!(PAMOJA_SCD4X_COMMAND_SET_TEMPERATURE_OFFSET == scd4x::command::SET_TEMPERATURE_OFFSET);
1904const _: () =
1905 assert!(PAMOJA_SCD4X_COMMAND_GET_TEMPERATURE_OFFSET == scd4x::command::GET_TEMPERATURE_OFFSET);
1906const _: () =
1907 assert!(PAMOJA_SCD4X_COMMAND_SET_SENSOR_ALTITUDE == scd4x::command::SET_SENSOR_ALTITUDE);
1908const _: () =
1909 assert!(PAMOJA_SCD4X_COMMAND_GET_SENSOR_ALTITUDE == scd4x::command::GET_SENSOR_ALTITUDE);
1910const _: () =
1911 assert!(PAMOJA_SCD4X_COMMAND_SET_AMBIENT_PRESSURE == scd4x::command::SET_AMBIENT_PRESSURE);
1912const _: () = assert!(
1913 PAMOJA_SCD4X_COMMAND_PERFORM_FORCED_RECALIBRATION
1914 == scd4x::command::PERFORM_FORCED_RECALIBRATION
1915);
1916const _: () = assert!(
1917 PAMOJA_SCD4X_COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED
1918 == scd4x::command::SET_AUTOMATIC_SELF_CALIBRATION_ENABLED
1919);
1920const _: () = assert!(
1921 PAMOJA_SCD4X_COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED
1922 == scd4x::command::GET_AUTOMATIC_SELF_CALIBRATION_ENABLED
1923);
1924const _: () = assert!(
1925 PAMOJA_SCD4X_COMMAND_START_LOW_POWER_PERIODIC_MEASUREMENT
1926 == scd4x::command::START_LOW_POWER_PERIODIC_MEASUREMENT
1927);
1928const _: () =
1929 assert!(PAMOJA_SCD4X_COMMAND_GET_DATA_READY_STATUS == scd4x::command::GET_DATA_READY_STATUS);
1930const _: () = assert!(PAMOJA_SCD4X_COMMAND_PERSIST_SETTINGS == scd4x::command::PERSIST_SETTINGS);
1931const _: () = assert!(PAMOJA_SCD4X_COMMAND_GET_SERIAL_NUMBER == scd4x::command::GET_SERIAL_NUMBER);
1932const _: () = assert!(PAMOJA_SCD4X_COMMAND_PERFORM_SELF_TEST == scd4x::command::PERFORM_SELF_TEST);
1933const _: () =
1934 assert!(PAMOJA_SCD4X_COMMAND_PERFORM_FACTORY_RESET == scd4x::command::PERFORM_FACTORY_RESET);
1935const _: () = assert!(PAMOJA_SCD4X_COMMAND_REINIT == scd4x::command::REINIT);
1936const _: () =
1937 assert!(PAMOJA_SCD4X_COMMAND_MEASURE_SINGLE_SHOT == scd4x::command::MEASURE_SINGLE_SHOT);
1938const _: () = assert!(
1939 PAMOJA_SCD4X_COMMAND_MEASURE_SINGLE_SHOT_RHT_ONLY
1940 == scd4x::command::MEASURE_SINGLE_SHOT_RHT_ONLY
1941);
1942const _: () = assert!(PAMOJA_SCD4X_COMMAND_POWER_DOWN == scd4x::command::POWER_DOWN);
1943const _: () = assert!(PAMOJA_SCD4X_COMMAND_WAKE_UP == scd4x::command::WAKE_UP);
1944
1945#[repr(C)]
1947#[derive(Clone, Copy, Debug, PartialEq)]
1948pub struct PamojaScd4xMeasurement {
1949 pub co2_ppm: u16,
1951 pub temperature_raw: u16,
1953 pub humidity_raw: u16,
1955 pub milli_celsius: i32,
1957 pub celsius: f32,
1959 pub humidity_milli_percent: u32,
1961 pub relative_humidity_percent: f32,
1963}
1964
1965#[no_mangle]
1976pub unsafe extern "C" fn pamoja_scd4x_crc(data: *const u8, data_len: usize) -> u8 {
1977 match read_bytes(data, data_len) {
1978 Ok(data) => scd4x::crc(&data),
1979 Err(_) => 0,
1980 }
1981}
1982
1983#[no_mangle]
1995pub unsafe extern "C" fn pamoja_scd4x_word(
1996 frame: *const u8,
1997 frame_len: usize,
1998 out_word: *mut u16,
1999) -> PamojaStatus {
2000 if out_word.is_null() {
2001 set_last_error("out_word must not be null".to_owned());
2002 return PamojaStatus::InvalidArgument;
2003 }
2004 let frame = match read_bytes(frame, frame_len) {
2005 Ok(bytes) => bytes,
2006 Err(status) => return status,
2007 };
2008 let Ok(frame) = <[u8; PAMOJA_SCD4X_WORD_LEN]>::try_from(&frame[..]) else {
2009 return wrong_length("word frame", PAMOJA_SCD4X_WORD_LEN);
2010 };
2011 match scd4x::word(&frame) {
2012 Ok(word) => {
2013 *out_word = word;
2014 PamojaStatus::Ok
2015 }
2016 Err(error) => failed(error),
2017 }
2018}
2019
2020#[no_mangle]
2031pub unsafe extern "C" fn pamoja_scd4x_word_frame(value: u16, out_bytes: *mut u8) -> PamojaStatus {
2032 if out_bytes.is_null() {
2033 set_last_error("out_bytes must not be null".to_owned());
2034 return PamojaStatus::InvalidArgument;
2035 }
2036 let bytes = scd4x::word_frame(value);
2037 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SCD4X_WORD_LEN);
2038 PamojaStatus::Ok
2039}
2040
2041#[no_mangle]
2052pub unsafe extern "C" fn pamoja_scd4x_command_frame(
2053 command: u16,
2054 out_bytes: *mut u8,
2055) -> PamojaStatus {
2056 if out_bytes.is_null() {
2057 set_last_error("out_bytes must not be null".to_owned());
2058 return PamojaStatus::InvalidArgument;
2059 }
2060 let bytes = scd4x::command_frame(command);
2061 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SCD4X_COMMAND_LEN);
2062 PamojaStatus::Ok
2063}
2064
2065#[no_mangle]
2076pub unsafe extern "C" fn pamoja_scd4x_write_frame(
2077 command: u16,
2078 value: u16,
2079 out_bytes: *mut u8,
2080) -> PamojaStatus {
2081 if out_bytes.is_null() {
2082 set_last_error("out_bytes must not be null".to_owned());
2083 return PamojaStatus::InvalidArgument;
2084 }
2085 let bytes = scd4x::write_frame(command, value);
2086 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SCD4X_WRITE_LEN);
2087 PamojaStatus::Ok
2088}
2089
2090#[no_mangle]
2101pub unsafe extern "C" fn pamoja_scd4x_max_duration_ms(command: u16, out_millis: *mut u16) -> bool {
2102 match scd4x::max_duration_ms(command) {
2103 Some(millis) if !out_millis.is_null() => {
2104 *out_millis = millis;
2105 true
2106 }
2107 _ => false,
2108 }
2109}
2110
2111#[no_mangle]
2117pub extern "C" fn pamoja_scd4x_allowed_during_measurement(command: u16) -> bool {
2118 scd4x::allowed_during_measurement(command)
2119}
2120
2121#[no_mangle]
2134pub unsafe extern "C" fn pamoja_scd4x_parse_measurement(
2135 frame: *const u8,
2136 frame_len: usize,
2137 out_measurement: *mut PamojaScd4xMeasurement,
2138) -> PamojaStatus {
2139 if out_measurement.is_null() {
2140 set_last_error("out_measurement must not be null".to_owned());
2141 return PamojaStatus::InvalidArgument;
2142 }
2143 let frame = match read_bytes(frame, frame_len) {
2144 Ok(bytes) => bytes,
2145 Err(status) => return status,
2146 };
2147 let Ok(frame) = <[u8; PAMOJA_SCD4X_MEASUREMENT_LEN]>::try_from(&frame[..]) else {
2148 return wrong_length("measurement frame", PAMOJA_SCD4X_MEASUREMENT_LEN);
2149 };
2150 match scd4x::Measurement::parse(&frame) {
2151 Ok(measurement) => {
2152 *out_measurement = measurement.into();
2153 PamojaStatus::Ok
2154 }
2155 Err(error) => failed(error),
2156 }
2157}
2158
2159#[no_mangle]
2170pub unsafe extern "C" fn pamoja_scd4x_measurement_from_physical(
2171 co2_ppm: u16,
2172 milli_celsius: i32,
2173 humidity_milli_percent: u32,
2174 out_measurement: *mut PamojaScd4xMeasurement,
2175) -> PamojaStatus {
2176 if out_measurement.is_null() {
2177 set_last_error("out_measurement must not be null".to_owned());
2178 return PamojaStatus::InvalidArgument;
2179 }
2180 let measurement =
2181 scd4x::Measurement::from_physical(co2_ppm, milli_celsius, humidity_milli_percent);
2182 *out_measurement = measurement.into();
2183 PamojaStatus::Ok
2184}
2185
2186#[no_mangle]
2197pub unsafe extern "C" fn pamoja_scd4x_measurement_bytes(
2198 co2_ppm: u16,
2199 temperature_raw: u16,
2200 humidity_raw: u16,
2201 out_bytes: *mut u8,
2202) -> PamojaStatus {
2203 if out_bytes.is_null() {
2204 set_last_error("out_bytes must not be null".to_owned());
2205 return PamojaStatus::InvalidArgument;
2206 }
2207 let measurement = scd4x::Measurement {
2208 co2_ppm,
2209 temperature_raw,
2210 humidity_raw,
2211 };
2212 let bytes = measurement.to_bytes();
2213 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SCD4X_MEASUREMENT_LEN);
2214 PamojaStatus::Ok
2215}
2216
2217#[no_mangle]
2223pub extern "C" fn pamoja_scd4x_milli_celsius(raw: u16) -> i32 {
2224 scd4x::milli_celsius(raw)
2225}
2226
2227#[no_mangle]
2233pub extern "C" fn pamoja_scd4x_celsius(raw: u16) -> f32 {
2234 scd4x::celsius(raw)
2235}
2236
2237#[no_mangle]
2243pub extern "C" fn pamoja_scd4x_temperature_raw(milli_celsius: i32) -> u16 {
2244 scd4x::temperature_raw(milli_celsius)
2245}
2246
2247#[no_mangle]
2253pub extern "C" fn pamoja_scd4x_humidity_milli_percent(raw: u16) -> u32 {
2254 scd4x::humidity_milli_percent(raw)
2255}
2256
2257#[no_mangle]
2263pub extern "C" fn pamoja_scd4x_relative_humidity_percent(raw: u16) -> f32 {
2264 scd4x::relative_humidity_percent(raw)
2265}
2266
2267#[no_mangle]
2273pub extern "C" fn pamoja_scd4x_humidity_raw(milli_percent: u32) -> u16 {
2274 scd4x::humidity_raw(milli_percent)
2275}
2276
2277#[no_mangle]
2283pub extern "C" fn pamoja_scd4x_data_ready(word: u16) -> bool {
2284 scd4x::data_ready(word)
2285}
2286
2287#[no_mangle]
2294pub extern "C" fn pamoja_scd4x_temperature_offset_word(milli_celsius: u32) -> u16 {
2295 scd4x::temperature_offset_word(milli_celsius)
2296}
2297
2298#[no_mangle]
2304pub extern "C" fn pamoja_scd4x_temperature_offset_milli_celsius(word: u16) -> u32 {
2305 scd4x::temperature_offset_milli_celsius(word)
2306}
2307
2308#[no_mangle]
2314pub extern "C" fn pamoja_scd4x_ambient_pressure_word(pascals: u32) -> u16 {
2315 scd4x::ambient_pressure_word(pascals)
2316}
2317
2318#[no_mangle]
2324pub extern "C" fn pamoja_scd4x_ambient_pressure_pascals(word: u16) -> u32 {
2325 scd4x::ambient_pressure_pascals(word)
2326}
2327
2328#[no_mangle]
2339pub unsafe extern "C" fn pamoja_scd4x_forced_recalibration_correction_ppm(
2340 word: u16,
2341 out_ppm: *mut i32,
2342) -> bool {
2343 match scd4x::forced_recalibration_correction_ppm(word) {
2344 Some(ppm) if !out_ppm.is_null() => {
2345 *out_ppm = ppm;
2346 true
2347 }
2348 _ => false,
2349 }
2350}
2351
2352#[no_mangle]
2358pub extern "C" fn pamoja_scd4x_forced_recalibration_word(
2359 succeeded: bool,
2360 correction_ppm: i32,
2361) -> u16 {
2362 scd4x::forced_recalibration_word(succeeded.then_some(correction_ppm))
2363}
2364
2365#[no_mangle]
2371pub extern "C" fn pamoja_scd4x_automatic_self_calibration_enabled(word: u16) -> bool {
2372 scd4x::automatic_self_calibration_enabled(word)
2373}
2374
2375#[no_mangle]
2381pub extern "C" fn pamoja_scd4x_automatic_self_calibration_word(enabled: bool) -> u16 {
2382 scd4x::automatic_self_calibration_word(enabled)
2383}
2384
2385#[no_mangle]
2391pub extern "C" fn pamoja_scd4x_self_test_passed(word: u16) -> bool {
2392 scd4x::self_test_passed(word)
2393}
2394
2395#[no_mangle]
2407pub unsafe extern "C" fn pamoja_scd4x_serial_number(
2408 frame: *const u8,
2409 frame_len: usize,
2410 out_serial: *mut u64,
2411) -> PamojaStatus {
2412 if out_serial.is_null() {
2413 set_last_error("out_serial must not be null".to_owned());
2414 return PamojaStatus::InvalidArgument;
2415 }
2416 let frame = match read_bytes(frame, frame_len) {
2417 Ok(bytes) => bytes,
2418 Err(status) => return status,
2419 };
2420 let Ok(frame) = <[u8; PAMOJA_SCD4X_MEASUREMENT_LEN]>::try_from(&frame[..]) else {
2421 return wrong_length("serial number frame", PAMOJA_SCD4X_MEASUREMENT_LEN);
2422 };
2423 match scd4x::serial_number(&frame) {
2424 Ok(serial) => {
2425 *out_serial = serial;
2426 PamojaStatus::Ok
2427 }
2428 Err(error) => failed(error),
2429 }
2430}
2431
2432#[no_mangle]
2443pub unsafe extern "C" fn pamoja_scd4x_serial_number_frame(
2444 serial: u64,
2445 out_bytes: *mut u8,
2446) -> PamojaStatus {
2447 if out_bytes.is_null() {
2448 set_last_error("out_bytes must not be null".to_owned());
2449 return PamojaStatus::InvalidArgument;
2450 }
2451 let bytes = scd4x::serial_number_frame(serial);
2452 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_SCD4X_MEASUREMENT_LEN);
2453 PamojaStatus::Ok
2454}
2455
2456pub const PAMOJA_TMP117_REGISTER_LEN: usize = 2;
2458
2459pub const PAMOJA_TMP117_DEVICE_ID: u16 = 0x0117;
2461
2462pub const PAMOJA_TMP117_CONFIG_RESET: u16 = 0x0220;
2464
2465pub const PAMOJA_TMP117_HIGH_LIMIT_RESET: u16 = 0x6000;
2467
2468pub const PAMOJA_TMP117_LOW_LIMIT_RESET: u16 = 0x8000;
2470
2471pub const PAMOJA_TMP117_TEMP_RESULT_RESET: u16 = 0x8000;
2473
2474pub const PAMOJA_TMP117_GENERAL_CALL_RESET: u8 = 0x06;
2476
2477pub const PAMOJA_TMP117_EEPROM_UNLOCK: u16 = 0x8000;
2479
2480pub const PAMOJA_TMP117_ADDRESS_ADD0_GND: u8 = 0x48;
2482
2483pub const PAMOJA_TMP117_ADDRESS_ADD0_VPLUS: u8 = 0x49;
2485
2486pub const PAMOJA_TMP117_ADDRESS_ADD0_SDA: u8 = 0x4A;
2488
2489pub const PAMOJA_TMP117_ADDRESS_ADD0_SCL: u8 = 0x4B;
2491
2492pub const PAMOJA_TMP117_REGISTER_TEMP_RESULT: u8 = 0x00;
2494
2495pub const PAMOJA_TMP117_REGISTER_CONFIGURATION: u8 = 0x01;
2497
2498pub const PAMOJA_TMP117_REGISTER_THIGH_LIMIT: u8 = 0x02;
2500
2501pub const PAMOJA_TMP117_REGISTER_TLOW_LIMIT: u8 = 0x03;
2503
2504pub const PAMOJA_TMP117_REGISTER_EEPROM_UL: u8 = 0x04;
2506
2507pub const PAMOJA_TMP117_REGISTER_EEPROM1: u8 = 0x05;
2509
2510pub const PAMOJA_TMP117_REGISTER_EEPROM2: u8 = 0x06;
2512
2513pub const PAMOJA_TMP117_REGISTER_TEMP_OFFSET: u8 = 0x07;
2515
2516pub const PAMOJA_TMP117_REGISTER_EEPROM3: u8 = 0x08;
2518
2519pub const PAMOJA_TMP117_REGISTER_DEVICE_ID: u8 = 0x0F;
2521
2522const _: () = assert!(PAMOJA_TMP117_DEVICE_ID == tmp117::DEVICE_ID);
2525const _: () = assert!(PAMOJA_TMP117_CONFIG_RESET == tmp117::CONFIG_RESET);
2526const _: () = assert!(PAMOJA_TMP117_HIGH_LIMIT_RESET == tmp117::HIGH_LIMIT_RESET);
2527const _: () = assert!(PAMOJA_TMP117_LOW_LIMIT_RESET == tmp117::LOW_LIMIT_RESET);
2528const _: () = assert!(PAMOJA_TMP117_TEMP_RESULT_RESET == tmp117::TEMP_RESULT_RESET);
2529const _: () = assert!(PAMOJA_TMP117_GENERAL_CALL_RESET == tmp117::GENERAL_CALL_RESET);
2530const _: () = assert!(PAMOJA_TMP117_EEPROM_UNLOCK == tmp117::EEPROM_UNLOCK);
2531const _: () = assert!(PAMOJA_TMP117_ADDRESS_ADD0_GND == tmp117::address::ADD0_GND);
2532const _: () = assert!(PAMOJA_TMP117_ADDRESS_ADD0_VPLUS == tmp117::address::ADD0_VPLUS);
2533const _: () = assert!(PAMOJA_TMP117_ADDRESS_ADD0_SDA == tmp117::address::ADD0_SDA);
2534const _: () = assert!(PAMOJA_TMP117_ADDRESS_ADD0_SCL == tmp117::address::ADD0_SCL);
2535const _: () = assert!(PAMOJA_TMP117_REGISTER_TEMP_RESULT == tmp117::register::TEMP_RESULT);
2536const _: () = assert!(PAMOJA_TMP117_REGISTER_CONFIGURATION == tmp117::register::CONFIGURATION);
2537const _: () = assert!(PAMOJA_TMP117_REGISTER_THIGH_LIMIT == tmp117::register::THIGH_LIMIT);
2538const _: () = assert!(PAMOJA_TMP117_REGISTER_TLOW_LIMIT == tmp117::register::TLOW_LIMIT);
2539const _: () = assert!(PAMOJA_TMP117_REGISTER_EEPROM_UL == tmp117::register::EEPROM_UL);
2540const _: () = assert!(PAMOJA_TMP117_REGISTER_EEPROM1 == tmp117::register::EEPROM1);
2541const _: () = assert!(PAMOJA_TMP117_REGISTER_EEPROM2 == tmp117::register::EEPROM2);
2542const _: () = assert!(PAMOJA_TMP117_REGISTER_TEMP_OFFSET == tmp117::register::TEMP_OFFSET);
2543const _: () = assert!(PAMOJA_TMP117_REGISTER_EEPROM3 == tmp117::register::EEPROM3);
2544const _: () = assert!(PAMOJA_TMP117_REGISTER_DEVICE_ID == tmp117::register::DEVICE_ID);
2545
2546#[repr(C)]
2548#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2549pub struct PamojaTmp117Config {
2550 pub high_alert: u8,
2552 pub low_alert: u8,
2554 pub data_ready: u8,
2556 pub eeprom_busy: u8,
2558 pub mode: u8,
2560 pub cycle: u8,
2562 pub averaging: u8,
2564 pub therm_mode: u8,
2566 pub alert_active_high: u8,
2568 pub alert_pin_data_ready: u8,
2570 pub soft_reset: u8,
2572}
2573
2574#[no_mangle]
2580pub extern "C" fn pamoja_tmp117_nano_celsius(raw: i16) -> i64 {
2581 tmp117::nano_celsius(raw)
2582}
2583
2584#[no_mangle]
2590pub extern "C" fn pamoja_tmp117_micro_celsius(raw: i16) -> i32 {
2591 tmp117::micro_celsius(raw)
2592}
2593
2594#[no_mangle]
2600pub extern "C" fn pamoja_tmp117_celsius(raw: i16) -> f32 {
2601 tmp117::celsius(raw)
2602}
2603
2604#[no_mangle]
2610pub extern "C" fn pamoja_tmp117_raw_from_micro_celsius(micro_celsius: i32) -> i16 {
2611 tmp117::raw_from_micro_celsius(micro_celsius)
2612}
2613
2614#[no_mangle]
2620pub extern "C" fn pamoja_tmp117_raw_from_celsius(celsius: f32) -> i16 {
2621 tmp117::raw_from_celsius(celsius)
2622}
2623
2624#[no_mangle]
2635pub unsafe extern "C" fn pamoja_tmp117_temperature_bytes(
2636 raw: i16,
2637 out_bytes: *mut u8,
2638) -> PamojaStatus {
2639 if out_bytes.is_null() {
2640 set_last_error("out_bytes must not be null".to_owned());
2641 return PamojaStatus::InvalidArgument;
2642 }
2643 let bytes = tmp117::temperature_bytes(raw);
2644 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_TMP117_REGISTER_LEN);
2645 PamojaStatus::Ok
2646}
2647
2648#[no_mangle]
2660pub unsafe extern "C" fn pamoja_tmp117_temperature_from_bytes(
2661 bytes: *const u8,
2662 bytes_len: usize,
2663 out_raw: *mut i16,
2664) -> PamojaStatus {
2665 if out_raw.is_null() {
2666 set_last_error("out_raw must not be null".to_owned());
2667 return PamojaStatus::InvalidArgument;
2668 }
2669 let bytes = match read_bytes(bytes, bytes_len) {
2670 Ok(bytes) => bytes,
2671 Err(status) => return status,
2672 };
2673 let Ok(bytes) = <[u8; PAMOJA_TMP117_REGISTER_LEN]>::try_from(&bytes[..]) else {
2674 return wrong_length("temperature register", PAMOJA_TMP117_REGISTER_LEN);
2675 };
2676 *out_raw = tmp117::temperature_from_bytes(bytes);
2677 PamojaStatus::Ok
2678}
2679
2680#[no_mangle]
2686pub extern "C" fn pamoja_tmp117_device_id(raw: u16) -> u16 {
2687 tmp117::device_id(raw)
2688}
2689
2690#[no_mangle]
2696pub extern "C" fn pamoja_tmp117_revision(raw: u16) -> u8 {
2697 tmp117::revision(raw)
2698}
2699
2700#[no_mangle]
2706pub extern "C" fn pamoja_tmp117_high_alert(config: u16) -> bool {
2707 tmp117::high_alert(config)
2708}
2709
2710#[no_mangle]
2716pub extern "C" fn pamoja_tmp117_low_alert(config: u16) -> bool {
2717 tmp117::low_alert(config)
2718}
2719
2720#[no_mangle]
2726pub extern "C" fn pamoja_tmp117_data_ready(config: u16) -> bool {
2727 tmp117::data_ready(config)
2728}
2729
2730#[no_mangle]
2736pub extern "C" fn pamoja_tmp117_eeprom_busy(config: u16) -> bool {
2737 tmp117::eeprom_busy(config)
2738}
2739
2740#[no_mangle]
2746pub extern "C" fn pamoja_tmp117_eeprom_unlock_busy(unlock: u16) -> bool {
2747 tmp117::eeprom_unlock_busy(unlock)
2748}
2749
2750#[no_mangle]
2756pub extern "C" fn pamoja_tmp117_config_bits(config: PamojaTmp117Config) -> u16 {
2757 tmp117::Configuration::from(config).bits()
2758}
2759
2760#[no_mangle]
2771pub unsafe extern "C" fn pamoja_tmp117_config_from_bits(
2772 bits: u16,
2773 out_config: *mut PamojaTmp117Config,
2774) -> PamojaStatus {
2775 if out_config.is_null() {
2776 set_last_error("out_config must not be null".to_owned());
2777 return PamojaStatus::InvalidArgument;
2778 }
2779 *out_config = tmp117::Configuration::from_bits(bits).into();
2780 PamojaStatus::Ok
2781}
2782
2783#[no_mangle]
2789pub extern "C" fn pamoja_tmp117_averaging_conversions(code: u8) -> u8 {
2790 tmp117::Averaging::from_code(code).conversions()
2791}
2792
2793#[no_mangle]
2799pub extern "C" fn pamoja_tmp117_averaging_micros(code: u8) -> u32 {
2800 tmp117::Averaging::from_code(code).conversion_micros()
2801}
2802
2803#[no_mangle]
2809pub extern "C" fn pamoja_tmp117_cycle_nominal_micros(code: u8) -> u32 {
2810 tmp117::ConversionCycle::from_code(code).nominal_micros()
2811}
2812
2813#[no_mangle]
2819pub extern "C" fn pamoja_tmp117_cycle_micros(cycle: u8, averaging: u8) -> u32 {
2820 let averaging = tmp117::Averaging::from_code(averaging);
2821 tmp117::ConversionCycle::from_code(cycle).cycle_micros(averaging)
2822}
2823
2824pub const PAMOJA_HDC1080_MEASUREMENT_LEN: usize = 4;
2826
2827pub const PAMOJA_HDC1080_SERIAL_ID_REGISTERS: usize = 3;
2829
2830pub const PAMOJA_HDC1080_I2C_ADDRESS: u8 = 0x40;
2832
2833pub const PAMOJA_HDC1080_MANUFACTURER_ID: u16 = 0x5449;
2835
2836pub const PAMOJA_HDC1080_DEVICE_ID: u16 = 0x1050;
2838
2839pub const PAMOJA_HDC1080_CONFIGURATION_RESET: u16 = 0x1000;
2841
2842pub const PAMOJA_HDC1080_REGISTER_TEMPERATURE: u8 = 0x00;
2844
2845pub const PAMOJA_HDC1080_REGISTER_HUMIDITY: u8 = 0x01;
2847
2848pub const PAMOJA_HDC1080_REGISTER_CONFIGURATION: u8 = 0x02;
2850
2851pub const PAMOJA_HDC1080_REGISTER_SERIAL_ID_HIGH: u8 = 0xFB;
2853
2854pub const PAMOJA_HDC1080_REGISTER_SERIAL_ID_MID: u8 = 0xFC;
2856
2857pub const PAMOJA_HDC1080_REGISTER_SERIAL_ID_LOW: u8 = 0xFD;
2859
2860pub const PAMOJA_HDC1080_REGISTER_MANUFACTURER_ID: u8 = 0xFE;
2862
2863pub const PAMOJA_HDC1080_REGISTER_DEVICE_ID: u8 = 0xFF;
2865
2866const _: () = assert!(PAMOJA_HDC1080_I2C_ADDRESS == hdc1080::I2C_ADDRESS);
2869const _: () = assert!(PAMOJA_HDC1080_MANUFACTURER_ID == hdc1080::MANUFACTURER_ID);
2870const _: () = assert!(PAMOJA_HDC1080_DEVICE_ID == hdc1080::DEVICE_ID);
2871const _: () = assert!(PAMOJA_HDC1080_CONFIGURATION_RESET == hdc1080::CONFIGURATION_RESET);
2872const _: () = assert!(PAMOJA_HDC1080_REGISTER_TEMPERATURE == hdc1080::register::TEMPERATURE);
2873const _: () = assert!(PAMOJA_HDC1080_REGISTER_HUMIDITY == hdc1080::register::HUMIDITY);
2874const _: () = assert!(PAMOJA_HDC1080_REGISTER_CONFIGURATION == hdc1080::register::CONFIGURATION);
2875const _: () = assert!(PAMOJA_HDC1080_REGISTER_SERIAL_ID_HIGH == hdc1080::register::SERIAL_ID_HIGH);
2876const _: () = assert!(PAMOJA_HDC1080_REGISTER_SERIAL_ID_MID == hdc1080::register::SERIAL_ID_MID);
2877const _: () = assert!(PAMOJA_HDC1080_REGISTER_SERIAL_ID_LOW == hdc1080::register::SERIAL_ID_LOW);
2878const _: () =
2879 assert!(PAMOJA_HDC1080_REGISTER_MANUFACTURER_ID == hdc1080::register::MANUFACTURER_ID);
2880const _: () = assert!(PAMOJA_HDC1080_REGISTER_DEVICE_ID == hdc1080::register::DEVICE_ID);
2881
2882#[repr(C)]
2884#[derive(Clone, Copy, Debug, PartialEq)]
2885pub struct PamojaHdc1080Measurement {
2886 pub temperature_raw: u16,
2888 pub humidity_raw: u16,
2890 pub milli_celsius: i32,
2892 pub celsius: f32,
2894 pub milli_percent: u32,
2896 pub relative_humidity: f32,
2898}
2899
2900#[repr(C)]
2902#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2903pub struct PamojaHdc1080Config {
2904 pub software_reset: u8,
2906 pub heater: u8,
2908 pub sequential: u8,
2910 pub battery_low: u8,
2912 pub temperature_resolution_bits: u8,
2914 pub humidity_resolution_bits: u8,
2916}
2917
2918#[no_mangle]
2924pub extern "C" fn pamoja_hdc1080_milli_celsius(raw: u16) -> i32 {
2925 hdc1080::milli_celsius(raw)
2926}
2927
2928#[no_mangle]
2934pub extern "C" fn pamoja_hdc1080_celsius(raw: u16) -> f32 {
2935 hdc1080::celsius(raw)
2936}
2937
2938#[no_mangle]
2944pub extern "C" fn pamoja_hdc1080_milli_percent(raw: u16) -> u32 {
2945 hdc1080::milli_percent(raw)
2946}
2947
2948#[no_mangle]
2954pub extern "C" fn pamoja_hdc1080_relative_humidity(raw: u16) -> f32 {
2955 hdc1080::relative_humidity(raw)
2956}
2957
2958#[no_mangle]
2964pub extern "C" fn pamoja_hdc1080_temperature_register(milli_celsius: i32) -> u16 {
2965 hdc1080::temperature_register(milli_celsius)
2966}
2967
2968#[no_mangle]
2974pub extern "C" fn pamoja_hdc1080_humidity_register(milli_percent: u32) -> u16 {
2975 hdc1080::humidity_register(milli_percent)
2976}
2977
2978#[no_mangle]
2984pub extern "C" fn pamoja_hdc1080_serial_id(high: u16, mid: u16, low: u16) -> u64 {
2985 hdc1080::serial_id(high, mid, low)
2986}
2987
2988#[no_mangle]
3000pub unsafe extern "C" fn pamoja_hdc1080_serial_id_registers(
3001 serial: u64,
3002 out_registers: *mut u16,
3003) -> PamojaStatus {
3004 if out_registers.is_null() {
3005 set_last_error("out_registers must not be null".to_owned());
3006 return PamojaStatus::InvalidArgument;
3007 }
3008 let registers = hdc1080::serial_id_registers(serial);
3009 core::ptr::copy_nonoverlapping(
3010 registers.as_ptr(),
3011 out_registers,
3012 PAMOJA_HDC1080_SERIAL_ID_REGISTERS,
3013 );
3014 PamojaStatus::Ok
3015}
3016
3017#[no_mangle]
3030pub unsafe extern "C" fn pamoja_hdc1080_parse_measurement(
3031 bytes: *const u8,
3032 bytes_len: usize,
3033 out_measurement: *mut PamojaHdc1080Measurement,
3034) -> PamojaStatus {
3035 if out_measurement.is_null() {
3036 set_last_error("out_measurement must not be null".to_owned());
3037 return PamojaStatus::InvalidArgument;
3038 }
3039 let bytes = match read_bytes(bytes, bytes_len) {
3040 Ok(bytes) => bytes,
3041 Err(status) => return status,
3042 };
3043 let Ok(bytes) = <[u8; PAMOJA_HDC1080_MEASUREMENT_LEN]>::try_from(&bytes[..]) else {
3044 return wrong_length("measurement", PAMOJA_HDC1080_MEASUREMENT_LEN);
3045 };
3046 *out_measurement = hdc1080::Measurement::parse(&bytes).into();
3047 PamojaStatus::Ok
3048}
3049
3050#[no_mangle]
3061pub unsafe extern "C" fn pamoja_hdc1080_measurement_from_physical(
3062 milli_celsius: i32,
3063 milli_percent: u32,
3064 out_measurement: *mut PamojaHdc1080Measurement,
3065) -> PamojaStatus {
3066 if out_measurement.is_null() {
3067 set_last_error("out_measurement must not be null".to_owned());
3068 return PamojaStatus::InvalidArgument;
3069 }
3070 let measurement = hdc1080::Measurement::from_physical(milli_celsius, milli_percent);
3071 *out_measurement = measurement.into();
3072 PamojaStatus::Ok
3073}
3074
3075#[no_mangle]
3086pub unsafe extern "C" fn pamoja_hdc1080_measurement_bytes(
3087 temperature_raw: u16,
3088 humidity_raw: u16,
3089 out_bytes: *mut u8,
3090) -> PamojaStatus {
3091 if out_bytes.is_null() {
3092 set_last_error("out_bytes must not be null".to_owned());
3093 return PamojaStatus::InvalidArgument;
3094 }
3095 let measurement = hdc1080::Measurement {
3096 temperature: temperature_raw,
3097 humidity: humidity_raw,
3098 };
3099 let bytes = measurement.to_bytes();
3100 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_HDC1080_MEASUREMENT_LEN);
3101 PamojaStatus::Ok
3102}
3103
3104#[no_mangle]
3117pub unsafe extern "C" fn pamoja_hdc1080_config_from_register(
3118 raw: u16,
3119 out_config: *mut PamojaHdc1080Config,
3120) -> PamojaStatus {
3121 if out_config.is_null() {
3122 set_last_error("out_config must not be null".to_owned());
3123 return PamojaStatus::InvalidArgument;
3124 }
3125 match hdc1080::Configuration::from_register(raw) {
3126 Ok(config) => {
3127 *out_config = config.into();
3128 PamojaStatus::Ok
3129 }
3130 Err(error) => failed(error),
3131 }
3132}
3133
3134#[no_mangle]
3146pub unsafe extern "C" fn pamoja_hdc1080_config_to_register(
3147 config: PamojaHdc1080Config,
3148 out_register: *mut u16,
3149) -> PamojaStatus {
3150 if out_register.is_null() {
3151 set_last_error("out_register must not be null".to_owned());
3152 return PamojaStatus::InvalidArgument;
3153 }
3154 match hdc1080::Configuration::try_from(config) {
3155 Ok(config) => {
3156 *out_register = config.to_register();
3157 PamojaStatus::Ok
3158 }
3159 Err(status) => status,
3160 }
3161}
3162
3163#[no_mangle]
3175pub unsafe extern "C" fn pamoja_hdc1080_conversion_time_micros(
3176 config: PamojaHdc1080Config,
3177 out_micros: *mut u32,
3178) -> PamojaStatus {
3179 if out_micros.is_null() {
3180 set_last_error("out_micros must not be null".to_owned());
3181 return PamojaStatus::InvalidArgument;
3182 }
3183 match hdc1080::Configuration::try_from(config) {
3184 Ok(config) => {
3185 *out_micros = config.conversion_time_micros();
3186 PamojaStatus::Ok
3187 }
3188 Err(status) => status,
3189 }
3190}
3191
3192#[no_mangle]
3203pub unsafe extern "C" fn pamoja_hdc1080_temperature_conversion_micros(
3204 bits: u8,
3205 out_micros: *mut u32,
3206) -> PamojaStatus {
3207 if out_micros.is_null() {
3208 set_last_error("out_micros must not be null".to_owned());
3209 return PamojaStatus::InvalidArgument;
3210 }
3211 match temperature_resolution(bits) {
3212 Some(resolution) => {
3213 *out_micros = resolution.conversion_time_micros();
3214 PamojaStatus::Ok
3215 }
3216 None => bad_temperature_resolution(),
3217 }
3218}
3219
3220#[no_mangle]
3231pub unsafe extern "C" fn pamoja_hdc1080_humidity_conversion_micros(
3232 bits: u8,
3233 out_micros: *mut u32,
3234) -> PamojaStatus {
3235 if out_micros.is_null() {
3236 set_last_error("out_micros must not be null".to_owned());
3237 return PamojaStatus::InvalidArgument;
3238 }
3239 match humidity_resolution(bits) {
3240 Some(resolution) => {
3241 *out_micros = resolution.conversion_time_micros();
3242 PamojaStatus::Ok
3243 }
3244 None => bad_humidity_resolution(),
3245 }
3246}
3247
3248pub const PAMOJA_OPT3001_REGISTER_LEN: usize = 2;
3250
3251pub const PAMOJA_OPT3001_I2C_ADDRESS_GND: u8 = 0x44;
3253
3254pub const PAMOJA_OPT3001_I2C_ADDRESS_VDD: u8 = 0x45;
3256
3257pub const PAMOJA_OPT3001_I2C_ADDRESS_SDA: u8 = 0x46;
3259
3260pub const PAMOJA_OPT3001_I2C_ADDRESS_SCL: u8 = 0x47;
3262
3263pub const PAMOJA_OPT3001_MANUFACTURER_ID: u16 = 0x5449;
3265
3266pub const PAMOJA_OPT3001_DEVICE_ID: u16 = 0x3001;
3268
3269pub const PAMOJA_OPT3001_CONFIGURATION_RESET: u16 = 0xC810;
3271
3272pub const PAMOJA_OPT3001_LOW_LIMIT_RESET: u16 = 0x0000;
3274
3275pub const PAMOJA_OPT3001_HIGH_LIMIT_RESET: u16 = 0xBFFF;
3277
3278pub const PAMOJA_OPT3001_LOW_LIMIT_END_OF_CONVERSION: u16 = 0xC000;
3280
3281pub const PAMOJA_OPT3001_RANGE_AUTOMATIC: u8 = 0b1100;
3283
3284pub const PAMOJA_OPT3001_RANGE_MAX: u8 = 11;
3286
3287pub const PAMOJA_OPT3001_REGISTER_RESULT: u8 = 0x00;
3289
3290pub const PAMOJA_OPT3001_REGISTER_CONFIGURATION: u8 = 0x01;
3292
3293pub const PAMOJA_OPT3001_REGISTER_LOW_LIMIT: u8 = 0x02;
3295
3296pub const PAMOJA_OPT3001_REGISTER_HIGH_LIMIT: u8 = 0x03;
3298
3299pub const PAMOJA_OPT3001_REGISTER_MANUFACTURER_ID: u8 = 0x7E;
3301
3302pub const PAMOJA_OPT3001_REGISTER_DEVICE_ID: u8 = 0x7F;
3304
3305const _: () = assert!(PAMOJA_OPT3001_I2C_ADDRESS_GND == opt3001::I2C_ADDRESS_GND);
3308const _: () = assert!(PAMOJA_OPT3001_I2C_ADDRESS_VDD == opt3001::I2C_ADDRESS_VDD);
3309const _: () = assert!(PAMOJA_OPT3001_I2C_ADDRESS_SDA == opt3001::I2C_ADDRESS_SDA);
3310const _: () = assert!(PAMOJA_OPT3001_I2C_ADDRESS_SCL == opt3001::I2C_ADDRESS_SCL);
3311const _: () = assert!(PAMOJA_OPT3001_MANUFACTURER_ID == opt3001::MANUFACTURER_ID);
3312const _: () = assert!(PAMOJA_OPT3001_DEVICE_ID == opt3001::DEVICE_ID);
3313const _: () = assert!(PAMOJA_OPT3001_CONFIGURATION_RESET == opt3001::CONFIGURATION_RESET);
3314const _: () = assert!(PAMOJA_OPT3001_LOW_LIMIT_RESET == opt3001::LOW_LIMIT_RESET);
3315const _: () = assert!(PAMOJA_OPT3001_HIGH_LIMIT_RESET == opt3001::HIGH_LIMIT_RESET);
3316const _: () =
3317 assert!(PAMOJA_OPT3001_LOW_LIMIT_END_OF_CONVERSION == opt3001::LOW_LIMIT_END_OF_CONVERSION);
3318const _: () = assert!(PAMOJA_OPT3001_RANGE_AUTOMATIC == opt3001::RANGE_AUTOMATIC);
3319const _: () = assert!(PAMOJA_OPT3001_RANGE_MAX == opt3001::RANGE_MAX);
3320const _: () = assert!(PAMOJA_OPT3001_REGISTER_RESULT == opt3001::register::RESULT);
3321const _: () = assert!(PAMOJA_OPT3001_REGISTER_CONFIGURATION == opt3001::register::CONFIGURATION);
3322const _: () = assert!(PAMOJA_OPT3001_REGISTER_LOW_LIMIT == opt3001::register::LOW_LIMIT);
3323const _: () = assert!(PAMOJA_OPT3001_REGISTER_HIGH_LIMIT == opt3001::register::HIGH_LIMIT);
3324const _: () =
3325 assert!(PAMOJA_OPT3001_REGISTER_MANUFACTURER_ID == opt3001::register::MANUFACTURER_ID);
3326const _: () = assert!(PAMOJA_OPT3001_REGISTER_DEVICE_ID == opt3001::register::DEVICE_ID);
3327
3328#[repr(C)]
3330#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3331pub struct PamojaOpt3001Config {
3332 pub range_number: u8,
3334 pub long_conversion: u8,
3336 pub mode: u8,
3338 pub overflow: u8,
3340 pub conversion_ready: u8,
3342 pub flag_high: u8,
3344 pub flag_low: u8,
3346 pub latched_window: u8,
3348 pub active_high: u8,
3350 pub mask_exponent: u8,
3352 pub fault_count: u8,
3354}
3355
3356#[no_mangle]
3367pub unsafe extern "C" fn pamoja_opt3001_lsb_milli_lux(
3368 exponent: u8,
3369 out_milli_lux: *mut u32,
3370) -> bool {
3371 match opt3001::lsb_milli_lux(exponent) {
3372 Some(step) if !out_milli_lux.is_null() => {
3373 *out_milli_lux = step;
3374 true
3375 }
3376 _ => false,
3377 }
3378}
3379
3380#[no_mangle]
3391pub unsafe extern "C" fn pamoja_opt3001_full_scale_milli_lux(
3392 range_number: u8,
3393 out_milli_lux: *mut u32,
3394) -> bool {
3395 match opt3001::full_scale_milli_lux(range_number) {
3396 Some(full_scale) if !out_milli_lux.is_null() => {
3397 *out_milli_lux = full_scale;
3398 true
3399 }
3400 _ => false,
3401 }
3402}
3403
3404#[no_mangle]
3410pub extern "C" fn pamoja_opt3001_milli_lux(raw: u16) -> u32 {
3411 opt3001::milli_lux(raw)
3412}
3413
3414#[no_mangle]
3420pub extern "C" fn pamoja_opt3001_lux(raw: u16) -> f32 {
3421 opt3001::lux(raw)
3422}
3423
3424#[no_mangle]
3431pub extern "C" fn pamoja_opt3001_raw_from_milli_lux(milli_lux: u32) -> u16 {
3432 opt3001::raw_from_milli_lux(milli_lux)
3433}
3434
3435#[no_mangle]
3447pub unsafe extern "C" fn pamoja_opt3001_word_from_bytes(
3448 bytes: *const u8,
3449 bytes_len: usize,
3450 out_word: *mut u16,
3451) -> PamojaStatus {
3452 if out_word.is_null() {
3453 set_last_error("out_word must not be null".to_owned());
3454 return PamojaStatus::InvalidArgument;
3455 }
3456 let bytes = match read_bytes(bytes, bytes_len) {
3457 Ok(bytes) => bytes,
3458 Err(status) => return status,
3459 };
3460 let Ok(bytes) = <[u8; PAMOJA_OPT3001_REGISTER_LEN]>::try_from(&bytes[..]) else {
3461 return wrong_length("register", PAMOJA_OPT3001_REGISTER_LEN);
3462 };
3463 *out_word = opt3001::word_from_bytes(bytes);
3464 PamojaStatus::Ok
3465}
3466
3467#[no_mangle]
3478pub unsafe extern "C" fn pamoja_opt3001_word_to_bytes(
3479 word: u16,
3480 out_bytes: *mut u8,
3481) -> PamojaStatus {
3482 if out_bytes.is_null() {
3483 set_last_error("out_bytes must not be null".to_owned());
3484 return PamojaStatus::InvalidArgument;
3485 }
3486 let bytes = opt3001::word_to_bytes(word);
3487 core::ptr::copy_nonoverlapping(bytes.as_ptr(), out_bytes, PAMOJA_OPT3001_REGISTER_LEN);
3488 PamojaStatus::Ok
3489}
3490
3491#[no_mangle]
3497pub extern "C" fn pamoja_opt3001_config_bits(config: PamojaOpt3001Config) -> u16 {
3498 opt3001::Configuration::from(config).bits()
3499}
3500
3501#[no_mangle]
3512pub unsafe extern "C" fn pamoja_opt3001_config_from_bits(
3513 bits: u16,
3514 out_config: *mut PamojaOpt3001Config,
3515) -> PamojaStatus {
3516 if out_config.is_null() {
3517 set_last_error("out_config must not be null".to_owned());
3518 return PamojaStatus::InvalidArgument;
3519 }
3520 *out_config = opt3001::Configuration::from_bits(bits).into();
3521 PamojaStatus::Ok
3522}
3523
3524#[no_mangle]
3530pub extern "C" fn pamoja_opt3001_conversion_millis(long_conversion: bool) -> u16 {
3531 conversion_time(long_conversion).millis()
3532}
3533
3534#[no_mangle]
3540pub extern "C" fn pamoja_opt3001_fault_count(code: u8) -> u8 {
3541 opt3001::FaultCount::from_code(code).count()
3542}
3543
3544#[no_mangle]
3550pub extern "C" fn pamoja_opt3001_is_automatic_range(range_number: u8) -> bool {
3551 range_number == opt3001::RANGE_AUTOMATIC
3552}
3553
3554pub const PAMOJA_INA226_BASE_ADDRESS: u8 = 0x40;
3556
3557pub const PAMOJA_INA226_MANUFACTURER_ID: u16 = 0x5449;
3559
3560pub const PAMOJA_INA226_DEVICE_ID: u16 = 0x226;
3562
3563pub const PAMOJA_INA226_CONFIG_RESET: u16 = 0x4127;
3565
3566pub const PAMOJA_INA226_SHUNT_LSB_NANOVOLTS: i32 = 2_500;
3568
3569pub const PAMOJA_INA226_BUS_LSB_MICROVOLTS: u32 = 1_250;
3571
3572pub const PAMOJA_INA226_POWER_LSB_RATIO: u32 = 25;
3574
3575pub const PAMOJA_INA226_REGISTER_CONFIGURATION: u8 = 0x00;
3577
3578pub const PAMOJA_INA226_REGISTER_SHUNT_VOLTAGE: u8 = 0x01;
3580
3581pub const PAMOJA_INA226_REGISTER_BUS_VOLTAGE: u8 = 0x02;
3583
3584pub const PAMOJA_INA226_REGISTER_POWER: u8 = 0x03;
3586
3587pub const PAMOJA_INA226_REGISTER_CURRENT: u8 = 0x04;
3589
3590pub const PAMOJA_INA226_REGISTER_CALIBRATION: u8 = 0x05;
3592
3593pub const PAMOJA_INA226_REGISTER_MASK_ENABLE: u8 = 0x06;
3595
3596pub const PAMOJA_INA226_REGISTER_ALERT_LIMIT: u8 = 0x07;
3598
3599pub const PAMOJA_INA226_REGISTER_MANUFACTURER_ID: u8 = 0xFE;
3601
3602pub const PAMOJA_INA226_REGISTER_DIE_ID: u8 = 0xFF;
3604
3605const _: () = assert!(PAMOJA_INA226_BASE_ADDRESS == ina226::BASE_ADDRESS);
3608const _: () = assert!(PAMOJA_INA226_MANUFACTURER_ID == ina226::MANUFACTURER_ID);
3609const _: () = assert!(PAMOJA_INA226_DEVICE_ID == ina226::DEVICE_ID);
3610const _: () = assert!(PAMOJA_INA226_CONFIG_RESET == ina226::CONFIG_RESET);
3611const _: () = assert!(PAMOJA_INA226_SHUNT_LSB_NANOVOLTS == ina226::SHUNT_LSB_NANOVOLTS);
3612const _: () = assert!(PAMOJA_INA226_BUS_LSB_MICROVOLTS == ina226::BUS_LSB_MICROVOLTS);
3613const _: () = assert!(PAMOJA_INA226_POWER_LSB_RATIO == ina226::POWER_LSB_RATIO);
3614const _: () = assert!(PAMOJA_INA226_REGISTER_CONFIGURATION == ina226::register::CONFIGURATION);
3615const _: () = assert!(PAMOJA_INA226_REGISTER_SHUNT_VOLTAGE == ina226::register::SHUNT_VOLTAGE);
3616const _: () = assert!(PAMOJA_INA226_REGISTER_BUS_VOLTAGE == ina226::register::BUS_VOLTAGE);
3617const _: () = assert!(PAMOJA_INA226_REGISTER_POWER == ina226::register::POWER);
3618const _: () = assert!(PAMOJA_INA226_REGISTER_CURRENT == ina226::register::CURRENT);
3619const _: () = assert!(PAMOJA_INA226_REGISTER_CALIBRATION == ina226::register::CALIBRATION);
3620const _: () = assert!(PAMOJA_INA226_REGISTER_MASK_ENABLE == ina226::register::MASK_ENABLE);
3621const _: () = assert!(PAMOJA_INA226_REGISTER_ALERT_LIMIT == ina226::register::ALERT_LIMIT);
3622const _: () = assert!(PAMOJA_INA226_REGISTER_MANUFACTURER_ID == ina226::register::MANUFACTURER_ID);
3623const _: () = assert!(PAMOJA_INA226_REGISTER_DIE_ID == ina226::register::DIE_ID);
3624
3625#[repr(C)]
3627#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3628pub enum PamojaIna226AlertFunction {
3629 ShuntOverLimit = 0,
3631 ShuntUnderLimit = 1,
3633 BusOverLimit = 2,
3635 BusUnderLimit = 3,
3637 PowerOverLimit = 4,
3639}
3640
3641#[repr(C)]
3643#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3644pub struct PamojaIna226Config {
3645 pub reset: u8,
3647 pub averaging: u8,
3649 pub bus_conversion_time: u8,
3651 pub shunt_conversion_time: u8,
3653 pub mode: u8,
3655}
3656
3657#[repr(C)]
3659#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3660pub struct PamojaIna226MaskEnable {
3661 pub shunt_over_limit: u8,
3663 pub shunt_under_limit: u8,
3665 pub bus_over_limit: u8,
3667 pub bus_under_limit: u8,
3669 pub power_over_limit: u8,
3671 pub conversion_ready: u8,
3673 pub alert_function_flag: u8,
3675 pub conversion_ready_flag: u8,
3677 pub math_overflow: u8,
3679 pub alert_active_high: u8,
3681 pub alert_latch: u8,
3683}
3684
3685#[repr(C)]
3687#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3688pub struct PamojaIna226DieId {
3689 pub device: u16,
3691 pub revision: u8,
3693}
3694
3695#[no_mangle]
3706pub unsafe extern "C" fn pamoja_ina226_address(
3707 a1: u8,
3708 a0: u8,
3709 out_address: *mut u8,
3710) -> PamojaStatus {
3711 if out_address.is_null() {
3712 set_last_error("out_address must not be null".to_owned());
3713 return PamojaStatus::InvalidArgument;
3714 }
3715 let (Some(a1), Some(a0)) = (address_pin(a1), address_pin(a0)) else {
3716 set_last_error(
3717 "an INA226 address pin must be tied to GND, VS, SDA, or SCL: code 0 to 3".to_owned(),
3718 );
3719 return PamojaStatus::InvalidArgument;
3720 };
3721 *out_address = ina226::address(a1, a0);
3722 PamojaStatus::Ok
3723}
3724
3725#[no_mangle]
3731pub extern "C" fn pamoja_ina226_averaging_samples(code: u8) -> u16 {
3732 averaging(code).samples()
3733}
3734
3735#[no_mangle]
3741pub extern "C" fn pamoja_ina226_conversion_micros(code: u8) -> u32 {
3742 conversion_time_setting(code).microseconds()
3743}
3744
3745#[no_mangle]
3751pub extern "C" fn pamoja_ina226_measures_shunt(code: u8) -> bool {
3752 mode(code).measures_shunt()
3753}
3754
3755#[no_mangle]
3761pub extern "C" fn pamoja_ina226_measures_bus(code: u8) -> bool {
3762 mode(code).measures_bus()
3763}
3764
3765#[no_mangle]
3771pub extern "C" fn pamoja_ina226_is_continuous(code: u8) -> bool {
3772 mode(code).is_continuous()
3773}
3774
3775#[no_mangle]
3786pub unsafe extern "C" fn pamoja_ina226_config_from_register(
3787 raw: u16,
3788 out_config: *mut PamojaIna226Config,
3789) -> PamojaStatus {
3790 if out_config.is_null() {
3791 set_last_error("out_config must not be null".to_owned());
3792 return PamojaStatus::InvalidArgument;
3793 }
3794 *out_config = ina226::Configuration::from_register(raw).into();
3795 PamojaStatus::Ok
3796}
3797
3798#[no_mangle]
3804pub extern "C" fn pamoja_ina226_config_to_register(config: PamojaIna226Config) -> u16 {
3805 ina226::Configuration::from(config).to_register()
3806}
3807
3808#[no_mangle]
3814pub extern "C" fn pamoja_ina226_update_micros(config: PamojaIna226Config) -> u32 {
3815 ina226::Configuration::from(config).update_microseconds()
3816}
3817
3818#[no_mangle]
3829pub unsafe extern "C" fn pamoja_ina226_mask_enable_from_register(
3830 raw: u16,
3831 out_mask: *mut PamojaIna226MaskEnable,
3832) -> PamojaStatus {
3833 if out_mask.is_null() {
3834 set_last_error("out_mask must not be null".to_owned());
3835 return PamojaStatus::InvalidArgument;
3836 }
3837 *out_mask = ina226::MaskEnable::from_register(raw).into();
3838 PamojaStatus::Ok
3839}
3840
3841#[no_mangle]
3847pub extern "C" fn pamoja_ina226_mask_enable_to_register(mask: PamojaIna226MaskEnable) -> u16 {
3848 ina226::MaskEnable::from(mask).to_register()
3849}
3850
3851#[no_mangle]
3862pub unsafe extern "C" fn pamoja_ina226_active_alert_function(
3863 mask: PamojaIna226MaskEnable,
3864 out_function: *mut PamojaIna226AlertFunction,
3865) -> bool {
3866 match ina226::MaskEnable::from(mask).active_alert_function() {
3867 Some(function) if !out_function.is_null() => {
3868 *out_function = function.into();
3869 true
3870 }
3871 _ => false,
3872 }
3873}
3874
3875#[no_mangle]
3886pub unsafe extern "C" fn pamoja_ina226_die_id(
3887 raw: u16,
3888 out_die_id: *mut PamojaIna226DieId,
3889) -> PamojaStatus {
3890 if out_die_id.is_null() {
3891 set_last_error("out_die_id must not be null".to_owned());
3892 return PamojaStatus::InvalidArgument;
3893 }
3894 *out_die_id = ina226::DieId::from_register(raw).into();
3895 PamojaStatus::Ok
3896}
3897
3898#[no_mangle]
3911pub unsafe extern "C" fn pamoja_ina226_identify(
3912 manufacturer_id: u16,
3913 die_id: u16,
3914 out_die_id: *mut PamojaIna226DieId,
3915) -> PamojaStatus {
3916 if out_die_id.is_null() {
3917 set_last_error("out_die_id must not be null".to_owned());
3918 return PamojaStatus::InvalidArgument;
3919 }
3920 match ina226::identify(manufacturer_id, die_id) {
3921 Ok(identified) => {
3922 *out_die_id = identified.into();
3923 PamojaStatus::Ok
3924 }
3925 Err(error) => failed(error),
3926 }
3927}
3928
3929#[no_mangle]
3935pub extern "C" fn pamoja_ina226_calibration(
3936 current_lsb_microamps: u32,
3937 shunt_milliohms: u32,
3938) -> u16 {
3939 ina226::calibration(current_lsb_microamps, shunt_milliohms)
3940}
3941
3942#[no_mangle]
3948pub extern "C" fn pamoja_ina226_minimum_current_lsb_microamps(max_expected_microamps: u32) -> u32 {
3949 ina226::minimum_current_lsb_microamps(max_expected_microamps)
3950}
3951
3952#[no_mangle]
3958pub extern "C" fn pamoja_ina226_shunt_nanovolts(raw: i16) -> i32 {
3959 ina226::shunt_nanovolts(raw)
3960}
3961
3962#[no_mangle]
3968pub extern "C" fn pamoja_ina226_shunt_millivolts(raw: i16) -> f32 {
3969 ina226::shunt_millivolts_f32(raw)
3970}
3971
3972#[no_mangle]
3978pub extern "C" fn pamoja_ina226_bus_microvolts(raw: u16) -> u32 {
3979 ina226::bus_microvolts(raw)
3980}
3981
3982#[no_mangle]
3988pub extern "C" fn pamoja_ina226_bus_volts(raw: u16) -> f32 {
3989 ina226::bus_volts_f32(raw)
3990}
3991
3992#[no_mangle]
3998pub extern "C" fn pamoja_ina226_current_microamps(raw: i16, current_lsb_microamps: u32) -> i32 {
3999 ina226::current_microamps(raw, current_lsb_microamps)
4000}
4001
4002#[no_mangle]
4008pub extern "C" fn pamoja_ina226_current_amps(raw: i16, current_lsb_microamps: u32) -> f32 {
4009 ina226::current_amps_f32(raw, current_lsb_microamps)
4010}
4011
4012#[no_mangle]
4018pub extern "C" fn pamoja_ina226_power_microwatts(raw: u16, current_lsb_microamps: u32) -> u32 {
4019 ina226::power_microwatts(raw, current_lsb_microamps)
4020}
4021
4022#[no_mangle]
4028pub extern "C" fn pamoja_ina226_power_watts(raw: u16, current_lsb_microamps: u32) -> f32 {
4029 ina226::power_watts_f32(raw, current_lsb_microamps)
4030}
4031
4032#[no_mangle]
4038pub extern "C" fn pamoja_ina226_shunt_register(nanovolts: i32) -> i16 {
4039 ina226::shunt_register(nanovolts)
4040}
4041
4042#[no_mangle]
4048pub extern "C" fn pamoja_ina226_bus_register(microvolts: u32) -> u16 {
4049 ina226::bus_register(microvolts)
4050}
4051
4052#[no_mangle]
4058pub extern "C" fn pamoja_ina226_current_register(
4059 microamps: i32,
4060 current_lsb_microamps: u32,
4061) -> i16 {
4062 ina226::current_register(microamps, current_lsb_microamps)
4063}
4064
4065#[no_mangle]
4071pub extern "C" fn pamoja_ina226_power_register(microwatts: u32, current_lsb_microamps: u32) -> u16 {
4072 ina226::power_register(microwatts, current_lsb_microamps)
4073}
4074
4075#[no_mangle]
4081pub extern "C" fn pamoja_ina226_current_register_from_shunt(shunt: i16, calibration: u16) -> i16 {
4082 ina226::current_register_from_shunt(shunt, calibration)
4083}
4084
4085#[no_mangle]
4091pub extern "C" fn pamoja_ina226_power_register_from_current(current: i16, bus: u16) -> u16 {
4092 ina226::power_register_from_current(current, bus)
4093}
4094
4095impl From<PamojaAds1115Config> for ads1115::Config {
4096 fn from(value: PamojaAds1115Config) -> Self {
4097 ads1115::Config {
4098 start_conversion: value.start_conversion != 0,
4099 mux: ads1115::Mux::from_code(value.mux),
4100 pga: ads1115::Pga::from_code(value.pga),
4101 mode: if value.single_shot != 0 {
4102 ads1115::Mode::SingleShot
4103 } else {
4104 ads1115::Mode::Continuous
4105 },
4106 data_rate: ads1115::DataRate::from_code(value.data_rate),
4107 comparator_mode: if value.window_comparator != 0 {
4108 ads1115::ComparatorMode::Window
4109 } else {
4110 ads1115::ComparatorMode::Traditional
4111 },
4112 comparator_polarity: if value.comparator_active_high != 0 {
4113 ads1115::ComparatorPolarity::ActiveHigh
4114 } else {
4115 ads1115::ComparatorPolarity::ActiveLow
4116 },
4117 comparator_latch: if value.comparator_latching != 0 {
4118 ads1115::ComparatorLatch::Latching
4119 } else {
4120 ads1115::ComparatorLatch::NonLatching
4121 },
4122 comparator_queue: ads1115::ComparatorQueue::from_code(value.comparator_queue),
4123 }
4124 }
4125}
4126
4127impl From<ads1115::Config> for PamojaAds1115Config {
4128 fn from(value: ads1115::Config) -> Self {
4129 PamojaAds1115Config {
4130 start_conversion: u8::from(value.start_conversion),
4131 mux: value.mux.code(),
4132 pga: value.pga.code(),
4133 single_shot: u8::from(matches!(value.mode, ads1115::Mode::SingleShot)),
4134 data_rate: value.data_rate.code(),
4135 window_comparator: u8::from(matches!(
4136 value.comparator_mode,
4137 ads1115::ComparatorMode::Window
4138 )),
4139 comparator_active_high: u8::from(matches!(
4140 value.comparator_polarity,
4141 ads1115::ComparatorPolarity::ActiveHigh
4142 )),
4143 comparator_latching: u8::from(matches!(
4144 value.comparator_latch,
4145 ads1115::ComparatorLatch::Latching
4146 )),
4147 comparator_queue: value.comparator_queue.code(),
4148 }
4149 }
4150}
4151
4152impl From<bmp280::Calibration> for PamojaBmp280Coefficients {
4153 fn from(value: bmp280::Calibration) -> Self {
4154 PamojaBmp280Coefficients {
4155 dig_t1: value.dig_t1,
4156 dig_t2: value.dig_t2,
4157 dig_t3: value.dig_t3,
4158 dig_p1: value.dig_p1,
4159 dig_p2: value.dig_p2,
4160 dig_p3: value.dig_p3,
4161 dig_p4: value.dig_p4,
4162 dig_p5: value.dig_p5,
4163 dig_p6: value.dig_p6,
4164 dig_p7: value.dig_p7,
4165 dig_p8: value.dig_p8,
4166 dig_p9: value.dig_p9,
4167 }
4168 }
4169}
4170
4171impl From<bmp280::Measurement> for PamojaBmp280Measurement {
4172 fn from(value: bmp280::Measurement) -> Self {
4173 PamojaBmp280Measurement {
4174 pressure: value.pressure,
4175 temperature: value.temperature,
4176 pressure_skipped: u8::from(value.pressure_skipped()),
4177 temperature_skipped: u8::from(value.temperature_skipped()),
4178 }
4179 }
4180}
4181
4182impl From<PamojaBmp280CtrlMeas> for bmp280::CtrlMeas {
4183 fn from(value: PamojaBmp280CtrlMeas) -> Self {
4184 bmp280::CtrlMeas {
4185 temperature: bmp280::Oversampling::from_code(value.temperature),
4186 pressure: bmp280::Oversampling::from_code(value.pressure),
4187 mode: bmp280::Mode::from_code(value.mode),
4188 }
4189 }
4190}
4191
4192impl From<bmp280::CtrlMeas> for PamojaBmp280CtrlMeas {
4193 fn from(value: bmp280::CtrlMeas) -> Self {
4194 PamojaBmp280CtrlMeas {
4195 temperature: value.temperature.code(),
4196 pressure: value.pressure.code(),
4197 mode: value.mode.code(),
4198 }
4199 }
4200}
4201
4202impl From<PamojaBmp280Config> for bmp280::Config {
4203 fn from(value: PamojaBmp280Config) -> Self {
4204 bmp280::Config {
4205 standby: bmp280::Standby::from_code(value.standby),
4206 filter: value.filter,
4207 spi_3wire: value.spi_3wire != 0,
4208 }
4209 }
4210}
4211
4212impl From<bmp280::Config> for PamojaBmp280Config {
4213 fn from(value: bmp280::Config) -> Self {
4214 PamojaBmp280Config {
4215 standby: value.standby.code(),
4216 filter: value.filter,
4217 spi_3wire: u8::from(value.spi_3wire),
4218 }
4219 }
4220}
4221
4222impl From<sht3x::Measurement> for PamojaSht3xMeasurement {
4223 fn from(value: sht3x::Measurement) -> Self {
4224 PamojaSht3xMeasurement {
4225 temperature_raw: value.temperature_raw,
4226 humidity_raw: value.humidity_raw,
4227 milli_celsius: value.temperature_milli_celsius(),
4228 celsius: value.temperature_celsius(),
4229 milli_fahrenheit: value.temperature_milli_fahrenheit(),
4230 fahrenheit: value.temperature_fahrenheit(),
4231 milli_percent: value.humidity_milli_percent(),
4232 relative_humidity: value.relative_humidity(),
4233 }
4234 }
4235}
4236
4237impl From<sht3x::Status> for PamojaSht3xStatus {
4238 fn from(value: sht3x::Status) -> Self {
4239 PamojaSht3xStatus {
4240 bits: value.bits(),
4241 alert_pending: u8::from(value.alert_pending()),
4242 heater_on: u8::from(value.heater_on()),
4243 humidity_tracking_alert: u8::from(value.humidity_tracking_alert()),
4244 temperature_tracking_alert: u8::from(value.temperature_tracking_alert()),
4245 reset_detected: u8::from(value.reset_detected()),
4246 command_failed: u8::from(value.command_failed()),
4247 write_checksum_failed: u8::from(value.write_checksum_failed()),
4248 }
4249 }
4250}
4251
4252impl From<scd4x::Measurement> for PamojaScd4xMeasurement {
4253 fn from(value: scd4x::Measurement) -> Self {
4254 PamojaScd4xMeasurement {
4255 co2_ppm: value.co2_ppm,
4256 temperature_raw: value.temperature_raw,
4257 humidity_raw: value.humidity_raw,
4258 milli_celsius: value.milli_celsius(),
4259 celsius: value.celsius(),
4260 humidity_milli_percent: value.humidity_milli_percent(),
4261 relative_humidity_percent: value.relative_humidity_percent(),
4262 }
4263 }
4264}
4265
4266impl From<PamojaTmp117Config> for tmp117::Configuration {
4267 fn from(value: PamojaTmp117Config) -> Self {
4268 tmp117::Configuration {
4269 high_alert: value.high_alert != 0,
4270 low_alert: value.low_alert != 0,
4271 data_ready: value.data_ready != 0,
4272 eeprom_busy: value.eeprom_busy != 0,
4273 mode: tmp117::ConversionMode::from_code(value.mode),
4274 cycle: tmp117::ConversionCycle::from_code(value.cycle),
4275 averaging: tmp117::Averaging::from_code(value.averaging),
4276 alert_mode: if value.therm_mode != 0 {
4277 tmp117::AlertMode::Therm
4278 } else {
4279 tmp117::AlertMode::Alert
4280 },
4281 alert_polarity: if value.alert_active_high != 0 {
4282 tmp117::AlertPolarity::ActiveHigh
4283 } else {
4284 tmp117::AlertPolarity::ActiveLow
4285 },
4286 alert_pin: if value.alert_pin_data_ready != 0 {
4287 tmp117::AlertPin::DataReady
4288 } else {
4289 tmp117::AlertPin::AlertFlags
4290 },
4291 soft_reset: value.soft_reset != 0,
4292 }
4293 }
4294}
4295
4296impl From<tmp117::Configuration> for PamojaTmp117Config {
4297 fn from(value: tmp117::Configuration) -> Self {
4298 PamojaTmp117Config {
4299 high_alert: u8::from(value.high_alert),
4300 low_alert: u8::from(value.low_alert),
4301 data_ready: u8::from(value.data_ready),
4302 eeprom_busy: u8::from(value.eeprom_busy),
4303 mode: value.mode.code(),
4304 cycle: value.cycle.code(),
4305 averaging: value.averaging.code(),
4306 therm_mode: u8::from(matches!(value.alert_mode, tmp117::AlertMode::Therm)),
4307 alert_active_high: u8::from(matches!(
4308 value.alert_polarity,
4309 tmp117::AlertPolarity::ActiveHigh
4310 )),
4311 alert_pin_data_ready: u8::from(matches!(value.alert_pin, tmp117::AlertPin::DataReady)),
4312 soft_reset: u8::from(value.soft_reset),
4313 }
4314 }
4315}
4316
4317impl From<hdc1080::Measurement> for PamojaHdc1080Measurement {
4318 fn from(value: hdc1080::Measurement) -> Self {
4319 PamojaHdc1080Measurement {
4320 temperature_raw: value.temperature,
4321 humidity_raw: value.humidity,
4322 milli_celsius: value.milli_celsius(),
4323 celsius: value.celsius(),
4324 milli_percent: value.milli_percent(),
4325 relative_humidity: value.relative_humidity(),
4326 }
4327 }
4328}
4329
4330impl From<hdc1080::Configuration> for PamojaHdc1080Config {
4331 fn from(value: hdc1080::Configuration) -> Self {
4332 PamojaHdc1080Config {
4333 software_reset: u8::from(value.software_reset),
4334 heater: u8::from(value.heater),
4335 sequential: u8::from(matches!(
4336 value.mode,
4337 hdc1080::AcquisitionMode::TemperatureThenHumidity
4338 )),
4339 battery_low: u8::from(value.battery_low),
4340 temperature_resolution_bits: match value.temperature_resolution {
4341 hdc1080::TemperatureResolution::Bits14 => 14,
4342 hdc1080::TemperatureResolution::Bits11 => 11,
4343 },
4344 humidity_resolution_bits: match value.humidity_resolution {
4345 hdc1080::HumidityResolution::Bits14 => 14,
4346 hdc1080::HumidityResolution::Bits11 => 11,
4347 hdc1080::HumidityResolution::Bits8 => 8,
4348 },
4349 }
4350 }
4351}
4352
4353impl TryFrom<PamojaHdc1080Config> for hdc1080::Configuration {
4354 type Error = PamojaStatus;
4355
4356 fn try_from(value: PamojaHdc1080Config) -> Result<Self, PamojaStatus> {
4357 let Some(temperature_resolution) =
4358 temperature_resolution(value.temperature_resolution_bits)
4359 else {
4360 return Err(bad_temperature_resolution());
4361 };
4362 let Some(humidity_resolution) = humidity_resolution(value.humidity_resolution_bits) else {
4363 return Err(bad_humidity_resolution());
4364 };
4365 Ok(hdc1080::Configuration {
4366 software_reset: value.software_reset != 0,
4367 heater: value.heater != 0,
4368 mode: if value.sequential != 0 {
4369 hdc1080::AcquisitionMode::TemperatureThenHumidity
4370 } else {
4371 hdc1080::AcquisitionMode::Single
4372 },
4373 battery_low: value.battery_low != 0,
4374 temperature_resolution,
4375 humidity_resolution,
4376 })
4377 }
4378}
4379
4380impl From<PamojaOpt3001Config> for opt3001::Configuration {
4381 fn from(value: PamojaOpt3001Config) -> Self {
4382 opt3001::Configuration {
4383 range_number: value.range_number,
4384 conversion_time: conversion_time(value.long_conversion != 0),
4385 mode: opt3001::Mode::from_code(value.mode),
4386 overflow: value.overflow != 0,
4387 conversion_ready: value.conversion_ready != 0,
4388 flag_high: value.flag_high != 0,
4389 flag_low: value.flag_low != 0,
4390 latch: if value.latched_window != 0 {
4391 opt3001::Latch::LatchedWindow
4392 } else {
4393 opt3001::Latch::TransparentHysteresis
4394 },
4395 polarity: if value.active_high != 0 {
4396 opt3001::Polarity::ActiveHigh
4397 } else {
4398 opt3001::Polarity::ActiveLow
4399 },
4400 mask_exponent: value.mask_exponent != 0,
4401 fault_count: opt3001::FaultCount::from_code(value.fault_count),
4402 }
4403 }
4404}
4405
4406impl From<opt3001::Configuration> for PamojaOpt3001Config {
4407 fn from(value: opt3001::Configuration) -> Self {
4408 PamojaOpt3001Config {
4409 range_number: value.range_number,
4410 long_conversion: u8::from(matches!(
4411 value.conversion_time,
4412 opt3001::ConversionTime::Ms800
4413 )),
4414 mode: value.mode.code(),
4415 overflow: u8::from(value.overflow),
4416 conversion_ready: u8::from(value.conversion_ready),
4417 flag_high: u8::from(value.flag_high),
4418 flag_low: u8::from(value.flag_low),
4419 latched_window: u8::from(matches!(value.latch, opt3001::Latch::LatchedWindow)),
4420 active_high: u8::from(matches!(value.polarity, opt3001::Polarity::ActiveHigh)),
4421 mask_exponent: u8::from(value.mask_exponent),
4422 fault_count: value.fault_count.code(),
4423 }
4424 }
4425}
4426
4427impl From<PamojaIna226Config> for ina226::Configuration {
4428 fn from(value: PamojaIna226Config) -> Self {
4429 ina226::Configuration {
4430 reset: value.reset != 0,
4431 averaging: averaging(value.averaging),
4432 bus_conversion_time: conversion_time_setting(value.bus_conversion_time),
4433 shunt_conversion_time: conversion_time_setting(value.shunt_conversion_time),
4434 mode: mode(value.mode),
4435 }
4436 }
4437}
4438
4439impl From<ina226::Configuration> for PamojaIna226Config {
4440 fn from(value: ina226::Configuration) -> Self {
4441 PamojaIna226Config {
4442 reset: u8::from(value.reset),
4443 averaging: value.averaging as u8,
4444 bus_conversion_time: value.bus_conversion_time as u8,
4445 shunt_conversion_time: value.shunt_conversion_time as u8,
4446 mode: value.mode as u8,
4447 }
4448 }
4449}
4450
4451impl From<PamojaIna226MaskEnable> for ina226::MaskEnable {
4452 fn from(value: PamojaIna226MaskEnable) -> Self {
4453 ina226::MaskEnable {
4454 shunt_over_limit: value.shunt_over_limit != 0,
4455 shunt_under_limit: value.shunt_under_limit != 0,
4456 bus_over_limit: value.bus_over_limit != 0,
4457 bus_under_limit: value.bus_under_limit != 0,
4458 power_over_limit: value.power_over_limit != 0,
4459 conversion_ready: value.conversion_ready != 0,
4460 alert_function_flag: value.alert_function_flag != 0,
4461 conversion_ready_flag: value.conversion_ready_flag != 0,
4462 math_overflow: value.math_overflow != 0,
4463 alert_active_high: value.alert_active_high != 0,
4464 alert_latch: value.alert_latch != 0,
4465 }
4466 }
4467}
4468
4469impl From<ina226::MaskEnable> for PamojaIna226MaskEnable {
4470 fn from(value: ina226::MaskEnable) -> Self {
4471 PamojaIna226MaskEnable {
4472 shunt_over_limit: u8::from(value.shunt_over_limit),
4473 shunt_under_limit: u8::from(value.shunt_under_limit),
4474 bus_over_limit: u8::from(value.bus_over_limit),
4475 bus_under_limit: u8::from(value.bus_under_limit),
4476 power_over_limit: u8::from(value.power_over_limit),
4477 conversion_ready: u8::from(value.conversion_ready),
4478 alert_function_flag: u8::from(value.alert_function_flag),
4479 conversion_ready_flag: u8::from(value.conversion_ready_flag),
4480 math_overflow: u8::from(value.math_overflow),
4481 alert_active_high: u8::from(value.alert_active_high),
4482 alert_latch: u8::from(value.alert_latch),
4483 }
4484 }
4485}
4486
4487impl From<ina226::AlertFunction> for PamojaIna226AlertFunction {
4488 fn from(value: ina226::AlertFunction) -> Self {
4489 match value {
4490 ina226::AlertFunction::ShuntOverLimit => Self::ShuntOverLimit,
4491 ina226::AlertFunction::ShuntUnderLimit => Self::ShuntUnderLimit,
4492 ina226::AlertFunction::BusOverLimit => Self::BusOverLimit,
4493 ina226::AlertFunction::BusUnderLimit => Self::BusUnderLimit,
4494 ina226::AlertFunction::PowerOverLimit => Self::PowerOverLimit,
4495 }
4496 }
4497}
4498
4499impl From<ina226::DieId> for PamojaIna226DieId {
4500 fn from(value: ina226::DieId) -> Self {
4501 PamojaIna226DieId {
4502 device: value.device,
4503 revision: value.revision,
4504 }
4505 }
4506}
4507
4508fn repeatability_from_code(code: u8) -> Option<sht3x::Repeatability> {
4510 match code {
4511 0 => Some(sht3x::Repeatability::Low),
4512 1 => Some(sht3x::Repeatability::Medium),
4513 2 => Some(sht3x::Repeatability::High),
4514 _ => None,
4515 }
4516}
4517
4518fn bad_repeatability() -> PamojaStatus {
4520 set_last_error("SHT3x repeatability must be 0 low, 1 medium, or 2 high".to_owned());
4521 PamojaStatus::InvalidArgument
4522}
4523
4524fn rate_from_code(code: u8) -> Option<sht3x::Rate> {
4526 match code {
4527 0 => Some(sht3x::Rate::HalfMps),
4528 1 => Some(sht3x::Rate::OneMps),
4529 2 => Some(sht3x::Rate::TwoMps),
4530 3 => Some(sht3x::Rate::FourMps),
4531 4 => Some(sht3x::Rate::TenMps),
4532 _ => None,
4533 }
4534}
4535
4536fn bad_rate() -> PamojaStatus {
4538 set_last_error("SHT3x rate must be 0 for 0.5, 1, 2, 3, or 4 for 10 per second".to_owned());
4539 PamojaStatus::InvalidArgument
4540}
4541
4542fn temperature_resolution(bits: u8) -> Option<hdc1080::TemperatureResolution> {
4544 match bits {
4545 14 => Some(hdc1080::TemperatureResolution::Bits14),
4546 11 => Some(hdc1080::TemperatureResolution::Bits11),
4547 _ => None,
4548 }
4549}
4550
4551fn bad_temperature_resolution() -> PamojaStatus {
4553 set_last_error("HDC1080 temperature resolution must be 14 or 11 bits".to_owned());
4554 PamojaStatus::InvalidArgument
4555}
4556
4557fn humidity_resolution(bits: u8) -> Option<hdc1080::HumidityResolution> {
4559 match bits {
4560 14 => Some(hdc1080::HumidityResolution::Bits14),
4561 11 => Some(hdc1080::HumidityResolution::Bits11),
4562 8 => Some(hdc1080::HumidityResolution::Bits8),
4563 _ => None,
4564 }
4565}
4566
4567fn bad_humidity_resolution() -> PamojaStatus {
4569 set_last_error("HDC1080 humidity resolution must be 14, 11, or 8 bits".to_owned());
4570 PamojaStatus::InvalidArgument
4571}
4572
4573fn conversion_time(long_conversion: bool) -> opt3001::ConversionTime {
4575 if long_conversion {
4576 opt3001::ConversionTime::Ms800
4577 } else {
4578 opt3001::ConversionTime::Ms100
4579 }
4580}
4581
4582fn averaging(code: u8) -> ina226::Averaging {
4584 ina226::Configuration::from_register(u16::from(code & 0x07) << 9).averaging
4585}
4586
4587fn conversion_time_setting(code: u8) -> ina226::ConversionTime {
4589 ina226::Configuration::from_register(u16::from(code & 0x07) << 6).bus_conversion_time
4590}
4591
4592fn mode(code: u8) -> ina226::Mode {
4594 ina226::Configuration::from_register(u16::from(code & 0x07)).mode
4595}
4596
4597fn address_pin(code: u8) -> Option<ina226::AddressPin> {
4599 match code {
4600 0 => Some(ina226::AddressPin::Ground),
4601 1 => Some(ina226::AddressPin::Supply),
4602 2 => Some(ina226::AddressPin::Sda),
4603 3 => Some(ina226::AddressPin::Scl),
4604 _ => None,
4605 }
4606}
4607
4608fn resolution(bits: u8) -> Option<ds18b20::Resolution> {
4610 match bits {
4611 9 => Some(ds18b20::Resolution::Bits9),
4612 10 => Some(ds18b20::Resolution::Bits10),
4613 11 => Some(ds18b20::Resolution::Bits11),
4614 12 => Some(ds18b20::Resolution::Bits12),
4615 _ => None,
4616 }
4617}
4618
4619fn bad_resolution() -> PamojaStatus {
4621 set_last_error("DS18B20 resolution must be 9, 10, 11, or 12 bits".to_owned());
4622 PamojaStatus::InvalidArgument
4623}
4624
4625fn wrong_length(what: &str, expected: usize) -> PamojaStatus {
4627 set_last_error(format!("{what} must be exactly {expected} bytes"));
4628 PamojaStatus::InvalidArgument
4629}
4630
4631fn failed(error: SensorError) -> PamojaStatus {
4633 set_last_error(error.to_string());
4634 PamojaStatus::Codec
4635}
4636
4637#[cfg(test)]
4638mod tests {
4639 use super::*;
4640 use std::ptr;
4641
4642 #[test]
4643 fn a_bme280_burst_read_compensates_to_a_reading() {
4644 let temp_press = [0u8; PAMOJA_BME280_CALIBRATION_TEMP_PRESS_LEN];
4646 let humidity = [0u8; PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN];
4647 let measurement = [0u8; PAMOJA_BME280_MEASUREMENT_LEN];
4648 let mut calibration = ptr::null_mut();
4649 let mut reading = PamojaBme280Measurement {
4650 celsius: 0.0,
4651 pascals: 0,
4652 hectopascals: 0.0,
4653 relative_humidity_percent: 0.0,
4654 };
4655
4656 unsafe {
4658 assert_eq!(
4659 pamoja_bme280_calibration_new(
4660 temp_press.as_ptr(),
4661 temp_press.len(),
4662 humidity.as_ptr(),
4663 humidity.len(),
4664 &mut calibration
4665 ),
4666 PamojaStatus::Ok
4667 );
4668 assert_eq!(
4669 pamoja_bme280_compensate(
4670 calibration,
4671 measurement.as_ptr(),
4672 measurement.len(),
4673 &mut reading
4674 ),
4675 PamojaStatus::Ok
4676 );
4677 pamoja_bme280_calibration_free(calibration);
4678 }
4679 assert!(reading.hectopascals.is_finite());
4680 }
4681
4682 #[test]
4683 fn a_calibration_of_the_wrong_length_is_refused() {
4684 let short = [0u8; 4];
4685 let humidity = [0u8; PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN];
4686 let mut calibration = ptr::null_mut();
4687 let status = unsafe {
4689 pamoja_bme280_calibration_new(
4690 short.as_ptr(),
4691 short.len(),
4692 humidity.as_ptr(),
4693 humidity.len(),
4694 &mut calibration,
4695 )
4696 };
4697 assert_eq!(status, PamojaStatus::InvalidArgument);
4698 assert!(calibration.is_null());
4699 }
4700
4701 #[test]
4702 fn a_scratchpad_decodes_and_its_crc_is_checked() {
4703 let mut bytes = [0x91u8, 0x01, 0x4B, 0x46, 0x7F, 0xFF, 0x0C, 0x10, 0x00];
4705 bytes[8] = unsafe { pamoja_ds18b20_crc8(bytes.as_ptr(), 8) };
4707
4708 let mut reading = PamojaDs18b20Reading {
4709 raw_temperature: 0,
4710 micro_celsius: 0,
4711 alarm_high: 0,
4712 alarm_low: 0,
4713 resolution_bits: 0,
4714 };
4715 unsafe {
4717 assert_eq!(
4718 pamoja_ds18b20_parse_scratchpad(bytes.as_ptr(), bytes.len(), &mut reading),
4719 PamojaStatus::Ok
4720 );
4721 }
4722 assert_eq!(reading.raw_temperature, 0x0191);
4723 assert_eq!(reading.micro_celsius, 25_062_500);
4724 assert_eq!(reading.resolution_bits, 12);
4725
4726 bytes[0] ^= 0xFF;
4727 let status =
4729 unsafe { pamoja_ds18b20_parse_scratchpad(bytes.as_ptr(), bytes.len(), &mut reading) };
4730 assert_eq!(
4731 status,
4732 PamojaStatus::Codec,
4733 "a read corrupted on the bus must not be trusted"
4734 );
4735 }
4736
4737 #[test]
4738 fn a_resolution_outside_the_datasheet_is_refused() {
4739 let mut byte = 0u8;
4740 let status = unsafe { pamoja_ds18b20_config_byte(8, &mut byte) };
4742 assert_eq!(status, PamojaStatus::InvalidArgument);
4743 }
4744
4745 #[test]
4746 fn the_resolution_round_trips_through_its_config_byte() {
4747 for bits in [9u8, 10, 11, 12] {
4748 let mut byte = 0u8;
4749 unsafe {
4751 assert_eq!(
4752 pamoja_ds18b20_config_byte(bits, &mut byte),
4753 PamojaStatus::Ok
4754 );
4755 }
4756 assert_eq!(pamoja_ds18b20_resolution_bits(byte), bits);
4757 }
4758 }
4759
4760 #[test]
4761 fn an_ina219_reading_converts_at_its_calibrated_resolution() {
4762 const CURRENT_LSB: u32 = 1_000;
4765 assert_eq!(pamoja_ina219_calibration(CURRENT_LSB, 2), 0x5000);
4766 assert_eq!(
4767 pamoja_ina219_minimum_current_lsb_microamps(15_000_000),
4768 458,
4769 "15 A over a 15-bit register, rounded up to the next whole microamp"
4770 );
4771 assert_eq!(
4772 pamoja_ina219_current_microamps(1_000, CURRENT_LSB),
4773 1_000_000
4774 );
4775 assert_eq!(pamoja_ina219_power_microwatts(100, CURRENT_LSB), 2_000_000);
4777 assert!(pamoja_ina219_conversion_ready(0x0002));
4778 assert!(pamoja_ina219_math_overflow(0x0001));
4779 }
4780
4781 #[test]
4782 fn an_ads1115_config_round_trips_through_its_register() {
4783 let config = PamojaAds1115Config {
4784 start_conversion: 1,
4785 mux: 4,
4786 pga: 1,
4787 single_shot: 1,
4788 data_rate: 4,
4789 window_comparator: 0,
4790 comparator_active_high: 0,
4791 comparator_latching: 0,
4792 comparator_queue: 3,
4793 };
4794 let bits = pamoja_ads1115_config_bits(config);
4795 let mut back = config;
4796 unsafe {
4798 assert_eq!(
4799 pamoja_ads1115_config_from_bits(bits, &mut back),
4800 PamojaStatus::Ok
4801 );
4802 }
4803 assert_eq!(back, config);
4804 }
4805
4806 #[test]
4807 fn an_ads1115_conversion_scales_to_its_full_range() {
4808 assert_eq!(pamoja_ads1115_full_scale_microvolts(1), 4_096_000);
4810 assert_eq!(pamoja_ads1115_to_nanovolts(1, 32_767), 4_095_875_000);
4811 assert!((pamoja_ads1115_to_volts(1, 0)).abs() < f32::EPSILON);
4812 }
4813
4814 #[test]
4815 fn a_bmp280_burst_read_compensates_and_its_coefficients_round_trip() {
4816 let calibration_bytes = [
4818 0x70, 0x6B, 0x43, 0x67, 0x18, 0xFC, 0x7D, 0x8E, 0x43, 0xD6, 0xD0, 0x0B, 0x27, 0x0B,
4819 0x8C, 0x00, 0xF9, 0xFF, 0x8C, 0x3C, 0xF8, 0xC6, 0x70, 0x17,
4820 ];
4821 let measurement = [0x65u8, 0x5A, 0xC0, 0x7E, 0xED, 0x00];
4822 let mut calibration = ptr::null_mut();
4823 let mut reading = PamojaBmp280Reading {
4824 celsius: 0.0,
4825 pascals: 0,
4826 hectopascals: 0.0,
4827 };
4828 let mut coefficients = [0u8; PAMOJA_BMP280_CALIBRATION_LEN];
4829
4830 unsafe {
4832 assert_eq!(
4833 pamoja_bmp280_calibration_new(
4834 calibration_bytes.as_ptr(),
4835 calibration_bytes.len(),
4836 &mut calibration
4837 ),
4838 PamojaStatus::Ok
4839 );
4840 assert_eq!(
4841 pamoja_bmp280_compensate(
4842 calibration,
4843 measurement.as_ptr(),
4844 measurement.len(),
4845 &mut reading
4846 ),
4847 PamojaStatus::Ok
4848 );
4849 assert_eq!(
4850 pamoja_bmp280_calibration_to_bytes(calibration, coefficients.as_mut_ptr()),
4851 PamojaStatus::Ok
4852 );
4853 pamoja_bmp280_calibration_free(calibration);
4854 }
4855 assert_eq!(reading.pascals, 100_653);
4856 assert!((reading.celsius - 25.08).abs() < 1e-2);
4857 assert_eq!(
4858 coefficients, calibration_bytes,
4859 "the coefficients round-trip through their registers"
4860 );
4861 }
4862
4863 #[test]
4864 fn a_bmp280_control_register_round_trips() {
4865 let ctrl = PamojaBmp280CtrlMeas {
4866 temperature: 2,
4867 pressure: 5,
4868 mode: 3,
4869 };
4870 let bits = pamoja_bmp280_ctrl_meas_bits(ctrl);
4871 let mut back = ctrl;
4872 unsafe {
4874 assert_eq!(
4875 pamoja_bmp280_ctrl_meas_from_bits(bits, &mut back),
4876 PamojaStatus::Ok
4877 );
4878 }
4879 assert_eq!(back, ctrl);
4880 assert_eq!(pamoja_bmp280_oversampling_factor(5), 16);
4881 assert!(pamoja_bmp280_measuring(0x08));
4882 }
4883
4884 #[test]
4885 fn an_sht3x_frame_decodes_and_its_crc_is_checked() {
4886 let frame = [0x66u8, 0x66, 0x93, 0x99, 0x99, 0xBE];
4888 let mut measurement = PamojaSht3xMeasurement {
4889 temperature_raw: 0,
4890 humidity_raw: 0,
4891 milli_celsius: 0,
4892 celsius: 0.0,
4893 milli_fahrenheit: 0,
4894 fahrenheit: 0.0,
4895 milli_percent: 0,
4896 relative_humidity: 0.0,
4897 };
4898 unsafe {
4900 assert_eq!(
4901 pamoja_sht3x_parse_measurement(frame.as_ptr(), frame.len(), &mut measurement),
4902 PamojaStatus::Ok
4903 );
4904 }
4905 assert_eq!(measurement.milli_celsius, 25_000);
4906 assert_eq!(measurement.milli_fahrenheit, 77_000);
4907 assert_eq!(measurement.milli_percent, 60_000);
4908
4909 let check = [0xBEu8, 0xEF];
4910 assert_eq!(
4912 unsafe { pamoja_sht3x_crc(check.as_ptr(), check.len()) },
4913 0x92,
4914 "Sensirion's published check value"
4915 );
4916
4917 let corrupt = [0x66u8, 0x67, 0x93, 0x99, 0x99, 0xBE];
4918 let status = unsafe {
4920 pamoja_sht3x_parse_measurement(corrupt.as_ptr(), corrupt.len(), &mut measurement)
4921 };
4922 assert_eq!(
4923 status,
4924 PamojaStatus::Codec,
4925 "a read corrupted on the bus must not be trusted"
4926 );
4927 }
4928
4929 #[test]
4930 fn an_sht3x_status_and_command_follow_the_tables() {
4931 let frame = [0x80u8, 0x10, 0xE1];
4932 let mut status = PamojaSht3xStatus {
4933 bits: 0,
4934 alert_pending: 0,
4935 heater_on: 0,
4936 humidity_tracking_alert: 0,
4937 temperature_tracking_alert: 0,
4938 reset_detected: 0,
4939 command_failed: 0,
4940 write_checksum_failed: 0,
4941 };
4942 let mut command = 0u16;
4943 unsafe {
4945 assert_eq!(
4946 pamoja_sht3x_parse_status(frame.as_ptr(), frame.len(), &mut status),
4947 PamojaStatus::Ok
4948 );
4949 assert_eq!(
4950 pamoja_sht3x_single_shot(2, true, &mut command),
4951 PamojaStatus::Ok
4952 );
4953 }
4954 assert_eq!(status.bits, PAMOJA_SHT3X_STATUS_DEFAULT);
4955 assert_eq!(status.alert_pending, 1);
4956 assert_eq!(status.reset_detected, 1);
4957 assert_eq!(status.heater_on, 0);
4958 assert_eq!(command, PAMOJA_SHT3X_COMMAND_SINGLE_SHOT_HIGH_STRETCH);
4959
4960 let status = unsafe { pamoja_sht3x_single_shot(3, true, &mut command) };
4962 assert_eq!(status, PamojaStatus::InvalidArgument);
4963 }
4964
4965 #[test]
4966 fn an_scd4x_frame_decodes_and_its_crc_is_checked() {
4967 let frame = [0x01u8, 0xF4, 0x33, 0x66, 0x67, 0xA2, 0x5E, 0xB9, 0x3C];
4968 let mut measurement = PamojaScd4xMeasurement {
4969 co2_ppm: 0,
4970 temperature_raw: 0,
4971 humidity_raw: 0,
4972 milli_celsius: 0,
4973 celsius: 0.0,
4974 humidity_milli_percent: 0,
4975 relative_humidity_percent: 0.0,
4976 };
4977 unsafe {
4979 assert_eq!(
4980 pamoja_scd4x_parse_measurement(frame.as_ptr(), frame.len(), &mut measurement),
4981 PamojaStatus::Ok
4982 );
4983 }
4984 assert_eq!(measurement.co2_ppm, 500);
4985 assert_eq!(measurement.milli_celsius, 25_003);
4986 assert_eq!(measurement.humidity_milli_percent, 37_002);
4987
4988 let corrupt = [0x01u8, 0xF4, 0xCC, 0x66, 0x67, 0xA2, 0x5E, 0xB9, 0x3C];
4989 let status = unsafe {
4991 pamoja_scd4x_parse_measurement(corrupt.as_ptr(), corrupt.len(), &mut measurement)
4992 };
4993 assert_eq!(status, PamojaStatus::Codec);
4994
4995 assert_eq!(pamoja_scd4x_temperature_offset_word(5_400), 0x07E6);
4997
4998 let serial_frame = [0xF8u8, 0x96, 0x31, 0x9F, 0x07, 0xC2, 0x3B, 0xBE, 0x89];
4999 let mut serial = 0u64;
5000 unsafe {
5002 assert_eq!(
5003 pamoja_scd4x_serial_number(serial_frame.as_ptr(), serial_frame.len(), &mut serial),
5004 PamojaStatus::Ok
5005 );
5006 }
5007 assert_eq!(serial, 273_325_796_834_238);
5008 }
5009
5010 #[test]
5011 fn a_tmp117_register_decodes_to_its_datasheet_row() {
5012 assert_eq!(pamoja_tmp117_micro_celsius(0x0C80), 25_000_000);
5013 assert_eq!(pamoja_tmp117_nano_celsius(-1), -7_812_500);
5014 assert_eq!(pamoja_tmp117_raw_from_micro_celsius(-25_000_000), -3_200);
5015 assert_eq!(pamoja_tmp117_device_id(0x1117), 0x117);
5016 assert_eq!(pamoja_tmp117_revision(0x1117), 1);
5017 assert!(pamoja_tmp117_high_alert(0xA220));
5018 assert!(!pamoja_tmp117_low_alert(0xA220));
5019 assert!(pamoja_tmp117_data_ready(0xA220));
5020
5021 let mut config = PamojaTmp117Config {
5022 high_alert: 0,
5023 low_alert: 0,
5024 data_ready: 0,
5025 eeprom_busy: 0,
5026 mode: 0,
5027 cycle: 0,
5028 averaging: 0,
5029 therm_mode: 0,
5030 alert_active_high: 0,
5031 alert_pin_data_ready: 0,
5032 soft_reset: 0,
5033 };
5034 unsafe {
5036 assert_eq!(
5037 pamoja_tmp117_config_from_bits(PAMOJA_TMP117_CONFIG_RESET, &mut config),
5038 PamojaStatus::Ok
5039 );
5040 }
5041 assert_eq!(
5042 pamoja_tmp117_config_bits(config),
5043 PAMOJA_TMP117_CONFIG_RESET
5044 );
5045 assert_eq!(pamoja_tmp117_cycle_micros(5, 0), 4_000_000);
5046 }
5047
5048 #[test]
5049 fn an_hdc1080_measurement_decodes_and_an_undefined_code_is_refused() {
5050 let bytes = [0x60u8, 0x00, 0x40, 0x00];
5051 let mut measurement = PamojaHdc1080Measurement {
5052 temperature_raw: 0,
5053 humidity_raw: 0,
5054 milli_celsius: 0,
5055 celsius: 0.0,
5056 milli_percent: 0,
5057 relative_humidity: 0.0,
5058 };
5059 let mut config = PamojaHdc1080Config {
5060 software_reset: 0,
5061 heater: 0,
5062 sequential: 0,
5063 battery_low: 0,
5064 temperature_resolution_bits: 0,
5065 humidity_resolution_bits: 0,
5066 };
5067 let mut register = 0u16;
5068 unsafe {
5070 assert_eq!(
5071 pamoja_hdc1080_parse_measurement(bytes.as_ptr(), bytes.len(), &mut measurement),
5072 PamojaStatus::Ok
5073 );
5074 assert_eq!(
5075 pamoja_hdc1080_config_from_register(
5076 PAMOJA_HDC1080_CONFIGURATION_RESET,
5077 &mut config
5078 ),
5079 PamojaStatus::Ok
5080 );
5081 assert_eq!(
5082 pamoja_hdc1080_config_to_register(config, &mut register),
5083 PamojaStatus::Ok
5084 );
5085 }
5086 assert_eq!(measurement.milli_celsius, 21_875);
5087 assert_eq!(measurement.milli_percent, 25_000);
5088 assert_eq!(register, PAMOJA_HDC1080_CONFIGURATION_RESET);
5089
5090 let status = unsafe { pamoja_hdc1080_config_from_register(0x1300, &mut config) };
5092 assert_eq!(
5093 status,
5094 PamojaStatus::Codec,
5095 "the humidity-resolution code the datasheet leaves undefined is refused"
5096 );
5097 }
5098
5099 #[test]
5100 fn an_opt3001_result_decodes_and_a_reserved_range_has_no_full_scale() {
5101 assert_eq!(pamoja_opt3001_milli_lux(0xBFFF), 83_865_600);
5102 assert_eq!(pamoja_opt3001_raw_from_milli_lux(88_800), 0x28AC);
5103
5104 let mut full_scale = 0u32;
5105 unsafe {
5107 assert!(pamoja_opt3001_full_scale_milli_lux(0, &mut full_scale));
5108 assert_eq!(full_scale, 40_950);
5109 assert!(
5110 !pamoja_opt3001_full_scale_milli_lux(
5111 PAMOJA_OPT3001_RANGE_AUTOMATIC,
5112 &mut full_scale
5113 ),
5114 "a reserved range number has no full scale"
5115 );
5116 }
5117
5118 let mut config = PamojaOpt3001Config {
5119 range_number: 0,
5120 long_conversion: 0,
5121 mode: 0,
5122 overflow: 0,
5123 conversion_ready: 0,
5124 flag_high: 0,
5125 flag_low: 0,
5126 latched_window: 0,
5127 active_high: 0,
5128 mask_exponent: 0,
5129 fault_count: 0,
5130 };
5131 unsafe {
5133 assert_eq!(
5134 pamoja_opt3001_config_from_bits(PAMOJA_OPT3001_CONFIGURATION_RESET, &mut config),
5135 PamojaStatus::Ok
5136 );
5137 }
5138 assert_eq!(
5139 pamoja_opt3001_config_bits(config),
5140 PAMOJA_OPT3001_CONFIGURATION_RESET
5141 );
5142 }
5143
5144 #[test]
5145 fn an_ina226_converts_at_its_calibrated_resolution_and_checks_its_die() {
5146 const CURRENT_LSB: u32 = 1_000;
5149 assert_eq!(pamoja_ina226_calibration(CURRENT_LSB, 2), 2_560);
5150 assert_eq!(pamoja_ina226_minimum_current_lsb_microamps(15_000_000), 458);
5151 assert_eq!(pamoja_ina226_shunt_nanovolts(8_000), 20_000_000);
5152 assert_eq!(pamoja_ina226_bus_microvolts(9_584), 11_980_000);
5153 assert_eq!(
5154 pamoja_ina226_current_microamps(10_000, CURRENT_LSB),
5155 10_000_000
5156 );
5157 assert_eq!(
5159 pamoja_ina226_power_microwatts(4_792, CURRENT_LSB),
5160 119_800_000
5161 );
5162
5163 let mut die = PamojaIna226DieId {
5164 device: 0,
5165 revision: 0,
5166 };
5167 unsafe {
5169 assert_eq!(
5170 pamoja_ina226_identify(PAMOJA_INA226_MANUFACTURER_ID, 0x2260, &mut die),
5171 PamojaStatus::Ok
5172 );
5173 assert_eq!(die.device, PAMOJA_INA226_DEVICE_ID);
5174 assert_eq!(
5175 pamoja_ina226_identify(PAMOJA_INA226_MANUFACTURER_ID, 0x2270, &mut die),
5176 PamojaStatus::Codec,
5177 "a die that is not an INA226 is refused"
5178 );
5179 }
5180 }
5181}