pamoja_ffi/sensors_driver.rs
1//! The C ABI for the sensor drivers that run over an I2C bus.
2//!
3//! A driver runs the datasheet's whole conversation with a part over a [`PamojaI2cBus`]:
4//! reset, identify, read the calibration, configure, measure. It holds a share of the bus, so
5//! the caller may free its own bus handle straight after building the driver, or keep it to
6//! look at the bus between reads. A driver waits as its datasheet asks through the bus's
7//! delay, which sleeps only when real parts are on the other end.
8//!
9//! Every part has a simulated twin that answers a driver with nothing plugged in: a
10//! `_sim_part` that reads the datasheet's typical values and a `_sim_reporting` that reads
11//! whatever it is asked to. Either goes on a simulated bus with
12//! [`pamoja_i2c_bus_attach`](crate::hal::pamoja_i2c_bus_attach).
13//!
14//! A driver's failure is [`PamojaStatus::Io`] when the bus failed or the part never finished,
15//! and [`PamojaStatus::Codec`] when a part answered but is not the one the driver expected or
16//! its checksum failed, with the reason in the last error message either way.
17//!
18//! The DS18B20 is the exception: on a Linux board it is not driven over a bus but read through
19//! the files the kernel's 1-Wire driver serves, which [`pamoja_ds18b20_thermometer_read`] does.
20
21use std::fmt::{Debug, Display};
22use std::os::raw::c_char;
23use std::panic::{catch_unwind, AssertUnwindSafe};
24use std::path::Path;
25
26use pamoja_hal::bus::{BusDelay, I2cBus};
27use pamoja_sensors::driver::I2cRegisters;
28use pamoja_sensors::{
29 ads1115, bme280, bmp280, ds18b20, hdc1080, ina219, ina226, opt3001, scd4x, sht3x, tmp117,
30 DriverError,
31};
32
33use crate::hal::{PamojaI2cBus, PamojaI2cPart};
34use crate::sensors::{
35 conversion_time, humidity_resolution, repeatability_from_code, temperature_resolution,
36 PamojaAds1115Config, PamojaBme280Measurement, PamojaBmp280Coefficients, PamojaBmp280Reading,
37 PamojaDs18b20Reading, PamojaHdc1080Config, PamojaHdc1080Measurement, PamojaIna219Config,
38 PamojaIna226Config, PamojaIna226DieId, PamojaIna226MaskEnable, PamojaOpt3001Config,
39 PamojaScd4xMeasurement, PamojaSht3xMeasurement, PamojaSht3xStatus,
40 PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN, PAMOJA_BME280_CALIBRATION_TEMP_PRESS_LEN,
41 PAMOJA_BME280_MEASUREMENT_LEN, PAMOJA_BMP280_CALIBRATION_LEN, PAMOJA_BMP280_DATA_LEN,
42};
43use crate::{read_str, set_last_error, PamojaStatus, PamojaString};
44
45type Bme280Driver = bme280::Bme280<I2cRegisters<I2cBus>, BusDelay>;
46type Bmp280Driver = bmp280::Bmp280<I2cRegisters<I2cBus>, BusDelay>;
47type Tmp117Driver = tmp117::Tmp117<I2cBus, BusDelay>;
48type Opt3001Driver = opt3001::Opt3001<I2cBus, BusDelay>;
49type Hdc1080Driver = hdc1080::Hdc1080<I2cBus, BusDelay>;
50type Ina219Driver = ina219::Ina219<I2cBus, BusDelay>;
51type Ina226Driver = ina226::Ina226<I2cBus, BusDelay>;
52type Ads1115Driver = ads1115::Ads1115<I2cBus, BusDelay>;
53type Sht3xDriver = sht3x::Sht3x<I2cBus, BusDelay>;
54type Scd4xDriver = scd4x::Scd4x<I2cBus, BusDelay>;
55
56/// The status a simulated BME280 reports when it is neither measuring nor loading its
57/// calibration.
58pub const PAMOJA_BME280_SIM_STATUS_IDLE: u8 = 0x00;
59
60/// The status a simulated BMP280 reports when it is neither measuring nor loading its
61/// calibration.
62pub const PAMOJA_BMP280_SIM_STATUS_IDLE: u8 = 0x00;
63
64/// The temperature a simulated TMP117 reports unless it is asked for another.
65pub const PAMOJA_TMP117_SIM_CELSIUS: f32 = 21.25;
66
67/// The illuminance a simulated OPT3001 reports unless it is asked for another.
68pub const PAMOJA_OPT3001_SIM_LUX: f32 = 380.0;
69
70/// The temperature a simulated HDC1080 reports unless it is asked for another.
71pub const PAMOJA_HDC1080_SIM_CELSIUS: f32 = 22.5;
72
73/// The relative humidity a simulated HDC1080 reports unless it is asked for another.
74pub const PAMOJA_HDC1080_SIM_RELATIVE_HUMIDITY: f32 = 45.0;
75
76/// The shunt a simulated INA219 sits across, in milliohms: the common breakout's.
77pub const PAMOJA_INA219_SIM_SHUNT_MILLIOHMS: u32 = 100;
78
79/// The largest current a simulated INA219 is sized for, in microamps.
80pub const PAMOJA_INA219_SIM_MAX_MICROAMPS: u32 = 3_200_000;
81
82/// The bus voltage a simulated INA219 reports unless it is asked for another, in millivolts.
83pub const PAMOJA_INA219_SIM_BUS_MILLIVOLTS: u32 = 12_000;
84
85/// The current a simulated INA219 reports unless it is asked for another, in microamps.
86pub const PAMOJA_INA219_SIM_MICROAMPS: i32 = 500_000;
87
88/// The shunt a simulated INA226 sits across, in milliohms.
89pub const PAMOJA_INA226_SIM_SHUNT_MILLIOHMS: u32 = 100;
90
91/// The largest current a simulated INA226 is sized for, in microamps.
92pub const PAMOJA_INA226_SIM_MAX_MICROAMPS: u32 = 3_200_000;
93
94/// The bus voltage a simulated INA226 reports unless it is asked for another, in microvolts.
95pub const PAMOJA_INA226_SIM_BUS_MICROVOLTS: u32 = 12_000_000;
96
97/// The current a simulated INA226 reports unless it is asked for another, in microamps.
98pub const PAMOJA_INA226_SIM_MICROAMPS: i32 = 500_000;
99
100/// The voltage a simulated ADS1115 reports unless it is asked for another: half a 3.3 V
101/// supply.
102pub const PAMOJA_ADS1115_SIM_VOLTS: f32 = 1.65;
103
104/// The temperature a simulated SHT3x reports unless it is asked for another.
105pub const PAMOJA_SHT3X_SIM_CELSIUS: f32 = 22.5;
106
107/// The relative humidity a simulated SHT3x reports unless it is asked for another.
108pub const PAMOJA_SHT3X_SIM_RELATIVE_HUMIDITY: f32 = 45.0;
109
110/// The carbon dioxide a simulated SCD4x reports unless it is asked for another, in parts per
111/// million.
112pub const PAMOJA_SCD4X_SIM_CO2_PPM: u16 = 800;
113
114/// The temperature a simulated SCD4x reports unless it is asked for another.
115pub const PAMOJA_SCD4X_SIM_CELSIUS: f32 = 22.5;
116
117/// The relative humidity a simulated SCD4x reports unless it is asked for another.
118pub const PAMOJA_SCD4X_SIM_RELATIVE_HUMIDITY: f32 = 45.0;
119
120/// The serial number every simulated SCD4x reports.
121pub const PAMOJA_SCD4X_SIM_SERIAL: u64 = 0x0000_5A4D_0C1E_2B3F;
122
123const _: () = assert!(PAMOJA_BME280_SIM_STATUS_IDLE == bme280::sim::STATUS_IDLE);
124const _: () = assert!(PAMOJA_BMP280_SIM_STATUS_IDLE == bmp280::sim::STATUS_IDLE);
125const _: () = assert!(PAMOJA_TMP117_SIM_CELSIUS == tmp117::sim::CELSIUS);
126const _: () = assert!(PAMOJA_OPT3001_SIM_LUX == opt3001::sim::LUX);
127const _: () = assert!(PAMOJA_HDC1080_SIM_CELSIUS == hdc1080::sim::CELSIUS);
128const _: () = assert!(PAMOJA_HDC1080_SIM_RELATIVE_HUMIDITY == hdc1080::sim::RELATIVE_HUMIDITY);
129const _: () = assert!(PAMOJA_INA219_SIM_SHUNT_MILLIOHMS == ina219::sim::SHUNT_MILLIOHMS);
130const _: () = assert!(PAMOJA_INA219_SIM_MAX_MICROAMPS == ina219::sim::MAX_MICROAMPS);
131const _: () = assert!(PAMOJA_INA219_SIM_BUS_MILLIVOLTS == ina219::sim::BUS_MILLIVOLTS);
132const _: () = assert!(PAMOJA_INA219_SIM_MICROAMPS == ina219::sim::MICROAMPS);
133const _: () = assert!(PAMOJA_INA226_SIM_SHUNT_MILLIOHMS == ina226::sim::SHUNT_MILLIOHMS);
134const _: () = assert!(PAMOJA_INA226_SIM_MAX_MICROAMPS == ina226::sim::MAX_MICROAMPS);
135const _: () = assert!(PAMOJA_INA226_SIM_BUS_MICROVOLTS == ina226::sim::BUS_MICROVOLTS);
136const _: () = assert!(PAMOJA_INA226_SIM_MICROAMPS == ina226::sim::MICROAMPS);
137const _: () = assert!(PAMOJA_ADS1115_SIM_VOLTS == ads1115::sim::VOLTS);
138const _: () = assert!(PAMOJA_SHT3X_SIM_CELSIUS == sht3x::sim::CELSIUS);
139const _: () = assert!(PAMOJA_SHT3X_SIM_RELATIVE_HUMIDITY == sht3x::sim::RELATIVE_HUMIDITY);
140const _: () = assert!(PAMOJA_SCD4X_SIM_CO2_PPM == scd4x::sim::CO2_PPM);
141const _: () = assert!(PAMOJA_SCD4X_SIM_CELSIUS == scd4x::sim::CELSIUS);
142const _: () = assert!(PAMOJA_SCD4X_SIM_RELATIVE_HUMIDITY == scd4x::sim::RELATIVE_HUMIDITY);
143const _: () = assert!(PAMOJA_SCD4X_SIM_SERIAL == scd4x::sim::SERIAL);
144
145/// How a BME280 driver measures: the oversampling of each measurement and the IIR filter.
146#[repr(C)]
147#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub struct PamojaBme280Settings {
149 /// The temperature oversampling code, `0..=5`, where `0` skips the measurement.
150 pub temperature: u8,
151 /// The pressure oversampling code, `0..=5`, where `0` skips the measurement.
152 pub pressure: u8,
153 /// The humidity oversampling code, `0..=5`, where `0` skips the measurement.
154 pub humidity: u8,
155 /// The IIR filter code, `0..=4`, where `0` is off.
156 pub filter: u8,
157}
158
159/// How a BMP280 driver measures: the oversampling of each measurement and the IIR filter.
160#[repr(C)]
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162pub struct PamojaBmp280Settings {
163 /// The temperature oversampling code, `0..=5`, where `0` skips the measurement.
164 pub temperature: u8,
165 /// The pressure oversampling code, `0..=5`, where `0` skips the measurement.
166 pub pressure: u8,
167 /// The IIR filter's `filter[2:0]` code, `0` for the filter off, written as given.
168 pub filter: u8,
169}
170
171/// A TMP117 temperature result.
172#[repr(C)]
173#[derive(Clone, Copy, Debug, PartialEq)]
174pub struct PamojaTmp117Reading {
175 /// The temperature register, 7.8125 millidegrees Celsius per count.
176 pub raw: i16,
177 /// The temperature in micro-degrees Celsius, exact in integer arithmetic.
178 pub micro_celsius: i32,
179 /// The temperature in degrees Celsius.
180 pub celsius: f32,
181}
182
183/// The TMP117's alert flags: whether a result since they were last read crossed a limit.
184#[repr(C)]
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub struct PamojaTmp117Alerts {
187 /// `1` when a result was above the high limit.
188 pub high: u8,
189 /// `1` when a result was below the low limit.
190 pub low: u8,
191}
192
193/// How an OPT3001 driver measures.
194#[repr(C)]
195#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub struct PamojaOpt3001Settings {
197 /// `1` integrates each conversion for 800 ms, for resolution; `0` for 100 ms, for speed.
198 pub long_conversion: u8,
199 /// The full-scale range number, `0..=11`, or `12` to let the part choose.
200 pub range_number: u8,
201}
202
203/// An OPT3001 illuminance result.
204#[repr(C)]
205#[derive(Clone, Copy, Debug, PartialEq)]
206pub struct PamojaOpt3001Reading {
207 /// The result register: a four-bit exponent over a twelve-bit mantissa.
208 pub raw: u16,
209 /// The illuminance in millilux, exact in integer arithmetic.
210 pub milli_lux: u32,
211 /// The illuminance in lux.
212 pub lux: f32,
213}
214
215/// How an HDC1080 driver measures: the resolution of each channel.
216#[repr(C)]
217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub struct PamojaHdc1080Settings {
219 /// The temperature resolution in bits: 14 or 11.
220 pub temperature_resolution_bits: u8,
221 /// The humidity resolution in bits: 14, 11, or 8.
222 pub humidity_resolution_bits: u8,
223}
224
225/// How an INA219 driver measures: the shunt, the current it is sized for, and the register
226/// settings.
227#[repr(C)]
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229pub struct PamojaIna219Settings {
230 /// The shunt resistance in milliohms.
231 pub shunt_milliohms: u32,
232 /// The largest current the shunt will carry, in microamps, which sets the finest current
233 /// step the calibration allows.
234 pub max_microamps: u32,
235 /// A current step in microamps per count to use instead, such as a round 100, or `0` to
236 /// take the finest step for `max_microamps`.
237 pub current_lsb_microamps: u32,
238 /// The range, gain, and converter settings; the mode is chosen per conversion.
239 pub config: PamojaIna219Config,
240}
241
242/// One INA219 conversion: the four result registers as read, and what they mean.
243#[repr(C)]
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub struct PamojaIna219Reading {
246 /// The shunt-voltage register.
247 pub shunt: i16,
248 /// The bus-voltage register, flags included.
249 pub bus: u16,
250 /// The current register.
251 pub current: i16,
252 /// The power register.
253 pub power: u16,
254 /// The current step the calibration programmed, in microamps per count.
255 pub current_lsb_microamps: u32,
256 /// The shunt voltage in microvolts.
257 pub shunt_microvolts: i32,
258 /// The bus voltage in millivolts.
259 pub bus_millivolts: u32,
260 /// The current in microamps; negative flows the other way through the shunt.
261 pub current_microamps: i32,
262 /// The power in microwatts.
263 pub power_microwatts: u32,
264 /// `1` when the part's arithmetic overflowed and the current and power are meaningless.
265 pub math_overflow: u8,
266}
267
268/// How an INA226 driver measures: the shunt, the current it is sized for, and the register
269/// settings.
270#[repr(C)]
271#[derive(Clone, Copy, Debug, PartialEq, Eq)]
272pub struct PamojaIna226Settings {
273 /// The shunt resistance in milliohms.
274 pub shunt_milliohms: u32,
275 /// The largest current the shunt will carry, in microamps, which sets the finest current
276 /// step the calibration allows.
277 pub max_microamps: u32,
278 /// A current step in microamps per count to use instead, or `0` to take the finest step
279 /// for `max_microamps`.
280 pub current_lsb_microamps: u32,
281 /// The averaging and conversion times; the mode is chosen per conversion.
282 pub config: PamojaIna226Config,
283}
284
285/// One INA226 conversion: the four result registers as read, and what they mean.
286#[repr(C)]
287#[derive(Clone, Copy, Debug, PartialEq)]
288pub struct PamojaIna226Reading {
289 /// The shunt-voltage register.
290 pub shunt: i16,
291 /// The bus-voltage register.
292 pub bus: u16,
293 /// The current register.
294 pub current: i16,
295 /// The power register.
296 pub power: u16,
297 /// The current step the calibration programmed, in microamps per count.
298 pub current_lsb_microamps: u32,
299 /// The shunt voltage in nanovolts.
300 pub shunt_nanovolts: i32,
301 /// The shunt voltage in millivolts.
302 pub shunt_millivolts: f32,
303 /// The bus voltage in microvolts.
304 pub bus_microvolts: u32,
305 /// The bus voltage in volts.
306 pub bus_volts: f32,
307 /// The current in microamps; negative flows the other way through the shunt.
308 pub current_microamps: i32,
309 /// The current in amps.
310 pub current_amps: f32,
311 /// The power in microwatts.
312 pub power_microwatts: u32,
313 /// The power in watts.
314 pub power_watts: f32,
315 /// `1` when the part's arithmetic overflowed and the current and power are meaningless.
316 pub math_overflow: u8,
317}
318
319/// How an ADS1115 driver converts: which input, at which range, how fast.
320#[repr(C)]
321#[derive(Clone, Copy, Debug, PartialEq, Eq)]
322pub struct PamojaAds1115Settings {
323 /// The input multiplexer code, `0..=7`: `0..=3` a differential pair, `4..=7` one input
324 /// against ground.
325 pub mux: u8,
326 /// The gain code, `0..=7`, which sets the full-scale range.
327 pub pga: u8,
328 /// The data-rate code, `0..=7`, from 8 to 860 samples per second.
329 pub data_rate: u8,
330}
331
332/// One ADS1115 conversion.
333#[repr(C)]
334#[derive(Clone, Copy, Debug, PartialEq)]
335pub struct PamojaAds1115Sample {
336 /// The conversion register, two's complement.
337 pub raw: i16,
338 /// The gain code the conversion ran at.
339 pub pga: u8,
340 /// The voltage in nanovolts, exact in integer arithmetic.
341 pub nanovolts: i64,
342 /// The voltage in volts.
343 pub volts: f32,
344 /// `1` when the conversion sits at an end code, where the output clips for a signal past
345 /// the range, so the voltage is a bound rather than the reading.
346 pub clipped: u8,
347}
348
349/// A BME280 driven over an I2C bus. Opaque; release it with [`pamoja_bme280_free`].
350pub struct PamojaBme280 {
351 sensor: Bme280Driver,
352}
353
354/// A BMP280 driven over an I2C bus. Opaque; release it with [`pamoja_bmp280_free`].
355pub struct PamojaBmp280 {
356 sensor: Bmp280Driver,
357}
358
359/// A TMP117 driven over an I2C bus. Opaque; release it with [`pamoja_tmp117_free`].
360pub struct PamojaTmp117 {
361 sensor: Tmp117Driver,
362}
363
364/// An OPT3001 driven over an I2C bus. Opaque; release it with [`pamoja_opt3001_free`].
365pub struct PamojaOpt3001 {
366 sensor: Opt3001Driver,
367}
368
369/// An HDC1080 driven over an I2C bus. Opaque; release it with [`pamoja_hdc1080_free`].
370pub struct PamojaHdc1080 {
371 sensor: Hdc1080Driver,
372}
373
374/// An INA219 driven over an I2C bus. Opaque; release it with [`pamoja_ina219_free`].
375pub struct PamojaIna219 {
376 sensor: Ina219Driver,
377}
378
379/// An INA226 driven over an I2C bus. Opaque; release it with [`pamoja_ina226_free`].
380pub struct PamojaIna226 {
381 sensor: Ina226Driver,
382}
383
384/// An ADS1115 driven over an I2C bus. Opaque; release it with [`pamoja_ads1115_free`].
385pub struct PamojaAds1115 {
386 sensor: Ads1115Driver,
387}
388
389/// An SHT3x driven over an I2C bus. Opaque; release it with [`pamoja_sht3x_free`].
390pub struct PamojaSht3x {
391 sensor: Sht3xDriver,
392}
393
394/// An SCD40 or SCD41 driven over an I2C bus. Opaque; release it with [`pamoja_scd4x_free`].
395pub struct PamojaScd4x {
396 sensor: Scd4xDriver,
397}
398
399/// A DS18B20 the Linux kernel serves as a `w1_slave` file. Opaque; release it with
400/// [`pamoja_ds18b20_thermometer_free`].
401pub struct PamojaDs18b20Thermometer {
402 thermometer: ds18b20::linux::Thermometer,
403}
404
405/// The DS18B20s the kernel has found, in order of their directory names. Opaque; release it
406/// with [`pamoja_ds18b20_thermometers_free`].
407pub struct PamojaDs18b20Thermometers {
408 found: Vec<ds18b20::linux::Thermometer>,
409}
410
411/// Returns the settings a BME280 driver starts with: every measurement at oversampling x1 and
412/// the filter off.
413///
414/// # Returns
415///
416/// The settings.
417#[no_mangle]
418pub extern "C" fn pamoja_bme280_settings_default() -> PamojaBme280Settings {
419 PamojaBme280Settings {
420 temperature: bme280::Oversampling::X1.code(),
421 pressure: bme280::Oversampling::X1.code(),
422 humidity: bme280::Oversampling::X1.code(),
423 filter: bme280::Filter::Off.code(),
424 }
425}
426
427/// Creates a BME280 driver on a bus. Nothing is sent until [`pamoja_bme280_init`] or the first
428/// [`pamoja_bme280_measure`].
429///
430/// # Arguments
431///
432/// * `bus` - the bus the part is on; the driver holds its own share.
433/// * `address` - [`crate::sensors::PAMOJA_BME280_I2C_ADDRESS_PRIMARY`] with SDO low, or
434/// [`crate::sensors::PAMOJA_BME280_I2C_ADDRESS_SECONDARY`] with SDO high.
435/// * `settings` - the oversampling and the filter.
436/// * `out_sensor` - receives the driver.
437///
438/// # Returns
439///
440/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
441/// for a null argument.
442///
443/// # Safety
444///
445/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
446#[no_mangle]
447pub unsafe extern "C" fn pamoja_bme280_new(
448 bus: *const PamojaI2cBus,
449 address: u8,
450 settings: PamojaBme280Settings,
451 out_sensor: *mut *mut PamojaBme280,
452) -> PamojaStatus {
453 build(bus, out_sensor, |bus, delay| PamojaBme280 {
454 sensor: bme280::Bme280::i2c(bus, address, delay)
455 .with_oversampling(
456 bme280::Oversampling::from_code(settings.temperature),
457 bme280::Oversampling::from_code(settings.pressure),
458 bme280::Oversampling::from_code(settings.humidity),
459 )
460 .with_filter(bme280::Filter::from_code(settings.filter)),
461 })
462}
463
464/// Resets the part, checks it is a BME280, reads its calibration, and writes the settings, in
465/// the order the datasheet requires, leaving the part asleep.
466///
467/// # Arguments
468///
469/// * `sensor` - the driver.
470///
471/// # Returns
472///
473/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
474/// [`PamojaStatus::Io`] when the bus fails or the calibration never finishes loading; or
475/// [`PamojaStatus::Codec`] when the part at the address is not a BME280.
476///
477/// # Safety
478///
479/// `sensor` must be a live handle or null.
480#[no_mangle]
481pub unsafe extern "C" fn pamoja_bme280_init(sensor: *mut PamojaBme280) -> PamojaStatus {
482 run(sensor, |held| held.sensor.init())
483}
484
485/// Runs one forced measurement and compensates it, initializing the part first if
486/// [`pamoja_bme280_init`] has not run.
487///
488/// # Arguments
489///
490/// * `sensor` - the driver.
491/// * `out_measurement` - receives the reading.
492///
493/// # Returns
494///
495/// As [`pamoja_bme280_init`], with [`PamojaStatus::Io`] also when the part is still measuring
496/// after the datasheet's time.
497///
498/// # Safety
499///
500/// `sensor` must be a live handle or null, and `out_measurement` a writable pointer or null.
501#[no_mangle]
502pub unsafe extern "C" fn pamoja_bme280_measure(
503 sensor: *mut PamojaBme280,
504 out_measurement: *mut PamojaBme280Measurement,
505) -> PamojaStatus {
506 fill(
507 sensor,
508 out_measurement,
509 "out_measurement",
510 |held| held.sensor.measure(),
511 |reading| PamojaBme280Measurement {
512 celsius: reading.celsius(),
513 pascals: reading.pascals(),
514 hectopascals: reading.hectopascals(),
515 relative_humidity_percent: reading.relative_humidity_percent(),
516 },
517 )
518}
519
520/// Releases a driver and its share of the bus. A null pointer is ignored.
521///
522/// # Safety
523///
524/// `sensor` must be a handle from [`pamoja_bme280_new`] that has not been freed, or null.
525#[no_mangle]
526pub unsafe extern "C" fn pamoja_bme280_free(sensor: *mut PamojaBme280) {
527 release(sensor);
528}
529
530/// Creates a simulated BME280 holding a real part's calibration and one measurement it took,
531/// which compensate to 20.44 C, 848.05 hPa, and 44.65 %.
532///
533/// # Arguments
534///
535/// * `address` - the address it answers to.
536///
537/// # Returns
538///
539/// The part, which the caller puts on a simulated bus and releases with
540/// [`crate::hal::pamoja_i2c_part_free`].
541#[no_mangle]
542pub extern "C" fn pamoja_bme280_sim_part(address: u8) -> *mut PamojaI2cPart {
543 PamojaI2cPart::into_raw(bme280::sim::part(address))
544}
545
546/// Creates a simulated BME280 that reads what it is asked to.
547///
548/// # Arguments
549///
550/// * `address` - the address it answers to.
551/// * `celsius` - the temperature it reports.
552/// * `hectopascals` - the pressure it reports.
553/// * `relative_humidity` - the humidity it reports, as a percentage.
554///
555/// # Returns
556///
557/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its readings
558/// land within a hundredth of a degree, a hundredth of a hectopascal, and a thousandth of a
559/// percent of what was asked for, the nearest its converter can represent.
560#[no_mangle]
561pub extern "C" fn pamoja_bme280_sim_reporting(
562 address: u8,
563 celsius: f32,
564 hectopascals: f32,
565 relative_humidity: f32,
566) -> *mut PamojaI2cPart {
567 PamojaI2cPart::into_raw(bme280::sim::reporting(
568 address,
569 celsius,
570 hectopascals,
571 relative_humidity,
572 ))
573}
574
575/// Copies the calibration a simulated BME280 holds: the two blocks a driver reads at start-up.
576///
577/// # Arguments
578///
579/// * `out_temp_press` - receives the 26-byte temperature and pressure block.
580/// * `out_humidity` - receives the 7-byte humidity block.
581///
582/// # Returns
583///
584/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null pointer.
585///
586/// # Safety
587///
588/// `out_temp_press` must point to 26 writable bytes and `out_humidity` to 7.
589#[no_mangle]
590pub unsafe extern "C" fn pamoja_bme280_sim_calibration(
591 out_temp_press: *mut u8,
592 out_humidity: *mut u8,
593) -> PamojaStatus {
594 if out_temp_press.is_null() || out_humidity.is_null() {
595 set_last_error("out_temp_press and out_humidity must not be null".to_owned());
596 return PamojaStatus::InvalidArgument;
597 }
598 std::slice::from_raw_parts_mut(out_temp_press, PAMOJA_BME280_CALIBRATION_TEMP_PRESS_LEN)
599 .copy_from_slice(&bme280::sim::CALIBRATION);
600 std::slice::from_raw_parts_mut(out_humidity, PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN)
601 .copy_from_slice(&bme280::sim::CALIBRATION_HUMIDITY);
602 PamojaStatus::Ok
603}
604
605/// Copies the one measurement a simulated BME280 holds: the eight data registers a real part
606/// read, which compensate to 20.44 C, 848.05 hPa, and 44.65 % against its calibration.
607///
608/// # Arguments
609///
610/// * `out_burst` - receives the eight bytes a burst read returns.
611///
612/// # Returns
613///
614/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null pointer.
615///
616/// # Safety
617///
618/// `out_burst` must point to 8 writable bytes.
619#[no_mangle]
620pub unsafe extern "C" fn pamoja_bme280_sim_burst(out_burst: *mut u8) -> PamojaStatus {
621 copy_out(out_burst, "out_burst", &bme280::sim::BURST)
622}
623
624/// Builds the eight data registers a simulated BME280 holds when it reports a reading.
625///
626/// # Arguments
627///
628/// * `celsius` - the temperature.
629/// * `hectopascals` - the pressure.
630/// * `relative_humidity` - the humidity, as a percentage.
631/// * `out_burst` - receives the eight bytes a burst read returns.
632///
633/// # Returns
634///
635/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null pointer.
636///
637/// # Safety
638///
639/// `out_burst` must point to 8 writable bytes.
640#[no_mangle]
641pub unsafe extern "C" fn pamoja_bme280_sim_burst_for(
642 celsius: f32,
643 hectopascals: f32,
644 relative_humidity: f32,
645 out_burst: *mut u8,
646) -> PamojaStatus {
647 let burst: [u8; PAMOJA_BME280_MEASUREMENT_LEN] =
648 bme280::sim::burst_for(celsius, hectopascals, relative_humidity);
649 copy_out(out_burst, "out_burst", &burst)
650}
651
652/// Returns the settings a BMP280 driver starts with: both measurements at oversampling x1 and
653/// the filter off.
654///
655/// # Returns
656///
657/// The settings.
658#[no_mangle]
659pub extern "C" fn pamoja_bmp280_settings_default() -> PamojaBmp280Settings {
660 PamojaBmp280Settings {
661 temperature: bmp280::Oversampling::X1.code(),
662 pressure: bmp280::Oversampling::X1.code(),
663 filter: 0,
664 }
665}
666
667/// Creates a BMP280 driver on a bus. Nothing is sent until [`pamoja_bmp280_init`] or the first
668/// [`pamoja_bmp280_measure`].
669///
670/// # Arguments
671///
672/// * `bus` - the bus the part is on; the driver holds its own share.
673/// * `address` - [`crate::sensors::PAMOJA_BMP280_I2C_ADDRESS_PRIMARY`] with SDO low, or
674/// [`crate::sensors::PAMOJA_BMP280_I2C_ADDRESS_SECONDARY`] with SDO high.
675/// * `settings` - the oversampling and the filter.
676/// * `out_sensor` - receives the driver.
677///
678/// # Returns
679///
680/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
681/// for a null argument.
682///
683/// # Safety
684///
685/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
686#[no_mangle]
687pub unsafe extern "C" fn pamoja_bmp280_new(
688 bus: *const PamojaI2cBus,
689 address: u8,
690 settings: PamojaBmp280Settings,
691 out_sensor: *mut *mut PamojaBmp280,
692) -> PamojaStatus {
693 build(bus, out_sensor, |bus, delay| PamojaBmp280 {
694 sensor: bmp280::Bmp280::i2c(bus, address, delay)
695 .with_oversampling(
696 bmp280::Oversampling::from_code(settings.temperature),
697 bmp280::Oversampling::from_code(settings.pressure),
698 )
699 .with_filter(settings.filter),
700 })
701}
702
703/// Resets the part, checks it is a BMP280, reads its trimming coefficients, and writes the
704/// settings, leaving the part asleep.
705///
706/// # Arguments
707///
708/// * `sensor` - the driver.
709///
710/// # Returns
711///
712/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
713/// [`PamojaStatus::Io`] when the bus fails or the trimming never finishes loading; or
714/// [`PamojaStatus::Codec`] when the part at the address is not a BMP280.
715///
716/// # Safety
717///
718/// `sensor` must be a live handle or null.
719#[no_mangle]
720pub unsafe extern "C" fn pamoja_bmp280_init(sensor: *mut PamojaBmp280) -> PamojaStatus {
721 run(sensor, |held| held.sensor.init())
722}
723
724/// Runs one forced measurement and compensates it, initializing the part first if
725/// [`pamoja_bmp280_init`] has not run.
726///
727/// # Arguments
728///
729/// * `sensor` - the driver.
730/// * `out_reading` - receives the reading.
731///
732/// # Returns
733///
734/// As [`pamoja_bmp280_init`], with [`PamojaStatus::Io`] also when the part is still measuring
735/// after the datasheet's time.
736///
737/// # Safety
738///
739/// `sensor` must be a live handle or null, and `out_reading` a writable pointer or null.
740#[no_mangle]
741pub unsafe extern "C" fn pamoja_bmp280_measure(
742 sensor: *mut PamojaBmp280,
743 out_reading: *mut PamojaBmp280Reading,
744) -> PamojaStatus {
745 fill(
746 sensor,
747 out_reading,
748 "out_reading",
749 |held| held.sensor.measure(),
750 |reading| PamojaBmp280Reading {
751 celsius: reading.celsius(),
752 pascals: reading.pascals(),
753 hectopascals: reading.hectopascals(),
754 },
755 )
756}
757
758/// Copies the trimming coefficients a BMP280 driver read at initialization.
759///
760/// # Arguments
761///
762/// * `sensor` - the driver.
763/// * `out_coefficients` - receives the coefficients.
764///
765/// # Returns
766///
767/// `true` with the coefficients in `out_coefficients`; `false` before the part has been
768/// initialized, or for a null argument.
769///
770/// # Safety
771///
772/// `sensor` must be a live handle or null, and `out_coefficients` a writable pointer or null.
773#[no_mangle]
774pub unsafe extern "C" fn pamoja_bmp280_coefficients(
775 sensor: *const PamojaBmp280,
776 out_coefficients: *mut PamojaBmp280Coefficients,
777) -> bool {
778 let Some(held) = sensor.as_ref() else {
779 return false;
780 };
781 store(
782 out_coefficients,
783 held.sensor.calibration().map(|c| (*c).into()),
784 )
785}
786
787/// Releases a driver and its share of the bus. A null pointer is ignored.
788///
789/// # Safety
790///
791/// `sensor` must be a handle from [`pamoja_bmp280_new`] that has not been freed, or null.
792#[no_mangle]
793pub unsafe extern "C" fn pamoja_bmp280_free(sensor: *mut PamojaBmp280) {
794 release(sensor);
795}
796
797/// Creates a simulated BMP280 holding a real part's trimming and one measurement it took,
798/// which compensate to 20.44 C and 848.05 hPa.
799///
800/// # Arguments
801///
802/// * `address` - the address it answers to.
803///
804/// # Returns
805///
806/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
807#[no_mangle]
808pub extern "C" fn pamoja_bmp280_sim_part(address: u8) -> *mut PamojaI2cPart {
809 PamojaI2cPart::into_raw(bmp280::sim::part(address))
810}
811
812/// Creates a simulated BMP280 that reads what it is asked to.
813///
814/// # Arguments
815///
816/// * `address` - the address it answers to.
817/// * `celsius` - the temperature it reports.
818/// * `hectopascals` - the pressure it reports.
819///
820/// # Returns
821///
822/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its readings
823/// land within a hundredth of a degree and a hundredth of a hectopascal.
824#[no_mangle]
825pub extern "C" fn pamoja_bmp280_sim_reporting(
826 address: u8,
827 celsius: f32,
828 hectopascals: f32,
829) -> *mut PamojaI2cPart {
830 PamojaI2cPart::into_raw(bmp280::sim::reporting(address, celsius, hectopascals))
831}
832
833/// Copies the 24 trimming bytes a simulated BMP280 holds.
834///
835/// # Arguments
836///
837/// * `out_calibration` - receives the bytes.
838///
839/// # Returns
840///
841/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null pointer.
842///
843/// # Safety
844///
845/// `out_calibration` must point to 24 writable bytes.
846#[no_mangle]
847pub unsafe extern "C" fn pamoja_bmp280_sim_calibration(out_calibration: *mut u8) -> PamojaStatus {
848 let calibration: [u8; PAMOJA_BMP280_CALIBRATION_LEN] = bmp280::sim::CALIBRATION;
849 copy_out(out_calibration, "out_calibration", &calibration)
850}
851
852/// Copies the six data registers a simulated BMP280 holds: one measurement a real part took.
853///
854/// # Arguments
855///
856/// * `out_burst` - receives the six bytes a burst read returns.
857///
858/// # Returns
859///
860/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null pointer.
861///
862/// # Safety
863///
864/// `out_burst` must point to 6 writable bytes.
865#[no_mangle]
866pub unsafe extern "C" fn pamoja_bmp280_sim_burst(out_burst: *mut u8) -> PamojaStatus {
867 let burst: [u8; PAMOJA_BMP280_DATA_LEN] = bmp280::sim::BURST;
868 copy_out(out_burst, "out_burst", &burst)
869}
870
871/// Builds the six data registers a simulated BMP280 holds when it reports a reading.
872///
873/// # Arguments
874///
875/// * `celsius` - the temperature.
876/// * `hectopascals` - the pressure.
877/// * `out_burst` - receives the six bytes a burst read returns.
878///
879/// # Returns
880///
881/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null pointer.
882///
883/// # Safety
884///
885/// `out_burst` must point to 6 writable bytes.
886#[no_mangle]
887pub unsafe extern "C" fn pamoja_bmp280_sim_burst_for(
888 celsius: f32,
889 hectopascals: f32,
890 out_burst: *mut u8,
891) -> PamojaStatus {
892 copy_out(
893 out_burst,
894 "out_burst",
895 &bmp280::sim::burst_for(celsius, hectopascals),
896 )
897}
898
899/// Creates a TMP117 driver on a bus. Nothing is sent until [`pamoja_tmp117_init`] or the first
900/// [`pamoja_tmp117_measure`].
901///
902/// # Arguments
903///
904/// * `bus` - the bus the part is on; the driver holds its own share.
905/// * `address` - one of the `PAMOJA_TMP117_ADDRESS_` constants, by where ADD0 is tied.
906/// * `averaging` - the averaging code, `0..=3`, for 1, 8, 32, or 64 conversions per result;
907/// `1`, eight, is the factory setting.
908/// * `out_sensor` - receives the driver.
909///
910/// # Returns
911///
912/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
913/// for a null argument.
914///
915/// # Safety
916///
917/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
918#[no_mangle]
919pub unsafe extern "C" fn pamoja_tmp117_new(
920 bus: *const PamojaI2cBus,
921 address: u8,
922 averaging: u8,
923 out_sensor: *mut *mut PamojaTmp117,
924) -> PamojaStatus {
925 build(bus, out_sensor, |bus, delay| PamojaTmp117 {
926 sensor: tmp117::Tmp117::new(bus, address, delay)
927 .with_averaging(tmp117::Averaging::from_code(averaging)),
928 })
929}
930
931/// Checks the part is a TMP117, waits for its EEPROM to finish loading, and writes the
932/// settings with the part in shutdown.
933///
934/// # Arguments
935///
936/// * `sensor` - the driver.
937///
938/// # Returns
939///
940/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
941/// [`PamojaStatus::Io`] when the bus fails or the EEPROM never reports ready; or
942/// [`PamojaStatus::Codec`] when the device id is not a TMP117's.
943///
944/// # Safety
945///
946/// `sensor` must be a live handle or null.
947#[no_mangle]
948pub unsafe extern "C" fn pamoja_tmp117_init(sensor: *mut PamojaTmp117) -> PamojaStatus {
949 run(sensor, |held| held.sensor.init())
950}
951
952/// Runs one conversion and reads the temperature, initializing the part first if
953/// [`pamoja_tmp117_init`] has not run.
954///
955/// # Arguments
956///
957/// * `sensor` - the driver.
958/// * `out_reading` - receives the temperature.
959///
960/// # Returns
961///
962/// As [`pamoja_tmp117_init`], with [`PamojaStatus::Io`] also when the part never reports the
963/// conversion done.
964///
965/// # Safety
966///
967/// `sensor` must be a live handle or null, and `out_reading` a writable pointer or null.
968#[no_mangle]
969pub unsafe extern "C" fn pamoja_tmp117_measure(
970 sensor: *mut PamojaTmp117,
971 out_reading: *mut PamojaTmp117Reading,
972) -> PamojaStatus {
973 fill(
974 sensor,
975 out_reading,
976 "out_reading",
977 |held| held.sensor.measure(),
978 |reading| PamojaTmp117Reading {
979 raw: reading.raw(),
980 micro_celsius: reading.micro_celsius(),
981 celsius: reading.celsius(),
982 },
983 )
984}
985
986/// Writes the high and low limits the part compares each result against.
987///
988/// # Arguments
989///
990/// * `sensor` - the driver.
991/// * `high_celsius` - the high limit; the factory value is 192 C.
992/// * `low_celsius` - the low limit; the factory value is -256 C.
993///
994/// # Returns
995///
996/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null driver, or
997/// [`PamojaStatus::Io`] when the bus fails.
998///
999/// # Safety
1000///
1001/// `sensor` must be a live handle or null.
1002#[no_mangle]
1003pub unsafe extern "C" fn pamoja_tmp117_set_alert_limits(
1004 sensor: *mut PamojaTmp117,
1005 high_celsius: f32,
1006 low_celsius: f32,
1007) -> PamojaStatus {
1008 run(sensor, |held| {
1009 held.sensor.set_alert_limits(high_celsius, low_celsius)
1010 })
1011}
1012
1013/// Reads the alert flags: whether a result since the last call was above the high limit or
1014/// below the low limit, including results the driver's own reads saw.
1015///
1016/// # Arguments
1017///
1018/// * `sensor` - the driver.
1019/// * `out_alerts` - receives the flags.
1020///
1021/// # Returns
1022///
1023/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null argument, or
1024/// [`PamojaStatus::Io`] when the bus fails.
1025///
1026/// # Safety
1027///
1028/// `sensor` must be a live handle or null, and `out_alerts` a writable pointer or null.
1029#[no_mangle]
1030pub unsafe extern "C" fn pamoja_tmp117_alerts(
1031 sensor: *mut PamojaTmp117,
1032 out_alerts: *mut PamojaTmp117Alerts,
1033) -> PamojaStatus {
1034 fill(
1035 sensor,
1036 out_alerts,
1037 "out_alerts",
1038 |held| held.sensor.alerts(),
1039 |alerts| PamojaTmp117Alerts {
1040 high: u8::from(alerts.high),
1041 low: u8::from(alerts.low),
1042 },
1043 )
1044}
1045
1046/// Reports the silicon revision a TMP117 driver read at initialization.
1047///
1048/// # Arguments
1049///
1050/// * `sensor` - the driver.
1051/// * `out_revision` - receives the revision.
1052///
1053/// # Returns
1054///
1055/// `true` with the revision in `out_revision`; `false` before the part has been initialized,
1056/// or for a null argument.
1057///
1058/// # Safety
1059///
1060/// `sensor` must be a live handle or null, and `out_revision` a writable pointer or null.
1061#[no_mangle]
1062pub unsafe extern "C" fn pamoja_tmp117_silicon_revision(
1063 sensor: *const PamojaTmp117,
1064 out_revision: *mut u8,
1065) -> bool {
1066 let Some(held) = sensor.as_ref() else {
1067 return false;
1068 };
1069 store(out_revision, held.sensor.revision())
1070}
1071
1072/// Releases a driver and its share of the bus. A null pointer is ignored.
1073///
1074/// # Safety
1075///
1076/// `sensor` must be a handle from [`pamoja_tmp117_new`] that has not been freed, or null.
1077#[no_mangle]
1078pub unsafe extern "C" fn pamoja_tmp117_free(sensor: *mut PamojaTmp117) {
1079 release(sensor);
1080}
1081
1082/// Creates a simulated TMP117 reading [`PAMOJA_TMP117_SIM_CELSIUS`].
1083///
1084/// # Arguments
1085///
1086/// * `address` - the address it answers to.
1087///
1088/// # Returns
1089///
1090/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1091#[no_mangle]
1092pub extern "C" fn pamoja_tmp117_sim_part(address: u8) -> *mut PamojaI2cPart {
1093 PamojaI2cPart::into_raw(tmp117::sim::part(address))
1094}
1095
1096/// Creates a simulated TMP117 that reads what it is asked to. Its configuration register keeps
1097/// the flags the part sets for itself, with the data-ready flag set.
1098///
1099/// # Arguments
1100///
1101/// * `address` - the address it answers to.
1102/// * `celsius` - the temperature it reports, which lands on the nearest 7.8125 millidegrees.
1103///
1104/// # Returns
1105///
1106/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1107#[no_mangle]
1108pub extern "C" fn pamoja_tmp117_sim_reporting(address: u8, celsius: f32) -> *mut PamojaI2cPart {
1109 PamojaI2cPart::into_raw(tmp117::sim::reporting(address, celsius))
1110}
1111
1112/// Returns the settings an OPT3001 driver starts with, the part's own reset settings: the
1113/// 800 ms integration time and the range chosen automatically.
1114///
1115/// # Returns
1116///
1117/// The settings.
1118#[no_mangle]
1119pub extern "C" fn pamoja_opt3001_settings_default() -> PamojaOpt3001Settings {
1120 PamojaOpt3001Settings {
1121 long_conversion: 1,
1122 range_number: opt3001::RANGE_AUTOMATIC,
1123 }
1124}
1125
1126/// Creates an OPT3001 driver on a bus. Nothing is sent until [`pamoja_opt3001_init`] or the
1127/// first [`pamoja_opt3001_measure`].
1128///
1129/// # Arguments
1130///
1131/// * `bus` - the bus the part is on; the driver holds its own share.
1132/// * `address` - one of the `PAMOJA_OPT3001_I2C_ADDRESS_` constants, by where ADDR is tied.
1133/// * `settings` - the integration time and the range.
1134/// * `out_sensor` - receives the driver.
1135///
1136/// # Returns
1137///
1138/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
1139/// for a null argument.
1140///
1141/// # Safety
1142///
1143/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
1144#[no_mangle]
1145pub unsafe extern "C" fn pamoja_opt3001_new(
1146 bus: *const PamojaI2cBus,
1147 address: u8,
1148 settings: PamojaOpt3001Settings,
1149 out_sensor: *mut *mut PamojaOpt3001,
1150) -> PamojaStatus {
1151 build(bus, out_sensor, |bus, delay| PamojaOpt3001 {
1152 sensor: opt3001::Opt3001::new(bus, address, delay)
1153 .with_conversion_time(conversion_time(settings.long_conversion != 0))
1154 .with_range(settings.range_number),
1155 })
1156}
1157
1158/// Checks the part is an OPT3001 and writes the settings with the part in shutdown.
1159///
1160/// # Arguments
1161///
1162/// * `sensor` - the driver.
1163///
1164/// # Returns
1165///
1166/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
1167/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when either id register
1168/// is not an OPT3001's.
1169///
1170/// # Safety
1171///
1172/// `sensor` must be a live handle or null.
1173#[no_mangle]
1174pub unsafe extern "C" fn pamoja_opt3001_init(sensor: *mut PamojaOpt3001) -> PamojaStatus {
1175 run(sensor, |held| held.sensor.init())
1176}
1177
1178/// Runs one conversion and reads the illuminance, initializing the part first if
1179/// [`pamoja_opt3001_init`] has not run.
1180///
1181/// # Arguments
1182///
1183/// * `sensor` - the driver.
1184/// * `out_reading` - receives the illuminance.
1185///
1186/// # Returns
1187///
1188/// As [`pamoja_opt3001_init`], with [`PamojaStatus::Io`] also when the conversion never
1189/// finishes.
1190///
1191/// # Safety
1192///
1193/// `sensor` must be a live handle or null, and `out_reading` a writable pointer or null.
1194#[no_mangle]
1195pub unsafe extern "C" fn pamoja_opt3001_measure(
1196 sensor: *mut PamojaOpt3001,
1197 out_reading: *mut PamojaOpt3001Reading,
1198) -> PamojaStatus {
1199 fill(
1200 sensor,
1201 out_reading,
1202 "out_reading",
1203 |held| held.sensor.measure(),
1204 |reading| PamojaOpt3001Reading {
1205 raw: reading.raw(),
1206 milli_lux: reading.milli_lux(),
1207 lux: reading.lux(),
1208 },
1209 )
1210}
1211
1212/// Writes the low and high limits the part's interrupt pin compares each result against.
1213///
1214/// # Arguments
1215///
1216/// * `sensor` - the driver.
1217/// * `low_milli_lux` - the low limit, in millilux.
1218/// * `high_milli_lux` - the high limit, in millilux.
1219///
1220/// # Returns
1221///
1222/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null driver, or
1223/// [`PamojaStatus::Io`] when the bus fails.
1224///
1225/// # Safety
1226///
1227/// `sensor` must be a live handle or null.
1228#[no_mangle]
1229pub unsafe extern "C" fn pamoja_opt3001_set_limits(
1230 sensor: *mut PamojaOpt3001,
1231 low_milli_lux: u32,
1232 high_milli_lux: u32,
1233) -> PamojaStatus {
1234 run(sensor, |held| {
1235 held.sensor.set_limits(low_milli_lux, high_milli_lux)
1236 })
1237}
1238
1239/// Copies the configuration an OPT3001 driver writes, with the part in shutdown.
1240///
1241/// # Arguments
1242///
1243/// * `sensor` - the driver.
1244/// * `out_config` - receives the configuration.
1245///
1246/// # Returns
1247///
1248/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null argument.
1249///
1250/// # Safety
1251///
1252/// `sensor` must be a live handle or null, and `out_config` a writable pointer or null.
1253#[no_mangle]
1254pub unsafe extern "C" fn pamoja_opt3001_configuration(
1255 sensor: *const PamojaOpt3001,
1256 out_config: *mut PamojaOpt3001Config,
1257) -> PamojaStatus {
1258 look(sensor, out_config, "out_config", |held| {
1259 held.sensor.configuration().into()
1260 })
1261}
1262
1263/// Releases a driver and its share of the bus. A null pointer is ignored.
1264///
1265/// # Safety
1266///
1267/// `sensor` must be a handle from [`pamoja_opt3001_new`] that has not been freed, or null.
1268#[no_mangle]
1269pub unsafe extern "C" fn pamoja_opt3001_free(sensor: *mut PamojaOpt3001) {
1270 release(sensor);
1271}
1272
1273/// Creates a simulated OPT3001 reading [`PAMOJA_OPT3001_SIM_LUX`].
1274///
1275/// # Arguments
1276///
1277/// * `address` - the address it answers to.
1278///
1279/// # Returns
1280///
1281/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1282#[no_mangle]
1283pub extern "C" fn pamoja_opt3001_sim_part(address: u8) -> *mut PamojaI2cPart {
1284 PamojaI2cPart::into_raw(opt3001::sim::part(address))
1285}
1286
1287/// Creates a simulated OPT3001 that reads what it is asked to. Its configuration register
1288/// keeps the flags the part sets for itself, with the conversion-ready flag set.
1289///
1290/// # Arguments
1291///
1292/// * `address` - the address it answers to.
1293/// * `lux` - the illuminance it reports, which lands on the nearest step its exponent and
1294/// mantissa represent.
1295///
1296/// # Returns
1297///
1298/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1299#[no_mangle]
1300pub extern "C" fn pamoja_opt3001_sim_reporting(address: u8, lux: f32) -> *mut PamojaI2cPart {
1301 PamojaI2cPart::into_raw(opt3001::sim::reporting(address, lux))
1302}
1303
1304/// Returns the settings an HDC1080 driver starts with: both channels at 14 bits.
1305///
1306/// # Returns
1307///
1308/// The settings.
1309#[no_mangle]
1310pub extern "C" fn pamoja_hdc1080_settings_default() -> PamojaHdc1080Settings {
1311 PamojaHdc1080Settings {
1312 temperature_resolution_bits: 14,
1313 humidity_resolution_bits: 14,
1314 }
1315}
1316
1317/// Creates an HDC1080 driver on a bus; the part has one address. Nothing is sent until
1318/// [`pamoja_hdc1080_init`] or the first [`pamoja_hdc1080_measure`].
1319///
1320/// # Arguments
1321///
1322/// * `bus` - the bus the part is on; the driver holds its own share.
1323/// * `settings` - the resolution of each channel.
1324/// * `out_sensor` - receives the driver.
1325///
1326/// # Returns
1327///
1328/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
1329/// for a null argument or a resolution the part does not have.
1330///
1331/// # Safety
1332///
1333/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
1334#[no_mangle]
1335pub unsafe extern "C" fn pamoja_hdc1080_new(
1336 bus: *const PamojaI2cBus,
1337 settings: PamojaHdc1080Settings,
1338 out_sensor: *mut *mut PamojaHdc1080,
1339) -> PamojaStatus {
1340 let Some(temperature) = temperature_resolution(settings.temperature_resolution_bits) else {
1341 return clear_then(out_sensor, crate::sensors::bad_temperature_resolution());
1342 };
1343 let Some(humidity) = humidity_resolution(settings.humidity_resolution_bits) else {
1344 return clear_then(out_sensor, crate::sensors::bad_humidity_resolution());
1345 };
1346 build(bus, out_sensor, |bus, delay| PamojaHdc1080 {
1347 sensor: hdc1080::Hdc1080::new(bus, delay).with_resolutions(temperature, humidity),
1348 })
1349}
1350
1351/// Checks the part is an HDC1080 and writes the configuration.
1352///
1353/// # Arguments
1354///
1355/// * `sensor` - the driver.
1356///
1357/// # Returns
1358///
1359/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
1360/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when either id register
1361/// is not an HDC1080's.
1362///
1363/// # Safety
1364///
1365/// `sensor` must be a live handle or null.
1366#[no_mangle]
1367pub unsafe extern "C" fn pamoja_hdc1080_init(sensor: *mut PamojaHdc1080) -> PamojaStatus {
1368 run(sensor, |held| held.sensor.init())
1369}
1370
1371/// Triggers one acquisition of both channels and reads them, initializing the part first if
1372/// [`pamoja_hdc1080_init`] has not run.
1373///
1374/// # Arguments
1375///
1376/// * `sensor` - the driver.
1377/// * `out_measurement` - receives the temperature and humidity.
1378///
1379/// # Returns
1380///
1381/// As [`pamoja_hdc1080_init`]. A part that does not acknowledge the read before its results
1382/// are ready is [`PamojaStatus::Io`].
1383///
1384/// # Safety
1385///
1386/// `sensor` must be a live handle or null, and `out_measurement` a writable pointer or null.
1387#[no_mangle]
1388pub unsafe extern "C" fn pamoja_hdc1080_measure(
1389 sensor: *mut PamojaHdc1080,
1390 out_measurement: *mut PamojaHdc1080Measurement,
1391) -> PamojaStatus {
1392 fill(
1393 sensor,
1394 out_measurement,
1395 "out_measurement",
1396 |held| held.sensor.measure(),
1397 PamojaHdc1080Measurement::from,
1398 )
1399}
1400
1401/// Switches the on-die heater, which runs only during acquisitions, on or off.
1402///
1403/// # Arguments
1404///
1405/// * `sensor` - the driver.
1406/// * `on` - whether the heater runs.
1407///
1408/// # Returns
1409///
1410/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null driver, or
1411/// [`PamojaStatus::Io`] when the bus fails.
1412///
1413/// # Safety
1414///
1415/// `sensor` must be a live handle or null.
1416#[no_mangle]
1417pub unsafe extern "C" fn pamoja_hdc1080_heater(
1418 sensor: *mut PamojaHdc1080,
1419 on: bool,
1420) -> PamojaStatus {
1421 run(sensor, |held| held.sensor.heater(on))
1422}
1423
1424/// Copies the configuration an HDC1080 driver writes.
1425///
1426/// # Arguments
1427///
1428/// * `sensor` - the driver.
1429/// * `out_config` - receives the configuration.
1430///
1431/// # Returns
1432///
1433/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null argument.
1434///
1435/// # Safety
1436///
1437/// `sensor` must be a live handle or null, and `out_config` a writable pointer or null.
1438#[no_mangle]
1439pub unsafe extern "C" fn pamoja_hdc1080_configuration(
1440 sensor: *const PamojaHdc1080,
1441 out_config: *mut PamojaHdc1080Config,
1442) -> PamojaStatus {
1443 look(sensor, out_config, "out_config", |held| {
1444 held.sensor.configuration().into()
1445 })
1446}
1447
1448/// Releases a driver and its share of the bus. A null pointer is ignored.
1449///
1450/// # Safety
1451///
1452/// `sensor` must be a handle from [`pamoja_hdc1080_new`] that has not been freed, or null.
1453#[no_mangle]
1454pub unsafe extern "C" fn pamoja_hdc1080_free(sensor: *mut PamojaHdc1080) {
1455 release(sensor);
1456}
1457
1458/// Creates a simulated HDC1080 reading [`PAMOJA_HDC1080_SIM_CELSIUS`] and
1459/// [`PAMOJA_HDC1080_SIM_RELATIVE_HUMIDITY`].
1460///
1461/// # Returns
1462///
1463/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1464#[no_mangle]
1465pub extern "C" fn pamoja_hdc1080_sim_part() -> *mut PamojaI2cPart {
1466 PamojaI2cPart::into_raw(hdc1080::sim::part())
1467}
1468
1469/// Creates a simulated HDC1080 that reads what it is asked to.
1470///
1471/// # Arguments
1472///
1473/// * `celsius` - the temperature it reports.
1474/// * `relative_humidity` - the humidity it reports, as a percentage.
1475///
1476/// # Returns
1477///
1478/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its readings
1479/// land within three thousandths of a degree and two thousandths of a percent.
1480#[no_mangle]
1481pub extern "C" fn pamoja_hdc1080_sim_reporting(
1482 celsius: f32,
1483 relative_humidity: f32,
1484) -> *mut PamojaI2cPart {
1485 PamojaI2cPart::into_raw(hdc1080::sim::reporting(celsius, relative_humidity))
1486}
1487
1488/// Returns the settings an INA219 driver starts with: a 100 milliohm shunt sized for 3.2 A,
1489/// the common breakout, the finest current step for it, and the power-on register settings.
1490///
1491/// # Returns
1492///
1493/// The settings.
1494#[no_mangle]
1495pub extern "C" fn pamoja_ina219_settings_default() -> PamojaIna219Settings {
1496 PamojaIna219Settings {
1497 shunt_milliohms: 100,
1498 max_microamps: 3_200_000,
1499 current_lsb_microamps: 0,
1500 config: ina219::Configuration::default().into(),
1501 }
1502}
1503
1504/// Creates an INA219 driver on a bus. Nothing is sent until [`pamoja_ina219_init`] or the first
1505/// [`pamoja_ina219_measure`].
1506///
1507/// # Arguments
1508///
1509/// * `bus` - the bus the part is on; the driver holds its own share.
1510/// * `address` - [`crate::sensors::PAMOJA_INA219_BASE_ADDRESS`] plus what A1 and A0 add.
1511/// * `settings` - the shunt, the current it is sized for, and the register settings.
1512/// * `out_sensor` - receives the driver.
1513///
1514/// # Returns
1515///
1516/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
1517/// for a null argument.
1518///
1519/// # Safety
1520///
1521/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
1522#[no_mangle]
1523pub unsafe extern "C" fn pamoja_ina219_new(
1524 bus: *const PamojaI2cBus,
1525 address: u8,
1526 settings: PamojaIna219Settings,
1527 out_sensor: *mut *mut PamojaIna219,
1528) -> PamojaStatus {
1529 build(bus, out_sensor, |bus, delay| {
1530 let mut sensor = ina219::Ina219::new(bus, address, delay)
1531 .with_shunt(settings.shunt_milliohms, settings.max_microamps)
1532 .with_configuration(settings.config.into());
1533 if settings.current_lsb_microamps != 0 {
1534 sensor = sensor.with_current_lsb(settings.current_lsb_microamps);
1535 }
1536 PamojaIna219 { sensor }
1537 })
1538}
1539
1540/// Resets the part, writes the configuration and the calibration, and reads the calibration
1541/// back, which is the identity check a part with no id register allows.
1542///
1543/// # Arguments
1544///
1545/// * `sensor` - the driver.
1546///
1547/// # Returns
1548///
1549/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
1550/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when the calibration
1551/// register does not hold what was written.
1552///
1553/// # Safety
1554///
1555/// `sensor` must be a live handle or null.
1556#[no_mangle]
1557pub unsafe extern "C" fn pamoja_ina219_init(sensor: *mut PamojaIna219) -> PamojaStatus {
1558 run(sensor, |held| held.sensor.init())
1559}
1560
1561/// Triggers one shunt and bus conversion and reads every result, initializing the part first
1562/// if [`pamoja_ina219_init`] has not run.
1563///
1564/// # Arguments
1565///
1566/// * `sensor` - the driver.
1567/// * `out_reading` - receives the results.
1568///
1569/// # Returns
1570///
1571/// As [`pamoja_ina219_init`], with [`PamojaStatus::Io`] also when the conversion-ready flag
1572/// never sets.
1573///
1574/// # Safety
1575///
1576/// `sensor` must be a live handle or null, and `out_reading` a writable pointer or null.
1577#[no_mangle]
1578pub unsafe extern "C" fn pamoja_ina219_measure(
1579 sensor: *mut PamojaIna219,
1580 out_reading: *mut PamojaIna219Reading,
1581) -> PamojaStatus {
1582 fill(
1583 sensor,
1584 out_reading,
1585 "out_reading",
1586 |held| held.sensor.measure(),
1587 |reading| PamojaIna219Reading {
1588 shunt: reading.shunt,
1589 bus: reading.bus,
1590 current: reading.current,
1591 power: reading.power,
1592 current_lsb_microamps: reading.current_lsb_microamps,
1593 shunt_microvolts: reading.shunt_microvolts(),
1594 bus_millivolts: reading.bus_millivolts(),
1595 current_microamps: reading.current_microamps(),
1596 power_microwatts: reading.power_microwatts(),
1597 math_overflow: u8::from(reading.math_overflow()),
1598 },
1599 )
1600}
1601
1602/// Returns the current step an INA219 driver programs.
1603///
1604/// # Arguments
1605///
1606/// * `sensor` - the driver.
1607///
1608/// # Returns
1609///
1610/// Microamps per count of the current register, or 0 for a null driver.
1611///
1612/// # Safety
1613///
1614/// `sensor` must be a live handle or null.
1615#[no_mangle]
1616pub unsafe extern "C" fn pamoja_ina219_current_lsb(sensor: *const PamojaIna219) -> u32 {
1617 sensor
1618 .as_ref()
1619 .map_or(0, |held| held.sensor.current_lsb_microamps())
1620}
1621
1622/// Returns the calibration word an INA219 driver programs.
1623///
1624/// # Arguments
1625///
1626/// * `sensor` - the driver.
1627///
1628/// # Returns
1629///
1630/// The word, from the shunt and the current step by the datasheet's equation, or 0 for a
1631/// null driver.
1632///
1633/// # Safety
1634///
1635/// `sensor` must be a live handle or null.
1636#[no_mangle]
1637pub unsafe extern "C" fn pamoja_ina219_calibration_word(sensor: *const PamojaIna219) -> u16 {
1638 sensor.as_ref().map_or(0, |held| held.sensor.calibration())
1639}
1640
1641/// Releases a driver and its share of the bus. A null pointer is ignored.
1642///
1643/// # Safety
1644///
1645/// `sensor` must be a handle from [`pamoja_ina219_new`] that has not been freed, or null.
1646#[no_mangle]
1647pub unsafe extern "C" fn pamoja_ina219_free(sensor: *mut PamojaIna219) {
1648 release(sensor);
1649}
1650
1651/// Creates a simulated INA219 carrying [`PAMOJA_INA219_SIM_MICROAMPS`] at
1652/// [`PAMOJA_INA219_SIM_BUS_MILLIVOLTS`] through the shunt a driver starts with.
1653///
1654/// # Arguments
1655///
1656/// * `address` - the address it answers to.
1657///
1658/// # Returns
1659///
1660/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1661#[no_mangle]
1662pub extern "C" fn pamoja_ina219_sim_part(address: u8) -> *mut PamojaI2cPart {
1663 PamojaI2cPart::into_raw(ina219::sim::part(address))
1664}
1665
1666/// Creates a simulated INA219 that reads what it is asked to, calibrated for the same shunt and
1667/// largest current a driver is given.
1668///
1669/// # Arguments
1670///
1671/// * `address` - the address it answers to.
1672/// * `shunt_milliohms` - the shunt, as in the driver's settings.
1673/// * `max_microamps` - the largest current, as in the driver's settings.
1674/// * `bus_millivolts` - the bus voltage it reports.
1675/// * `microamps` - the current it reports; negative flows the other way through the shunt.
1676///
1677/// # Returns
1678///
1679/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its readings
1680/// land on the steps its registers count in: 4 mV of bus, 10 uV of shunt, and the
1681/// calibration's current step.
1682#[no_mangle]
1683pub extern "C" fn pamoja_ina219_sim_reporting(
1684 address: u8,
1685 shunt_milliohms: u32,
1686 max_microamps: u32,
1687 bus_millivolts: u32,
1688 microamps: i32,
1689) -> *mut PamojaI2cPart {
1690 PamojaI2cPart::into_raw(ina219::sim::reporting(
1691 address,
1692 shunt_milliohms,
1693 max_microamps,
1694 bus_millivolts,
1695 microamps,
1696 ))
1697}
1698
1699/// Returns the settings an INA226 driver starts with: a 100 milliohm shunt sized for 3.2 A,
1700/// the finest current step for it, and the power-on register settings.
1701///
1702/// # Returns
1703///
1704/// The settings.
1705#[no_mangle]
1706pub extern "C" fn pamoja_ina226_settings_default() -> PamojaIna226Settings {
1707 PamojaIna226Settings {
1708 shunt_milliohms: 100,
1709 max_microamps: 3_200_000,
1710 current_lsb_microamps: 0,
1711 config: ina226::Configuration::RESET.into(),
1712 }
1713}
1714
1715/// Creates an INA226 driver on a bus. Nothing is sent until [`pamoja_ina226_init`] or the first
1716/// [`pamoja_ina226_measure`].
1717///
1718/// # Arguments
1719///
1720/// * `bus` - the bus the part is on; the driver holds its own share.
1721/// * `address` - the address A1 and A0 select, which
1722/// [`crate::sensors::pamoja_ina226_address`] works out.
1723/// * `settings` - the shunt, the current it is sized for, and the register settings.
1724/// * `out_sensor` - receives the driver.
1725///
1726/// # Returns
1727///
1728/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
1729/// for a null argument.
1730///
1731/// # Safety
1732///
1733/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
1734#[no_mangle]
1735pub unsafe extern "C" fn pamoja_ina226_new(
1736 bus: *const PamojaI2cBus,
1737 address: u8,
1738 settings: PamojaIna226Settings,
1739 out_sensor: *mut *mut PamojaIna226,
1740) -> PamojaStatus {
1741 build(bus, out_sensor, |bus, delay| {
1742 let mut sensor = ina226::Ina226::new(bus, address, delay)
1743 .with_shunt(settings.shunt_milliohms, settings.max_microamps)
1744 .with_configuration(settings.config.into());
1745 if settings.current_lsb_microamps != 0 {
1746 sensor = sensor.with_current_lsb(settings.current_lsb_microamps);
1747 }
1748 PamojaIna226 { sensor }
1749 })
1750}
1751
1752/// Resets the part, checks it is an INA226, writes the configuration and the calibration, and
1753/// reads the calibration back.
1754///
1755/// # Arguments
1756///
1757/// * `sensor` - the driver.
1758///
1759/// # Returns
1760///
1761/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
1762/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when the id registers
1763/// are not an INA226's or the calibration does not read back.
1764///
1765/// # Safety
1766///
1767/// `sensor` must be a live handle or null.
1768#[no_mangle]
1769pub unsafe extern "C" fn pamoja_ina226_init(sensor: *mut PamojaIna226) -> PamojaStatus {
1770 run(sensor, |held| held.sensor.init())
1771}
1772
1773/// Triggers one shunt and bus conversion and reads every result, initializing the part first
1774/// if [`pamoja_ina226_init`] has not run.
1775///
1776/// # Arguments
1777///
1778/// * `sensor` - the driver.
1779/// * `out_reading` - receives the results.
1780///
1781/// # Returns
1782///
1783/// As [`pamoja_ina226_init`], with [`PamojaStatus::Io`] also when the conversion-ready flag
1784/// never sets.
1785///
1786/// # Safety
1787///
1788/// `sensor` must be a live handle or null, and `out_reading` a writable pointer or null.
1789#[no_mangle]
1790pub unsafe extern "C" fn pamoja_ina226_measure(
1791 sensor: *mut PamojaIna226,
1792 out_reading: *mut PamojaIna226Reading,
1793) -> PamojaStatus {
1794 fill(
1795 sensor,
1796 out_reading,
1797 "out_reading",
1798 |held| held.sensor.measure(),
1799 |reading| PamojaIna226Reading {
1800 shunt: reading.shunt,
1801 bus: reading.bus,
1802 current: reading.current,
1803 power: reading.power,
1804 current_lsb_microamps: reading.current_lsb_microamps,
1805 shunt_nanovolts: reading.shunt_nanovolts(),
1806 shunt_millivolts: reading.shunt_millivolts(),
1807 bus_microvolts: reading.bus_microvolts(),
1808 bus_volts: reading.bus_volts(),
1809 current_microamps: reading.current_microamps(),
1810 current_amps: reading.current_amps(),
1811 power_microwatts: reading.power_microwatts(),
1812 power_watts: reading.power_watts(),
1813 math_overflow: u8::from(reading.math_overflow),
1814 },
1815 )
1816}
1817
1818/// Programs the alert pin: which limit it watches, and the limit.
1819///
1820/// # Arguments
1821///
1822/// * `sensor` - the driver.
1823/// * `mask` - the mask/enable settings, one alert function at a time.
1824/// * `limit` - the alert-limit register, in the units of the register the function watches.
1825///
1826/// # Returns
1827///
1828/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null driver, or
1829/// [`PamojaStatus::Io`] when the bus fails.
1830///
1831/// # Safety
1832///
1833/// `sensor` must be a live handle or null.
1834#[no_mangle]
1835pub unsafe extern "C" fn pamoja_ina226_set_alert(
1836 sensor: *mut PamojaIna226,
1837 mask: PamojaIna226MaskEnable,
1838 limit: u16,
1839) -> PamojaStatus {
1840 run(sensor, |held| held.sensor.set_alert(mask.into(), limit))
1841}
1842
1843/// Returns the current step an INA226 driver programs.
1844///
1845/// # Arguments
1846///
1847/// * `sensor` - the driver.
1848///
1849/// # Returns
1850///
1851/// Microamps per count of the current register, or 0 for a null driver.
1852///
1853/// # Safety
1854///
1855/// `sensor` must be a live handle or null.
1856#[no_mangle]
1857pub unsafe extern "C" fn pamoja_ina226_current_lsb(sensor: *const PamojaIna226) -> u32 {
1858 sensor
1859 .as_ref()
1860 .map_or(0, |held| held.sensor.current_lsb_microamps())
1861}
1862
1863/// Returns the calibration word an INA226 driver programs.
1864///
1865/// # Arguments
1866///
1867/// * `sensor` - the driver.
1868///
1869/// # Returns
1870///
1871/// The word, or 0 for a null driver.
1872///
1873/// # Safety
1874///
1875/// `sensor` must be a live handle or null.
1876#[no_mangle]
1877pub unsafe extern "C" fn pamoja_ina226_calibration_word(sensor: *const PamojaIna226) -> u16 {
1878 sensor.as_ref().map_or(0, |held| held.sensor.calibration())
1879}
1880
1881/// Reports the die id an INA226 driver read at initialization.
1882///
1883/// # Arguments
1884///
1885/// * `sensor` - the driver.
1886/// * `out_die_id` - receives the device and revision.
1887///
1888/// # Returns
1889///
1890/// `true` with the id in `out_die_id`; `false` before the part has been initialized, or for a
1891/// null argument.
1892///
1893/// # Safety
1894///
1895/// `sensor` must be a live handle or null, and `out_die_id` a writable pointer or null.
1896#[no_mangle]
1897pub unsafe extern "C" fn pamoja_ina226_identity(
1898 sensor: *const PamojaIna226,
1899 out_die_id: *mut PamojaIna226DieId,
1900) -> bool {
1901 let Some(held) = sensor.as_ref() else {
1902 return false;
1903 };
1904 store(out_die_id, held.sensor.die_id().map(Into::into))
1905}
1906
1907/// Releases a driver and its share of the bus. A null pointer is ignored.
1908///
1909/// # Safety
1910///
1911/// `sensor` must be a handle from [`pamoja_ina226_new`] that has not been freed, or null.
1912#[no_mangle]
1913pub unsafe extern "C" fn pamoja_ina226_free(sensor: *mut PamojaIna226) {
1914 release(sensor);
1915}
1916
1917/// Creates a simulated INA226 carrying [`PAMOJA_INA226_SIM_MICROAMPS`] at
1918/// [`PAMOJA_INA226_SIM_BUS_MICROVOLTS`] through the shunt a driver starts with.
1919///
1920/// # Arguments
1921///
1922/// * `address` - the address it answers to.
1923///
1924/// # Returns
1925///
1926/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
1927#[no_mangle]
1928pub extern "C" fn pamoja_ina226_sim_part(address: u8) -> *mut PamojaI2cPart {
1929 PamojaI2cPart::into_raw(ina226::sim::part(address))
1930}
1931
1932/// Creates a simulated INA226 that reads what it is asked to, calibrated for the same shunt and
1933/// largest current a driver is given.
1934///
1935/// # Arguments
1936///
1937/// * `address` - the address it answers to.
1938/// * `shunt_milliohms` - the shunt, as in the driver's settings.
1939/// * `max_microamps` - the largest current, as in the driver's settings.
1940/// * `bus_microvolts` - the bus voltage it reports.
1941/// * `microamps` - the current it reports; negative flows the other way through the shunt.
1942///
1943/// # Returns
1944///
1945/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its readings
1946/// land on the steps its registers count in: 1.25 mV of bus, 2.5 uV of shunt, and the
1947/// calibration's current step.
1948#[no_mangle]
1949pub extern "C" fn pamoja_ina226_sim_reporting(
1950 address: u8,
1951 shunt_milliohms: u32,
1952 max_microamps: u32,
1953 bus_microvolts: u32,
1954 microamps: i32,
1955) -> *mut PamojaI2cPart {
1956 PamojaI2cPart::into_raw(ina226::sim::reporting(
1957 address,
1958 shunt_milliohms,
1959 max_microamps,
1960 bus_microvolts,
1961 microamps,
1962 ))
1963}
1964
1965/// Returns the settings an ADS1115 driver starts with, the part's own reset settings: AIN0
1966/// against AIN1, the 2.048 V range, and 128 samples per second.
1967///
1968/// # Returns
1969///
1970/// The settings.
1971#[no_mangle]
1972pub extern "C" fn pamoja_ads1115_settings_default() -> PamojaAds1115Settings {
1973 let config = ads1115::Config::default();
1974 PamojaAds1115Settings {
1975 mux: config.mux.code(),
1976 pga: config.pga.code(),
1977 data_rate: config.data_rate.code(),
1978 }
1979}
1980
1981/// Creates an ADS1115 driver on a bus. Nothing is sent until [`pamoja_ads1115_init`] or the
1982/// first [`pamoja_ads1115_sample`].
1983///
1984/// # Arguments
1985///
1986/// * `bus` - the bus the part is on; the driver holds its own share.
1987/// * `address` - one of the `PAMOJA_ADS1115_ADDRESS_` constants, by where ADDR is tied.
1988/// * `settings` - the input, the range, and the data rate.
1989/// * `out_sensor` - receives the driver.
1990///
1991/// # Returns
1992///
1993/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
1994/// for a null argument.
1995///
1996/// # Safety
1997///
1998/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
1999#[no_mangle]
2000pub unsafe extern "C" fn pamoja_ads1115_new(
2001 bus: *const PamojaI2cBus,
2002 address: u8,
2003 settings: PamojaAds1115Settings,
2004 out_sensor: *mut *mut PamojaAds1115,
2005) -> PamojaStatus {
2006 build(bus, out_sensor, |bus, delay| PamojaAds1115 {
2007 sensor: ads1115::Ads1115::new(bus, address, delay)
2008 .with_input(ads1115::Mux::from_code(settings.mux))
2009 .with_gain(ads1115::Pga::from_code(settings.pga))
2010 .with_data_rate(ads1115::DataRate::from_code(settings.data_rate)),
2011 })
2012}
2013
2014/// Writes the input, range, and data rate, and reads the configuration back, which is the
2015/// identity check a part with no id register allows.
2016///
2017/// # Arguments
2018///
2019/// * `sensor` - the driver.
2020///
2021/// # Returns
2022///
2023/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
2024/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when the configuration
2025/// reads back differently.
2026///
2027/// # Safety
2028///
2029/// `sensor` must be a live handle or null.
2030#[no_mangle]
2031pub unsafe extern "C" fn pamoja_ads1115_init(sensor: *mut PamojaAds1115) -> PamojaStatus {
2032 run(sensor, |held| held.sensor.init())
2033}
2034
2035/// Runs one conversion of the configured input, initializing the part first if
2036/// [`pamoja_ads1115_init`] has not run.
2037///
2038/// # Arguments
2039///
2040/// * `sensor` - the driver.
2041/// * `out_sample` - receives the conversion, with the range it ran at.
2042///
2043/// # Returns
2044///
2045/// As [`pamoja_ads1115_init`], with [`PamojaStatus::Io`] also when the part never reports the
2046/// conversion done.
2047///
2048/// # Safety
2049///
2050/// `sensor` must be a live handle or null, and `out_sample` a writable pointer or null.
2051#[no_mangle]
2052pub unsafe extern "C" fn pamoja_ads1115_sample(
2053 sensor: *mut PamojaAds1115,
2054 out_sample: *mut PamojaAds1115Sample,
2055) -> PamojaStatus {
2056 fill(
2057 sensor,
2058 out_sample,
2059 "out_sample",
2060 |held| held.sensor.sample(),
2061 sample_of,
2062 )
2063}
2064
2065/// Converts another input once, leaving the configured input as it was.
2066///
2067/// # Arguments
2068///
2069/// * `sensor` - the driver.
2070/// * `mux` - the input multiplexer code for this one conversion.
2071/// * `out_sample` - receives the conversion.
2072///
2073/// # Returns
2074///
2075/// As [`pamoja_ads1115_sample`].
2076///
2077/// # Safety
2078///
2079/// `sensor` must be a live handle or null, and `out_sample` a writable pointer or null.
2080#[no_mangle]
2081pub unsafe extern "C" fn pamoja_ads1115_sample_input(
2082 sensor: *mut PamojaAds1115,
2083 mux: u8,
2084 out_sample: *mut PamojaAds1115Sample,
2085) -> PamojaStatus {
2086 fill(
2087 sensor,
2088 out_sample,
2089 "out_sample",
2090 |held| held.sensor.sample_input(ads1115::Mux::from_code(mux)),
2091 sample_of,
2092 )
2093}
2094
2095/// Copies the configuration an ADS1115 driver writes.
2096///
2097/// # Arguments
2098///
2099/// * `sensor` - the driver.
2100/// * `out_config` - receives the configuration.
2101///
2102/// # Returns
2103///
2104/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null argument.
2105///
2106/// # Safety
2107///
2108/// `sensor` must be a live handle or null, and `out_config` a writable pointer or null.
2109#[no_mangle]
2110pub unsafe extern "C" fn pamoja_ads1115_config(
2111 sensor: *const PamojaAds1115,
2112 out_config: *mut PamojaAds1115Config,
2113) -> PamojaStatus {
2114 look(sensor, out_config, "out_config", |held| {
2115 held.sensor.config().into()
2116 })
2117}
2118
2119/// Releases a driver and its share of the bus. A null pointer is ignored.
2120///
2121/// # Safety
2122///
2123/// `sensor` must be a handle from [`pamoja_ads1115_new`] that has not been freed, or null.
2124#[no_mangle]
2125pub unsafe extern "C" fn pamoja_ads1115_free(sensor: *mut PamojaAds1115) {
2126 release(sensor);
2127}
2128
2129/// Creates a simulated ADS1115 reading [`PAMOJA_ADS1115_SIM_VOLTS`] at the range a driver
2130/// starts with.
2131///
2132/// # Arguments
2133///
2134/// * `address` - the address it answers to.
2135///
2136/// # Returns
2137///
2138/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
2139#[no_mangle]
2140pub extern "C" fn pamoja_ads1115_sim_part(address: u8) -> *mut PamojaI2cPart {
2141 PamojaI2cPart::into_raw(ads1115::sim::part(address))
2142}
2143
2144/// Creates a simulated ADS1115 that reads what it is asked to at the range a driver converts
2145/// at.
2146///
2147/// # Arguments
2148///
2149/// * `address` - the address it answers to.
2150/// * `pga` - the gain code the driver converts at.
2151/// * `volts` - the voltage it reports, held to the range.
2152///
2153/// # Returns
2154///
2155/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its reading
2156/// lands on the nearest of the 32768 steps either side of zero the range divides into.
2157#[no_mangle]
2158pub extern "C" fn pamoja_ads1115_sim_reporting(
2159 address: u8,
2160 pga: u8,
2161 volts: f32,
2162) -> *mut PamojaI2cPart {
2163 PamojaI2cPart::into_raw(ads1115::sim::reporting(
2164 address,
2165 ads1115::Pga::from_code(pga),
2166 volts,
2167 ))
2168}
2169
2170/// Creates an SHT3x driver on a bus. Nothing is sent until [`pamoja_sht3x_init`] or the first
2171/// [`pamoja_sht3x_measure`].
2172///
2173/// # Arguments
2174///
2175/// * `bus` - the bus the part is on; the driver holds its own share.
2176/// * `address` - `PAMOJA_SHT3X_I2C_ADDRESS_A` with ADDR low, `PAMOJA_SHT3X_I2C_ADDRESS_B` with
2177/// ADDR high.
2178/// * `repeatability` - `0` low, `1` medium, or `2` high, which trades noise against
2179/// measurement time.
2180/// * `out_sensor` - receives the driver.
2181///
2182/// # Returns
2183///
2184/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
2185/// for a null argument or a repeatability code the part does not have.
2186///
2187/// # Safety
2188///
2189/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
2190#[no_mangle]
2191pub unsafe extern "C" fn pamoja_sht3x_new(
2192 bus: *const PamojaI2cBus,
2193 address: u8,
2194 repeatability: u8,
2195 out_sensor: *mut *mut PamojaSht3x,
2196) -> PamojaStatus {
2197 let Some(repeatability) = repeatability_from_code(repeatability) else {
2198 return clear_then(out_sensor, crate::sensors::bad_repeatability());
2199 };
2200 build(bus, out_sensor, |bus, delay| PamojaSht3x {
2201 sensor: sht3x::Sht3x::new(bus, address, delay).with_repeatability(repeatability),
2202 })
2203}
2204
2205/// Soft-resets the part and reads its status; a status word whose CRC checks is what confirms
2206/// an SHT3x answers, as the part has no id register.
2207///
2208/// # Arguments
2209///
2210/// * `sensor` - the driver.
2211///
2212/// # Returns
2213///
2214/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
2215/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when the status word
2216/// fails its CRC.
2217///
2218/// # Safety
2219///
2220/// `sensor` must be a live handle or null.
2221#[no_mangle]
2222pub unsafe extern "C" fn pamoja_sht3x_init(sensor: *mut PamojaSht3x) -> PamojaStatus {
2223 run(sensor, |held| held.sensor.init())
2224}
2225
2226/// Runs one single-shot measurement, initializing the part first if [`pamoja_sht3x_init`] has
2227/// not run.
2228///
2229/// # Arguments
2230///
2231/// * `sensor` - the driver.
2232/// * `out_measurement` - receives the CRC-checked temperature and humidity.
2233///
2234/// # Returns
2235///
2236/// As [`pamoja_sht3x_init`], with [`PamojaStatus::Codec`] also when a data word fails its CRC.
2237///
2238/// # Safety
2239///
2240/// `sensor` must be a live handle or null, and `out_measurement` a writable pointer or null.
2241#[no_mangle]
2242pub unsafe extern "C" fn pamoja_sht3x_measure(
2243 sensor: *mut PamojaSht3x,
2244 out_measurement: *mut PamojaSht3xMeasurement,
2245) -> PamojaStatus {
2246 fill(
2247 sensor,
2248 out_measurement,
2249 "out_measurement",
2250 |held| held.sensor.measure(),
2251 PamojaSht3xMeasurement::from,
2252 )
2253}
2254
2255/// Reads the status register.
2256///
2257/// # Arguments
2258///
2259/// * `sensor` - the driver.
2260/// * `out_status` - receives the status.
2261///
2262/// # Returns
2263///
2264/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null argument,
2265/// [`PamojaStatus::Io`] when the bus fails, or [`PamojaStatus::Codec`] when the word fails its
2266/// CRC.
2267///
2268/// # Safety
2269///
2270/// `sensor` must be a live handle or null, and `out_status` a writable pointer or null.
2271#[no_mangle]
2272pub unsafe extern "C" fn pamoja_sht3x_read_status(
2273 sensor: *mut PamojaSht3x,
2274 out_status: *mut PamojaSht3xStatus,
2275) -> PamojaStatus {
2276 fill(
2277 sensor,
2278 out_status,
2279 "out_status",
2280 |held| held.sensor.read_status(),
2281 PamojaSht3xStatus::from,
2282 )
2283}
2284
2285/// Reports the status an SHT3x driver last read, without reading it again.
2286///
2287/// # Arguments
2288///
2289/// * `sensor` - the driver.
2290/// * `out_status` - receives the status.
2291///
2292/// # Returns
2293///
2294/// `true` with the status in `out_status`; `false` before the status has been read, or for a
2295/// null argument.
2296///
2297/// # Safety
2298///
2299/// `sensor` must be a live handle or null, and `out_status` a writable pointer or null.
2300#[no_mangle]
2301pub unsafe extern "C" fn pamoja_sht3x_last_status(
2302 sensor: *const PamojaSht3x,
2303 out_status: *mut PamojaSht3xStatus,
2304) -> bool {
2305 let Some(held) = sensor.as_ref() else {
2306 return false;
2307 };
2308 store(out_status, held.sensor.status().map(Into::into))
2309}
2310
2311/// Switches the plausibility-check heater on, initializing the part first if needed, since
2312/// the reset in initialization switches it off.
2313///
2314/// # Arguments
2315///
2316/// * `sensor` - the driver.
2317///
2318/// # Returns
2319///
2320/// As [`pamoja_sht3x_init`].
2321///
2322/// # Safety
2323///
2324/// `sensor` must be a live handle or null.
2325#[no_mangle]
2326pub unsafe extern "C" fn pamoja_sht3x_heater_on(sensor: *mut PamojaSht3x) -> PamojaStatus {
2327 run(sensor, |held| held.sensor.heater_on())
2328}
2329
2330/// Switches the heater off, which is its state after any reset.
2331///
2332/// # Arguments
2333///
2334/// * `sensor` - the driver.
2335///
2336/// # Returns
2337///
2338/// As [`pamoja_sht3x_init`].
2339///
2340/// # Safety
2341///
2342/// `sensor` must be a live handle or null.
2343#[no_mangle]
2344pub unsafe extern "C" fn pamoja_sht3x_heater_off(sensor: *mut PamojaSht3x) -> PamojaStatus {
2345 run(sensor, |held| held.sensor.heater_off())
2346}
2347
2348/// Releases a driver and its share of the bus. A null pointer is ignored.
2349///
2350/// # Safety
2351///
2352/// `sensor` must be a handle from [`pamoja_sht3x_new`] that has not been freed, or null.
2353#[no_mangle]
2354pub unsafe extern "C" fn pamoja_sht3x_free(sensor: *mut PamojaSht3x) {
2355 release(sensor);
2356}
2357
2358/// Creates a simulated SHT3x reading [`PAMOJA_SHT3X_SIM_CELSIUS`] and
2359/// [`PAMOJA_SHT3X_SIM_RELATIVE_HUMIDITY`].
2360///
2361/// # Arguments
2362///
2363/// * `address` - the address it answers to.
2364///
2365/// # Returns
2366///
2367/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
2368#[no_mangle]
2369pub extern "C" fn pamoja_sht3x_sim_part(address: u8) -> *mut PamojaI2cPart {
2370 PamojaI2cPart::into_raw(sht3x::sim::part(address))
2371}
2372
2373/// Creates a simulated SHT3x that reads what it is asked to. It answers every single-shot
2374/// command, a periodic fetch, and the status command with the replies a real part gives.
2375///
2376/// # Arguments
2377///
2378/// * `address` - the address it answers to.
2379/// * `celsius` - the temperature it reports.
2380/// * `relative_humidity` - the humidity it reports, as a percentage.
2381///
2382/// # Returns
2383///
2384/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`]. Its readings
2385/// land within three thousandths of a degree and two thousandths of a percent.
2386#[no_mangle]
2387pub extern "C" fn pamoja_sht3x_sim_reporting(
2388 address: u8,
2389 celsius: f32,
2390 relative_humidity: f32,
2391) -> *mut PamojaI2cPart {
2392 PamojaI2cPart::into_raw(sht3x::sim::reporting(address, celsius, relative_humidity))
2393}
2394
2395/// Creates an SCD40 or SCD41 driver on a bus; the part has one address. Nothing is sent until
2396/// [`pamoja_scd4x_init`] or the first [`pamoja_scd4x_measure`].
2397///
2398/// # Arguments
2399///
2400/// * `bus` - the bus the part is on; the driver holds its own share.
2401/// * `out_sensor` - receives the driver.
2402///
2403/// # Returns
2404///
2405/// [`PamojaStatus::Ok`] with the driver in `out_sensor`, or [`PamojaStatus::InvalidArgument`]
2406/// for a null argument.
2407///
2408/// # Safety
2409///
2410/// `bus` must be a live handle or null, and `out_sensor` a writable pointer or null.
2411#[no_mangle]
2412pub unsafe extern "C" fn pamoja_scd4x_new(
2413 bus: *const PamojaI2cBus,
2414 out_sensor: *mut *mut PamojaScd4x,
2415) -> PamojaStatus {
2416 build(bus, out_sensor, |bus, delay| PamojaScd4x {
2417 sensor: scd4x::Scd4x::new(bus, delay),
2418 })
2419}
2420
2421/// Stops any running measurement, reads the serial number, and starts periodic measurement,
2422/// after which the part has a new result every five seconds.
2423///
2424/// # Arguments
2425///
2426/// * `sensor` - the driver.
2427///
2428/// # Returns
2429///
2430/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null driver;
2431/// [`PamojaStatus::Io`] when the bus fails; or [`PamojaStatus::Codec`] when the serial number
2432/// fails its CRC.
2433///
2434/// # Safety
2435///
2436/// `sensor` must be a live handle or null.
2437#[no_mangle]
2438pub unsafe extern "C" fn pamoja_scd4x_init(sensor: *mut PamojaScd4x) -> PamojaStatus {
2439 run(sensor, |held| held.sensor.init())
2440}
2441
2442/// Waits for the next periodic result and reads it, initializing the part first if
2443/// [`pamoja_scd4x_init`] has not run.
2444///
2445/// # Arguments
2446///
2447/// * `sensor` - the driver.
2448/// * `out_measurement` - receives the carbon dioxide, temperature, and humidity.
2449///
2450/// # Returns
2451///
2452/// As [`pamoja_scd4x_init`], with [`PamojaStatus::Io`] also when no result becomes ready and
2453/// [`PamojaStatus::Codec`] when a word fails its CRC.
2454///
2455/// # Safety
2456///
2457/// `sensor` must be a live handle or null, and `out_measurement` a writable pointer or null.
2458#[no_mangle]
2459pub unsafe extern "C" fn pamoja_scd4x_measure(
2460 sensor: *mut PamojaScd4x,
2461 out_measurement: *mut PamojaScd4xMeasurement,
2462) -> PamojaStatus {
2463 fill(
2464 sensor,
2465 out_measurement,
2466 "out_measurement",
2467 |held| held.sensor.measure(),
2468 PamojaScd4xMeasurement::from,
2469 )
2470}
2471
2472/// Runs one on-demand measurement on an SCD41, which takes five seconds. The part must not be
2473/// measuring periodically: call [`pamoja_scd4x_stop`] first, or use this in place of
2474/// [`pamoja_scd4x_init`].
2475///
2476/// # Arguments
2477///
2478/// * `sensor` - the driver.
2479/// * `out_measurement` - receives the measurement.
2480///
2481/// # Returns
2482///
2483/// As [`pamoja_scd4x_measure`].
2484///
2485/// # Safety
2486///
2487/// `sensor` must be a live handle or null, and `out_measurement` a writable pointer or null.
2488#[no_mangle]
2489pub unsafe extern "C" fn pamoja_scd4x_measure_single_shot(
2490 sensor: *mut PamojaScd4x,
2491 out_measurement: *mut PamojaScd4xMeasurement,
2492) -> PamojaStatus {
2493 fill(
2494 sensor,
2495 out_measurement,
2496 "out_measurement",
2497 |held| held.sensor.measure_single_shot(),
2498 PamojaScd4xMeasurement::from,
2499 )
2500}
2501
2502/// Asks the part whether a periodic result is waiting.
2503///
2504/// # Arguments
2505///
2506/// * `sensor` - the driver.
2507/// * `out_ready` - receives `true` when [`pamoja_scd4x_measure`] would read without waiting.
2508///
2509/// # Returns
2510///
2511/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null argument,
2512/// [`PamojaStatus::Io`] when the bus fails, or [`PamojaStatus::Codec`] when the status word
2513/// fails its CRC.
2514///
2515/// # Safety
2516///
2517/// `sensor` must be a live handle or null, and `out_ready` a writable pointer or null.
2518#[no_mangle]
2519pub unsafe extern "C" fn pamoja_scd4x_poll_ready(
2520 sensor: *mut PamojaScd4x,
2521 out_ready: *mut bool,
2522) -> PamojaStatus {
2523 fill(
2524 sensor,
2525 out_ready,
2526 "out_ready",
2527 |held| held.sensor.data_ready(),
2528 |ready| ready,
2529 )
2530}
2531
2532/// Stops periodic measurement, after which the part takes its settings commands.
2533///
2534/// # Arguments
2535///
2536/// * `sensor` - the driver.
2537///
2538/// # Returns
2539///
2540/// [`PamojaStatus::Ok`], [`PamojaStatus::InvalidArgument`] for a null driver, or
2541/// [`PamojaStatus::Io`] when the bus fails.
2542///
2543/// # Safety
2544///
2545/// `sensor` must be a live handle or null.
2546#[no_mangle]
2547pub unsafe extern "C" fn pamoja_scd4x_stop(sensor: *mut PamojaScd4x) -> PamojaStatus {
2548 run(sensor, |held| held.sensor.stop())
2549}
2550
2551/// Starts periodic measurement.
2552///
2553/// # Arguments
2554///
2555/// * `sensor` - the driver.
2556///
2557/// # Returns
2558///
2559/// As [`pamoja_scd4x_stop`].
2560///
2561/// # Safety
2562///
2563/// `sensor` must be a live handle or null.
2564#[no_mangle]
2565pub unsafe extern "C" fn pamoja_scd4x_start(sensor: *mut PamojaScd4x) -> PamojaStatus {
2566 run(sensor, |held| held.sensor.start())
2567}
2568
2569/// Sets the temperature offset that compensates the part's own warmth, stopping periodic
2570/// measurement to write it and starting it again. The setting lasts until power is lost.
2571///
2572/// # Arguments
2573///
2574/// * `sensor` - the driver.
2575/// * `milli_celsius` - the offset to subtract, in millidegrees.
2576///
2577/// # Returns
2578///
2579/// As [`pamoja_scd4x_stop`].
2580///
2581/// # Safety
2582///
2583/// `sensor` must be a live handle or null.
2584#[no_mangle]
2585pub unsafe extern "C" fn pamoja_scd4x_set_temperature_offset(
2586 sensor: *mut PamojaScd4x,
2587 milli_celsius: u32,
2588) -> PamojaStatus {
2589 run(sensor, |held| {
2590 held.sensor.set_temperature_offset(milli_celsius)
2591 })
2592}
2593
2594/// Sets the altitude the part corrects its carbon dioxide reading for, stopping periodic
2595/// measurement to write it and starting it again.
2596///
2597/// # Arguments
2598///
2599/// * `sensor` - the driver.
2600/// * `meters` - the altitude above sea level.
2601///
2602/// # Returns
2603///
2604/// As [`pamoja_scd4x_stop`].
2605///
2606/// # Safety
2607///
2608/// `sensor` must be a live handle or null.
2609#[no_mangle]
2610pub unsafe extern "C" fn pamoja_scd4x_set_sensor_altitude(
2611 sensor: *mut PamojaScd4x,
2612 meters: u16,
2613) -> PamojaStatus {
2614 run(sensor, |held| held.sensor.set_sensor_altitude(meters))
2615}
2616
2617/// Reports the serial number an SCD4x driver read at initialization.
2618///
2619/// # Arguments
2620///
2621/// * `sensor` - the driver.
2622/// * `out_serial` - receives the 48-bit serial number.
2623///
2624/// # Returns
2625///
2626/// `true` with the serial in `out_serial`; `false` before the part has been initialized, or
2627/// for a null argument.
2628///
2629/// # Safety
2630///
2631/// `sensor` must be a live handle or null, and `out_serial` a writable pointer or null.
2632#[no_mangle]
2633pub unsafe extern "C" fn pamoja_scd4x_serial(
2634 sensor: *const PamojaScd4x,
2635 out_serial: *mut u64,
2636) -> bool {
2637 let Some(held) = sensor.as_ref() else {
2638 return false;
2639 };
2640 store(out_serial, held.sensor.serial())
2641}
2642
2643/// Releases a driver and its share of the bus. A null pointer is ignored.
2644///
2645/// # Safety
2646///
2647/// `sensor` must be a handle from [`pamoja_scd4x_new`] that has not been freed, or null.
2648#[no_mangle]
2649pub unsafe extern "C" fn pamoja_scd4x_free(sensor: *mut PamojaScd4x) {
2650 release(sensor);
2651}
2652
2653/// Creates a simulated SCD4x reading [`PAMOJA_SCD4X_SIM_CO2_PPM`],
2654/// [`PAMOJA_SCD4X_SIM_CELSIUS`], and [`PAMOJA_SCD4X_SIM_RELATIVE_HUMIDITY`].
2655///
2656/// # Returns
2657///
2658/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
2659#[no_mangle]
2660pub extern "C" fn pamoja_scd4x_sim_part() -> *mut PamojaI2cPart {
2661 PamojaI2cPart::into_raw(scd4x::sim::part())
2662}
2663
2664/// Creates a simulated SCD4x that reads what it is asked to. It answers the serial number,
2665/// data-ready, and measurement commands, and always has a result waiting.
2666///
2667/// # Arguments
2668///
2669/// * `co2_ppm` - the carbon dioxide it reports, in parts per million.
2670/// * `celsius` - the temperature it reports.
2671/// * `relative_humidity` - the humidity it reports, as a percentage.
2672///
2673/// # Returns
2674///
2675/// The part, which the caller releases with [`crate::hal::pamoja_i2c_part_free`].
2676#[no_mangle]
2677pub extern "C" fn pamoja_scd4x_sim_reporting(
2678 co2_ppm: u16,
2679 celsius: f32,
2680 relative_humidity: f32,
2681) -> *mut PamojaI2cPart {
2682 PamojaI2cPart::into_raw(scd4x::sim::reporting(co2_ppm, celsius, relative_humidity))
2683}
2684
2685/// Names a DS18B20 the kernel serves by the serial in its directory name, reading
2686/// `/sys/bus/w1/devices/28-<serial>/w1_slave`.
2687///
2688/// # Arguments
2689///
2690/// * `serial` - the twelve hex digits after `28-`.
2691/// * `out_thermometer` - receives the thermometer.
2692///
2693/// # Returns
2694///
2695/// [`PamojaStatus::Ok`], or [`PamojaStatus::InvalidArgument`] for a null or non-UTF-8
2696/// argument. Nothing is read until [`pamoja_ds18b20_thermometer_read`].
2697///
2698/// # Safety
2699///
2700/// `serial` must be a null-terminated string or null, and `out_thermometer` a writable pointer
2701/// or null.
2702#[no_mangle]
2703pub unsafe extern "C" fn pamoja_ds18b20_thermometer_new(
2704 serial: *const c_char,
2705 out_thermometer: *mut *mut PamojaDs18b20Thermometer,
2706) -> PamojaStatus {
2707 thermometer_from(
2708 serial,
2709 "serial",
2710 out_thermometer,
2711 ds18b20::linux::Thermometer::new,
2712 )
2713}
2714
2715/// Names a DS18B20 by the path of its `w1_slave` file, for a system that mounts sysfs elsewhere
2716/// or a test that writes the file itself.
2717///
2718/// # Arguments
2719///
2720/// * `path` - the file to read.
2721/// * `out_thermometer` - receives the thermometer.
2722///
2723/// # Returns
2724///
2725/// As [`pamoja_ds18b20_thermometer_new`].
2726///
2727/// # Safety
2728///
2729/// `path` must be a null-terminated string or null, and `out_thermometer` a writable pointer or
2730/// null.
2731#[no_mangle]
2732pub unsafe extern "C" fn pamoja_ds18b20_thermometer_at(
2733 path: *const c_char,
2734 out_thermometer: *mut *mut PamojaDs18b20Thermometer,
2735) -> PamojaStatus {
2736 thermometer_from(path, "path", out_thermometer, |path| {
2737 ds18b20::linux::Thermometer::at(path)
2738 })
2739}
2740
2741/// Returns the path of the file a thermometer reads.
2742///
2743/// # Arguments
2744///
2745/// * `thermometer` - the thermometer.
2746///
2747/// # Returns
2748///
2749/// The path, which the caller releases with [`crate::pamoja_string_free`], or null for a null
2750/// thermometer.
2751///
2752/// # Safety
2753///
2754/// `thermometer` must be a live handle or null.
2755#[no_mangle]
2756pub unsafe extern "C" fn pamoja_ds18b20_thermometer_path(
2757 thermometer: *const PamojaDs18b20Thermometer,
2758) -> *mut PamojaString {
2759 match thermometer.as_ref() {
2760 Some(held) => {
2761 PamojaString::into_raw(held.thermometer.path().to_string_lossy().into_owned())
2762 }
2763 None => std::ptr::null_mut(),
2764 }
2765}
2766
2767/// Returns the serial the kernel named a thermometer's directory after.
2768///
2769/// # Arguments
2770///
2771/// * `thermometer` - the thermometer.
2772///
2773/// # Returns
2774///
2775/// The twelve hex digits after `28-`, which the caller releases with
2776/// [`crate::pamoja_string_free`], or null when the file does not sit in a DS18B20's
2777/// directory or the thermometer is null.
2778///
2779/// # Safety
2780///
2781/// `thermometer` must be a live handle or null.
2782#[no_mangle]
2783pub unsafe extern "C" fn pamoja_ds18b20_thermometer_serial(
2784 thermometer: *const PamojaDs18b20Thermometer,
2785) -> *mut PamojaString {
2786 match thermometer
2787 .as_ref()
2788 .and_then(|held| held.thermometer.serial())
2789 {
2790 Some(serial) => PamojaString::into_raw(serial.to_owned()),
2791 None => std::ptr::null_mut(),
2792 }
2793}
2794
2795/// Reads a thermometer's file, which makes the kernel run a conversion, and decodes it.
2796///
2797/// # Arguments
2798///
2799/// * `thermometer` - the thermometer.
2800/// * `out_reading` - receives the CRC-checked reading.
2801///
2802/// # Returns
2803///
2804/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null argument;
2805/// [`PamojaStatus::Io`] when the file cannot be read, because the 1-Wire overlay is off, the
2806/// probe is gone, or the process may not read it; or [`PamojaStatus::Codec`] when the kernel or
2807/// this decoder rejects the CRC.
2808///
2809/// # Safety
2810///
2811/// `thermometer` must be a live handle or null, and `out_reading` a writable pointer or null.
2812#[no_mangle]
2813pub unsafe extern "C" fn pamoja_ds18b20_thermometer_read(
2814 thermometer: *const PamojaDs18b20Thermometer,
2815 out_reading: *mut PamojaDs18b20Reading,
2816) -> PamojaStatus {
2817 if out_reading.is_null() {
2818 set_last_error("out_reading must not be null".to_owned());
2819 return PamojaStatus::InvalidArgument;
2820 }
2821 let Some(held) = thermometer.as_ref() else {
2822 set_last_error("thermometer must not be null".to_owned());
2823 return PamojaStatus::InvalidArgument;
2824 };
2825 match catch_unwind(AssertUnwindSafe(|| held.thermometer.read_scratchpad())) {
2826 Ok(Ok(scratchpad)) => {
2827 *out_reading = scratchpad.into();
2828 PamojaStatus::Ok
2829 }
2830 Ok(Err(error)) => {
2831 set_last_error(error.to_string());
2832 match error {
2833 ds18b20::linux::ThermometerError::Io(_) => PamojaStatus::Io,
2834 ds18b20::linux::ThermometerError::Sensor(_) => PamojaStatus::Codec,
2835 }
2836 }
2837 Err(_) => panicked(),
2838 }
2839}
2840
2841/// Releases a thermometer. A null pointer is ignored.
2842///
2843/// # Safety
2844///
2845/// `thermometer` must be a handle that has not been freed, or null.
2846#[no_mangle]
2847pub unsafe extern "C" fn pamoja_ds18b20_thermometer_free(
2848 thermometer: *mut PamojaDs18b20Thermometer,
2849) {
2850 release(thermometer);
2851}
2852
2853/// Lists every DS18B20 the kernel has found: one per `28-` directory.
2854///
2855/// # Arguments
2856///
2857/// * `devices` - the directory the kernel lists its 1-Wire devices in, or null for
2858/// `/sys/bus/w1/devices`.
2859/// * `out_thermometers` - receives the list, sorted by directory name.
2860///
2861/// # Returns
2862///
2863/// [`PamojaStatus::Ok`]; [`PamojaStatus::InvalidArgument`] for a null `out_thermometers` or a
2864/// non-UTF-8 directory; or [`PamojaStatus::Io`] when the directory cannot be listed, which
2865/// usually means the 1-Wire overlay is off.
2866///
2867/// # Safety
2868///
2869/// `devices` must be a null-terminated string or null, and `out_thermometers` a writable
2870/// pointer or null.
2871#[no_mangle]
2872pub unsafe extern "C" fn pamoja_ds18b20_discover(
2873 devices: *const c_char,
2874 out_thermometers: *mut *mut PamojaDs18b20Thermometers,
2875) -> PamojaStatus {
2876 if out_thermometers.is_null() {
2877 set_last_error("out_thermometers must not be null".to_owned());
2878 return PamojaStatus::InvalidArgument;
2879 }
2880 *out_thermometers = std::ptr::null_mut();
2881 let devices = if devices.is_null() {
2882 ds18b20::linux::DEVICES
2883 } else {
2884 match read_str(devices, "devices") {
2885 Some(devices) => devices,
2886 None => return PamojaStatus::InvalidArgument,
2887 }
2888 };
2889 match ds18b20::linux::Thermometer::discover_in(Path::new(devices)) {
2890 Ok(found) => {
2891 *out_thermometers = Box::into_raw(Box::new(PamojaDs18b20Thermometers { found }));
2892 PamojaStatus::Ok
2893 }
2894 Err(error) => {
2895 set_last_error(format!("{devices}: {error}"));
2896 PamojaStatus::Io
2897 }
2898 }
2899}
2900
2901/// Returns how many thermometers a list holds.
2902///
2903/// # Arguments
2904///
2905/// * `thermometers` - the list.
2906///
2907/// # Returns
2908///
2909/// The count, or 0 for a null list.
2910///
2911/// # Safety
2912///
2913/// `thermometers` must be a live handle or null.
2914#[no_mangle]
2915pub unsafe extern "C" fn pamoja_ds18b20_thermometers_len(
2916 thermometers: *const PamojaDs18b20Thermometers,
2917) -> usize {
2918 thermometers.as_ref().map_or(0, |held| held.found.len())
2919}
2920
2921/// Copies one thermometer out of a list.
2922///
2923/// # Arguments
2924///
2925/// * `thermometers` - the list.
2926/// * `index` - which one, below [`pamoja_ds18b20_thermometers_len`].
2927///
2928/// # Returns
2929///
2930/// A new thermometer, which the caller releases with [`pamoja_ds18b20_thermometer_free`], or
2931/// null for a null list or an index past the end.
2932///
2933/// # Safety
2934///
2935/// `thermometers` must be a live handle or null.
2936#[no_mangle]
2937pub unsafe extern "C" fn pamoja_ds18b20_thermometers_get(
2938 thermometers: *const PamojaDs18b20Thermometers,
2939 index: usize,
2940) -> *mut PamojaDs18b20Thermometer {
2941 match thermometers.as_ref().and_then(|held| held.found.get(index)) {
2942 Some(thermometer) => Box::into_raw(Box::new(PamojaDs18b20Thermometer {
2943 thermometer: thermometer.clone(),
2944 })),
2945 None => std::ptr::null_mut(),
2946 }
2947}
2948
2949/// Releases a list of thermometers. A null pointer is ignored.
2950///
2951/// # Safety
2952///
2953/// `thermometers` must be a handle that has not been freed, or null.
2954#[no_mangle]
2955pub unsafe extern "C" fn pamoja_ds18b20_thermometers_free(
2956 thermometers: *mut PamojaDs18b20Thermometers,
2957) {
2958 release(thermometers);
2959}
2960
2961/// An ADS1115 sample as it crosses the boundary.
2962fn sample_of(sample: ads1115::Sample) -> PamojaAds1115Sample {
2963 PamojaAds1115Sample {
2964 raw: sample.raw,
2965 pga: sample.pga.code(),
2966 nanovolts: sample.nanovolts(),
2967 volts: sample.volts(),
2968 clipped: u8::from(sample.clipped()),
2969 }
2970}
2971
2972/// Builds a driver on the caller's bus and hands it back through `out`.
2973///
2974/// # Safety
2975///
2976/// `bus` must be a live handle or null, and `out` a writable pointer or null.
2977unsafe fn build<H>(
2978 bus: *const PamojaI2cBus,
2979 out: *mut *mut H,
2980 make: impl FnOnce(I2cBus, BusDelay) -> H,
2981) -> PamojaStatus {
2982 if out.is_null() {
2983 set_last_error("out_sensor must not be null".to_owned());
2984 return PamojaStatus::InvalidArgument;
2985 }
2986 *out = std::ptr::null_mut();
2987 let Some(bus) = bus.as_ref() else {
2988 set_last_error("bus must not be null".to_owned());
2989 return PamojaStatus::InvalidArgument;
2990 };
2991 let bus = bus.bus.clone();
2992 let delay = bus.delay();
2993 *out = Box::into_raw(Box::new(make(bus, delay)));
2994 PamojaStatus::Ok
2995}
2996
2997/// Clears a caller's out pointer before returning a refusal that built nothing.
2998///
2999/// # Safety
3000///
3001/// `out` must be a writable pointer or null.
3002unsafe fn clear_then<H>(out: *mut *mut H, status: PamojaStatus) -> PamojaStatus {
3003 if !out.is_null() {
3004 *out = std::ptr::null_mut();
3005 }
3006 status
3007}
3008
3009/// Names a DS18B20 from a string argument and hands it back through `out`.
3010///
3011/// # Safety
3012///
3013/// `text` must be a null-terminated string or null, and `out` a writable pointer or null.
3014unsafe fn thermometer_from(
3015 text: *const c_char,
3016 what: &str,
3017 out: *mut *mut PamojaDs18b20Thermometer,
3018 make: impl FnOnce(&str) -> ds18b20::linux::Thermometer,
3019) -> PamojaStatus {
3020 if out.is_null() {
3021 set_last_error("out_thermometer must not be null".to_owned());
3022 return PamojaStatus::InvalidArgument;
3023 }
3024 *out = std::ptr::null_mut();
3025 let Some(text) = read_str(text, what) else {
3026 return PamojaStatus::InvalidArgument;
3027 };
3028 *out = Box::into_raw(Box::new(PamojaDs18b20Thermometer {
3029 thermometer: make(text),
3030 }));
3031 PamojaStatus::Ok
3032}
3033
3034/// Runs a driver call that returns nothing, turning a failure or a panic into a status.
3035///
3036/// # Safety
3037///
3038/// `handle` must be a live handle or null.
3039unsafe fn run<H, E: Debug + Display>(
3040 handle: *mut H,
3041 call: impl FnOnce(&mut H) -> Result<(), DriverError<E>>,
3042) -> PamojaStatus {
3043 match on_driver(handle, call) {
3044 Ok(()) => PamojaStatus::Ok,
3045 Err(status) => status,
3046 }
3047}
3048
3049/// Runs a driver call and writes what it returns through `out`, converted for the boundary.
3050///
3051/// # Safety
3052///
3053/// `handle` must be a live handle or null, and `out` a writable pointer or null.
3054unsafe fn fill<H, T, R, E: Debug + Display>(
3055 handle: *mut H,
3056 out: *mut R,
3057 name: &str,
3058 call: impl FnOnce(&mut H) -> Result<T, DriverError<E>>,
3059 convert: impl FnOnce(T) -> R,
3060) -> PamojaStatus {
3061 if out.is_null() {
3062 set_last_error(format!("{name} must not be null"));
3063 return PamojaStatus::InvalidArgument;
3064 }
3065 match on_driver(handle, call) {
3066 Ok(value) => {
3067 *out = convert(value);
3068 PamojaStatus::Ok
3069 }
3070 Err(status) => status,
3071 }
3072}
3073
3074/// Writes something a driver holds, without touching the bus, through `out`.
3075///
3076/// # Safety
3077///
3078/// `handle` must be a live handle or null, and `out` a writable pointer or null.
3079unsafe fn look<H, R>(
3080 handle: *const H,
3081 out: *mut R,
3082 name: &str,
3083 read: impl FnOnce(&H) -> R,
3084) -> PamojaStatus {
3085 if out.is_null() {
3086 set_last_error(format!("{name} must not be null"));
3087 return PamojaStatus::InvalidArgument;
3088 }
3089 let Some(held) = handle.as_ref() else {
3090 set_last_error("sensor must not be null".to_owned());
3091 return PamojaStatus::InvalidArgument;
3092 };
3093 *out = read(held);
3094 PamojaStatus::Ok
3095}
3096
3097/// Writes a value a driver may not have yet through `out`, reporting whether it had one.
3098///
3099/// # Safety
3100///
3101/// `out` must be a writable pointer or null.
3102unsafe fn store<R>(out: *mut R, value: Option<R>) -> bool {
3103 match (out.is_null(), value) {
3104 (false, Some(value)) => {
3105 *out = value;
3106 true
3107 }
3108 _ => false,
3109 }
3110}
3111
3112/// Copies a block of bytes into a caller's buffer of exactly that size.
3113///
3114/// # Safety
3115///
3116/// `out` must point to `bytes.len()` writable bytes, or be null.
3117unsafe fn copy_out(out: *mut u8, name: &str, bytes: &[u8]) -> PamojaStatus {
3118 if out.is_null() {
3119 set_last_error(format!("{name} must not be null"));
3120 return PamojaStatus::InvalidArgument;
3121 }
3122 std::slice::from_raw_parts_mut(out, bytes.len()).copy_from_slice(bytes);
3123 PamojaStatus::Ok
3124}
3125
3126/// Releases a boxed handle; a null pointer is ignored.
3127///
3128/// # Safety
3129///
3130/// `handle` must be a boxed handle that has not been freed, or null.
3131unsafe fn release<H>(handle: *mut H) {
3132 if !handle.is_null() {
3133 drop(Box::from_raw(handle));
3134 }
3135}
3136
3137/// Runs a driver call, turning a failure or a panic into a status.
3138///
3139/// # Safety
3140///
3141/// `handle` must be a live handle or null.
3142unsafe fn on_driver<H, T, E: Debug + Display>(
3143 handle: *mut H,
3144 call: impl FnOnce(&mut H) -> Result<T, DriverError<E>>,
3145) -> Result<T, PamojaStatus> {
3146 let Some(held) = handle.as_mut() else {
3147 set_last_error("sensor must not be null".to_owned());
3148 return Err(PamojaStatus::InvalidArgument);
3149 };
3150 match catch_unwind(AssertUnwindSafe(|| call(held))) {
3151 Ok(Ok(value)) => Ok(value),
3152 Ok(Err(error)) => Err(driver_failed(&error)),
3153 Err(_) => Err(panicked()),
3154 }
3155}
3156
3157/// Records why a driver failed, in the bus's own words when the bus was the cause, and maps
3158/// it onto its status.
3159fn driver_failed<E: Debug + Display>(error: &DriverError<E>) -> PamojaStatus {
3160 match error {
3161 DriverError::Bus(error) => {
3162 set_last_error(error.to_string());
3163 PamojaStatus::Io
3164 }
3165 DriverError::Timeout => {
3166 set_last_error(error.to_string());
3167 PamojaStatus::Io
3168 }
3169 DriverError::Sensor(sensor) => {
3170 set_last_error(sensor.to_string());
3171 PamojaStatus::Codec
3172 }
3173 }
3174}
3175
3176/// Records a caught panic and reports it as [`PamojaStatus::Panic`].
3177fn panicked() -> PamojaStatus {
3178 set_last_error("panic at the FFI boundary".to_owned());
3179 PamojaStatus::Panic
3180}
3181
3182#[cfg(test)]
3183mod tests {
3184 use super::*;
3185 use crate::hal::{
3186 pamoja_i2c_bus_attach, pamoja_i2c_bus_free, pamoja_i2c_bus_part, pamoja_i2c_bus_simulated,
3187 pamoja_i2c_bus_waited_micros, pamoja_i2c_part_free, pamoja_i2c_part_load,
3188 pamoja_i2c_part_new, pamoja_i2c_part_received, pamoja_i2c_part_received_count,
3189 pamoja_i2c_part_register, pamoja_i2c_part_word,
3190 };
3191 use crate::sensors::{
3192 pamoja_bme280_ctrl_hum_from_bits, pamoja_bme280_ctrl_meas_from_bits,
3193 pamoja_bme280_max_measurement_micros, PamojaBme280CtrlMeas,
3194 PAMOJA_BME280_I2C_ADDRESS_PRIMARY, PAMOJA_BME280_REGISTER_CHIP_ID,
3195 PAMOJA_BME280_REGISTER_CTRL_HUM, PAMOJA_BME280_REGISTER_CTRL_MEAS,
3196 };
3197 use std::ffi::{CStr, CString};
3198 use std::ptr;
3199
3200 const PART: u8 = PAMOJA_BME280_I2C_ADDRESS_PRIMARY;
3201
3202 fn last_error() -> String {
3203 let message = crate::pamoja_last_error_message();
3204 if message.is_null() {
3205 return String::new();
3206 }
3207 unsafe { CStr::from_ptr(message) }
3208 .to_string_lossy()
3209 .into_owned()
3210 }
3211
3212 fn measurement() -> PamojaBme280Measurement {
3213 PamojaBme280Measurement {
3214 celsius: 0.0,
3215 pascals: 0,
3216 hectopascals: 0.0,
3217 relative_humidity_percent: 0.0,
3218 }
3219 }
3220
3221 /// A simulated bus holding one part, with the part's own handle already freed.
3222 unsafe fn bus_with(part: *mut PamojaI2cPart) -> *mut PamojaI2cBus {
3223 let bus = pamoja_i2c_bus_simulated();
3224 attach(bus, part);
3225 bus
3226 }
3227
3228 /// Puts a part on a simulated bus and frees the part's own handle.
3229 unsafe fn attach(bus: *mut PamojaI2cBus, part: *mut PamojaI2cPart) {
3230 assert_eq!(pamoja_i2c_bus_attach(bus, part), PamojaStatus::Ok);
3231 pamoja_i2c_part_free(part);
3232 }
3233
3234 #[test]
3235 fn a_driver_measures_a_simulated_part_and_leaves_it_configured() {
3236 unsafe {
3237 let bus = bus_with(pamoja_bme280_sim_part(PART));
3238 let mut sensor = ptr::null_mut();
3239 assert_eq!(
3240 pamoja_bme280_new(bus, PART, pamoja_bme280_settings_default(), &mut sensor),
3241 PamojaStatus::Ok
3242 );
3243 let mut reading = measurement();
3244 assert_eq!(
3245 pamoja_bme280_measure(sensor, &mut reading),
3246 PamojaStatus::Ok
3247 );
3248 assert_eq!(reading.celsius, 20.44);
3249 assert_eq!(reading.pascals, 84_805);
3250
3251 let held = pamoja_i2c_bus_part(bus, PART);
3252 let mut ctrl = PamojaBme280CtrlMeas {
3253 temperature: 0,
3254 pressure: 0,
3255 mode: 0,
3256 };
3257 assert_eq!(
3258 pamoja_bme280_ctrl_meas_from_bits(
3259 pamoja_i2c_part_register(held, PAMOJA_BME280_REGISTER_CTRL_MEAS),
3260 &mut ctrl
3261 ),
3262 PamojaStatus::Ok
3263 );
3264 assert_eq!(
3265 ctrl.mode,
3266 bme280::Mode::Forced.code(),
3267 "the last write forced it"
3268 );
3269 assert_eq!(
3270 pamoja_bme280_ctrl_hum_from_bits(pamoja_i2c_part_register(
3271 held,
3272 PAMOJA_BME280_REGISTER_CTRL_HUM
3273 )),
3274 bme280::Oversampling::X1.code()
3275 );
3276 pamoja_i2c_part_free(held);
3277
3278 assert_eq!(
3279 pamoja_i2c_bus_waited_micros(bus),
3280 u64::from(bme280::STARTUP_MICROS)
3281 + u64::from(pamoja_bme280_max_measurement_micros(1, 1, 1)),
3282 "the reset's start-up time and one measurement's"
3283 );
3284 pamoja_bme280_free(sensor);
3285 pamoja_i2c_bus_free(bus);
3286 }
3287 }
3288
3289 #[test]
3290 fn a_part_asked_for_a_reading_reports_it() {
3291 unsafe {
3292 let bus = bus_with(pamoja_bme280_sim_reporting(PART, 4.0, 1013.25, 80.0));
3293 let mut sensor = ptr::null_mut();
3294 assert_eq!(
3295 pamoja_bme280_new(bus, PART, pamoja_bme280_settings_default(), &mut sensor),
3296 PamojaStatus::Ok
3297 );
3298 pamoja_i2c_bus_free(bus);
3299
3300 let mut reading = measurement();
3301 assert_eq!(
3302 pamoja_bme280_measure(sensor, &mut reading),
3303 PamojaStatus::Ok
3304 );
3305 assert_eq!(reading.celsius, 4.0);
3306 assert!((reading.hectopascals - 1013.25).abs() < 0.01);
3307 assert!((reading.relative_humidity_percent - 80.0).abs() < 0.01);
3308 pamoja_bme280_free(sensor);
3309 }
3310 }
3311
3312 #[test]
3313 fn a_missing_part_is_an_io_error_and_another_part_a_codec_error() {
3314 unsafe {
3315 let bus = pamoja_i2c_bus_simulated();
3316 let mut sensor = ptr::null_mut();
3317 assert_eq!(
3318 pamoja_bme280_new(bus, PART, pamoja_bme280_settings_default(), &mut sensor),
3319 PamojaStatus::Ok
3320 );
3321 assert_eq!(pamoja_bme280_init(sensor), PamojaStatus::Io);
3322 assert_eq!(last_error(), "nothing answered at 0x76");
3323
3324 let bmp280 = pamoja_i2c_part_new(PART);
3325 assert_eq!(
3326 pamoja_i2c_part_load(bmp280, PAMOJA_BME280_REGISTER_CHIP_ID, [0x58].as_ptr(), 1),
3327 PamojaStatus::Ok
3328 );
3329 assert_eq!(pamoja_i2c_bus_attach(bus, bmp280), PamojaStatus::Ok);
3330 pamoja_i2c_part_free(bmp280);
3331 assert_eq!(pamoja_bme280_init(sensor), PamojaStatus::Codec);
3332 assert_eq!(last_error(), "sensor identification mismatch");
3333
3334 pamoja_bme280_free(sensor);
3335 pamoja_i2c_bus_free(bus);
3336 }
3337 }
3338
3339 #[test]
3340 fn the_shipped_calibration_and_a_built_burst_compensate_to_the_reading() {
3341 unsafe {
3342 let mut temp_press = [0u8; PAMOJA_BME280_CALIBRATION_TEMP_PRESS_LEN];
3343 let mut humidity = [0u8; PAMOJA_BME280_CALIBRATION_HUMIDITY_LEN];
3344 assert_eq!(
3345 pamoja_bme280_sim_calibration(temp_press.as_mut_ptr(), humidity.as_mut_ptr()),
3346 PamojaStatus::Ok
3347 );
3348 let calibration = bme280::Calibration::from_registers(&temp_press, &humidity);
3349
3350 let mut shipped = [0u8; PAMOJA_BME280_MEASUREMENT_LEN];
3351 assert_eq!(
3352 pamoja_bme280_sim_burst(shipped.as_mut_ptr()),
3353 PamojaStatus::Ok
3354 );
3355 let reading = calibration.compensate(&bme280::RawMeasurement::from_registers(&shipped));
3356 assert_eq!(reading.celsius(), 20.44);
3357
3358 let mut burst = [0u8; PAMOJA_BME280_MEASUREMENT_LEN];
3359 assert_eq!(
3360 pamoja_bme280_sim_burst_for(-12.5, 990.0, 35.0, burst.as_mut_ptr()),
3361 PamojaStatus::Ok
3362 );
3363 let reading = calibration.compensate(&bme280::RawMeasurement::from_registers(&burst));
3364 assert_eq!(reading.celsius(), -12.5);
3365 assert!((reading.hectopascals() - 990.0).abs() < 0.01);
3366 assert!((reading.relative_humidity_percent() - 35.0).abs() < 0.01);
3367 assert_eq!(
3368 pamoja_bme280_sim_burst_for(0.0, 0.0, 0.0, ptr::null_mut()),
3369 PamojaStatus::InvalidArgument
3370 );
3371 assert_eq!(
3372 pamoja_bme280_sim_burst(ptr::null_mut()),
3373 PamojaStatus::InvalidArgument
3374 );
3375 }
3376 }
3377
3378 #[test]
3379 fn a_bmp280_reads_its_simulated_twin_and_keeps_the_trimming() {
3380 unsafe {
3381 let bus = bus_with(pamoja_bmp280_sim_reporting(0x77, -7.5, 1003.0));
3382 let mut sensor = ptr::null_mut();
3383 assert_eq!(
3384 pamoja_bmp280_new(bus, 0x77, pamoja_bmp280_settings_default(), &mut sensor),
3385 PamojaStatus::Ok
3386 );
3387 let mut coefficients = std::mem::zeroed::<PamojaBmp280Coefficients>();
3388 assert!(!pamoja_bmp280_coefficients(sensor, &mut coefficients));
3389 let mut reading = PamojaBmp280Reading {
3390 celsius: 0.0,
3391 pascals: 0,
3392 hectopascals: 0.0,
3393 };
3394 assert_eq!(
3395 pamoja_bmp280_measure(sensor, &mut reading),
3396 PamojaStatus::Ok
3397 );
3398 assert!((reading.celsius + 7.5).abs() < 0.01);
3399 assert!((reading.hectopascals - 1003.0).abs() < 0.01);
3400 assert!(pamoja_bmp280_coefficients(sensor, &mut coefficients));
3401 assert_eq!(
3402 coefficients,
3403 PamojaBmp280Coefficients::from(bmp280::sim::calibration())
3404 );
3405
3406 let mut burst = [0u8; PAMOJA_BMP280_DATA_LEN];
3407 let mut calibration = [0u8; PAMOJA_BMP280_CALIBRATION_LEN];
3408 assert_eq!(
3409 pamoja_bmp280_sim_burst(burst.as_mut_ptr()),
3410 PamojaStatus::Ok
3411 );
3412 assert_eq!(
3413 pamoja_bmp280_sim_calibration(calibration.as_mut_ptr()),
3414 PamojaStatus::Ok
3415 );
3416 let compensated = bmp280::Calibration::parse(&calibration)
3417 .compensate(&bmp280::Measurement::parse(&burst));
3418 assert_eq!(compensated.celsius(), 20.44);
3419 pamoja_bmp280_free(sensor);
3420 pamoja_i2c_bus_free(bus);
3421 }
3422 }
3423
3424 #[test]
3425 fn a_tmp117_reads_its_twin_and_holds_the_alerts_it_consumed() {
3426 unsafe {
3427 let bus = bus_with(pamoja_tmp117_sim_part(0x48));
3428 let mut sensor = ptr::null_mut();
3429 assert_eq!(
3430 pamoja_tmp117_new(bus, 0x48, 1, &mut sensor),
3431 PamojaStatus::Ok
3432 );
3433 let mut revision = 0;
3434 assert!(!pamoja_tmp117_silicon_revision(sensor, &mut revision));
3435 let mut reading = PamojaTmp117Reading {
3436 raw: 0,
3437 micro_celsius: 0,
3438 celsius: 0.0,
3439 };
3440 assert_eq!(
3441 pamoja_tmp117_measure(sensor, &mut reading),
3442 PamojaStatus::Ok
3443 );
3444 assert_eq!(reading.celsius, PAMOJA_TMP117_SIM_CELSIUS);
3445 assert_eq!(reading.micro_celsius, 21_250_000);
3446 assert!(pamoja_tmp117_silicon_revision(sensor, &mut revision));
3447 assert_eq!(
3448 pamoja_tmp117_set_alert_limits(sensor, 30.0, 10.0),
3449 PamojaStatus::Ok
3450 );
3451 let mut alerts = PamojaTmp117Alerts { high: 1, low: 1 };
3452 assert_eq!(pamoja_tmp117_alerts(sensor, &mut alerts), PamojaStatus::Ok);
3453 assert_eq!(alerts, PamojaTmp117Alerts { high: 0, low: 0 });
3454
3455 let held = pamoja_i2c_bus_part(bus, 0x48);
3456 assert_eq!(
3457 pamoja_i2c_part_word(held, tmp117::register::THIGH_LIMIT),
3458 tmp117::raw_from_celsius(30.0) as u16
3459 );
3460 pamoja_i2c_part_free(held);
3461 pamoja_tmp117_free(sensor);
3462 pamoja_i2c_bus_free(bus);
3463 }
3464 }
3465
3466 #[test]
3467 fn an_opt3001_and_an_hdc1080_read_their_twins() {
3468 unsafe {
3469 let bus = bus_with(pamoja_opt3001_sim_reporting(0x44, 1200.0));
3470 attach(bus, pamoja_hdc1080_sim_reporting(-20.0, 12.5));
3471
3472 let mut light = ptr::null_mut();
3473 let settings = PamojaOpt3001Settings {
3474 long_conversion: 0,
3475 range_number: opt3001::RANGE_AUTOMATIC,
3476 };
3477 assert_eq!(
3478 pamoja_opt3001_new(bus, 0x44, settings, &mut light),
3479 PamojaStatus::Ok
3480 );
3481 let mut lux = PamojaOpt3001Reading {
3482 raw: 0,
3483 milli_lux: 0,
3484 lux: 0.0,
3485 };
3486 assert_eq!(pamoja_opt3001_measure(light, &mut lux), PamojaStatus::Ok);
3487 assert_eq!(lux.lux, 1200.0);
3488 assert_eq!(
3489 pamoja_i2c_bus_waited_micros(bus),
3490 100_000,
3491 "one short conversion"
3492 );
3493 let mut config = std::mem::zeroed::<PamojaOpt3001Config>();
3494 assert_eq!(
3495 pamoja_opt3001_configuration(light, &mut config),
3496 PamojaStatus::Ok
3497 );
3498 assert_eq!(config.long_conversion, 0);
3499
3500 let mut climate = ptr::null_mut();
3501 let wrong = PamojaHdc1080Settings {
3502 temperature_resolution_bits: 12,
3503 humidity_resolution_bits: 14,
3504 };
3505 assert_eq!(
3506 pamoja_hdc1080_new(bus, wrong, &mut climate),
3507 PamojaStatus::InvalidArgument
3508 );
3509 assert!(climate.is_null());
3510 assert_eq!(
3511 pamoja_hdc1080_new(bus, pamoja_hdc1080_settings_default(), &mut climate),
3512 PamojaStatus::Ok
3513 );
3514 let mut reading = std::mem::zeroed::<PamojaHdc1080Measurement>();
3515 assert_eq!(
3516 pamoja_hdc1080_measure(climate, &mut reading),
3517 PamojaStatus::Ok
3518 );
3519 assert!((reading.celsius + 20.0).abs() < 0.003);
3520 assert!((reading.relative_humidity - 12.5).abs() < 0.002);
3521 assert_eq!(pamoja_hdc1080_heater(climate, true), PamojaStatus::Ok);
3522 let mut config = std::mem::zeroed::<PamojaHdc1080Config>();
3523 assert_eq!(
3524 pamoja_hdc1080_configuration(climate, &mut config),
3525 PamojaStatus::Ok
3526 );
3527 assert_eq!(config.heater, 1);
3528
3529 pamoja_opt3001_free(light);
3530 pamoja_hdc1080_free(climate);
3531 pamoja_i2c_bus_free(bus);
3532 }
3533 }
3534
3535 #[test]
3536 fn the_current_monitors_read_the_load_their_twins_carry() {
3537 unsafe {
3538 let bus = bus_with(pamoja_ina219_sim_part(0x40));
3539 attach(
3540 bus,
3541 pamoja_ina226_sim_reporting(0x45, 2, 20_000_000, 3_300_000, -10_000_000),
3542 );
3543
3544 let mut solar = ptr::null_mut();
3545 assert_eq!(
3546 pamoja_ina219_new(bus, 0x40, pamoja_ina219_settings_default(), &mut solar),
3547 PamojaStatus::Ok
3548 );
3549 let mut reading = std::mem::zeroed::<PamojaIna219Reading>();
3550 assert_eq!(pamoja_ina219_measure(solar, &mut reading), PamojaStatus::Ok);
3551 assert_eq!(reading.bus_millivolts, PAMOJA_INA219_SIM_BUS_MILLIVOLTS);
3552 assert!(
3553 (reading.current_microamps - PAMOJA_INA219_SIM_MICROAMPS).abs()
3554 < reading.current_lsb_microamps as i32
3555 );
3556 assert_eq!(
3557 pamoja_ina219_current_lsb(solar),
3558 crate::sensors::pamoja_ina219_minimum_current_lsb_microamps(3_200_000)
3559 );
3560 assert_ne!(pamoja_ina219_calibration_word(solar), 0);
3561
3562 let mut battery = ptr::null_mut();
3563 let settings = PamojaIna226Settings {
3564 shunt_milliohms: 2,
3565 max_microamps: 20_000_000,
3566 ..pamoja_ina226_settings_default()
3567 };
3568 assert_eq!(
3569 pamoja_ina226_new(bus, 0x45, settings, &mut battery),
3570 PamojaStatus::Ok
3571 );
3572 let mut reading = std::mem::zeroed::<PamojaIna226Reading>();
3573 assert_eq!(
3574 pamoja_ina226_measure(battery, &mut reading),
3575 PamojaStatus::Ok
3576 );
3577 assert_eq!(reading.bus_microvolts, 3_300_000);
3578 assert!((reading.current_amps + 10.0).abs() < 0.001);
3579 let mut die = PamojaIna226DieId {
3580 device: 0,
3581 revision: 0,
3582 };
3583 assert!(pamoja_ina226_identity(battery, &mut die));
3584 assert_eq!(die.device, ina226::DEVICE_ID);
3585
3586 pamoja_ina219_free(solar);
3587 pamoja_ina226_free(battery);
3588 pamoja_i2c_bus_free(bus);
3589 }
3590 }
3591
3592 #[test]
3593 fn an_ads1115_converts_at_the_range_its_twin_was_built_for() {
3594 unsafe {
3595 let wide = ads1115::Pga::Fsr4_096.code();
3596 let bus = bus_with(pamoja_ads1115_sim_reporting(0x48, wide, 3.0));
3597 let mut adc = ptr::null_mut();
3598 let settings = PamojaAds1115Settings {
3599 pga: wide,
3600 ..pamoja_ads1115_settings_default()
3601 };
3602 assert_eq!(
3603 pamoja_ads1115_new(bus, 0x48, settings, &mut adc),
3604 PamojaStatus::Ok
3605 );
3606 let mut sample = std::mem::zeroed::<PamojaAds1115Sample>();
3607 assert_eq!(pamoja_ads1115_sample(adc, &mut sample), PamojaStatus::Ok);
3608 assert!((sample.volts - 3.0).abs() < 0.001);
3609 assert_eq!(sample.pga, wide);
3610 assert_eq!(
3611 pamoja_ads1115_sample_input(adc, ads1115::Mux::Ain3Gnd.code(), &mut sample),
3612 PamojaStatus::Ok
3613 );
3614 let mut config = std::mem::zeroed::<PamojaAds1115Config>();
3615 assert_eq!(pamoja_ads1115_config(adc, &mut config), PamojaStatus::Ok);
3616 assert_eq!(
3617 config.mux,
3618 ads1115::Mux::Ain0Ain1.code(),
3619 "one input once, then back"
3620 );
3621 pamoja_ads1115_free(adc);
3622 pamoja_i2c_bus_free(bus);
3623 }
3624 }
3625
3626 #[test]
3627 fn the_sensirion_parts_answer_their_commands() {
3628 unsafe {
3629 let bus = bus_with(pamoja_sht3x_sim_reporting(0x45, -12.0, 88.5));
3630 attach(bus, pamoja_scd4x_sim_part());
3631
3632 let mut sht = ptr::null_mut();
3633 assert_eq!(
3634 pamoja_sht3x_new(bus, 0x45, 7, &mut sht),
3635 PamojaStatus::InvalidArgument
3636 );
3637 assert_eq!(pamoja_sht3x_new(bus, 0x45, 2, &mut sht), PamojaStatus::Ok);
3638 let mut status = std::mem::zeroed::<PamojaSht3xStatus>();
3639 assert!(!pamoja_sht3x_last_status(sht, &mut status));
3640 let mut reading = std::mem::zeroed::<PamojaSht3xMeasurement>();
3641 assert_eq!(pamoja_sht3x_measure(sht, &mut reading), PamojaStatus::Ok);
3642 assert!((reading.celsius + 12.0).abs() < 0.003);
3643 assert!(pamoja_sht3x_last_status(sht, &mut status));
3644 assert_eq!(pamoja_sht3x_heater_on(sht), PamojaStatus::Ok);
3645
3646 let held = pamoja_i2c_bus_part(bus, 0x45);
3647 assert_eq!(pamoja_i2c_part_received_count(held), 4);
3648 let first = pamoja_i2c_part_received(held, 0);
3649 let written = std::slice::from_raw_parts(
3650 crate::pamoja_buffer_data(first),
3651 crate::pamoja_buffer_len(first),
3652 );
3653 assert_eq!(
3654 written,
3655 &sht3x::command::SOFT_RESET.to_be_bytes()[..],
3656 "the reset went first"
3657 );
3658 crate::pamoja_buffer_free(first);
3659 pamoja_i2c_part_free(held);
3660
3661 let mut co2 = ptr::null_mut();
3662 assert_eq!(pamoja_scd4x_new(bus, &mut co2), PamojaStatus::Ok);
3663 let mut serial = 0;
3664 assert!(!pamoja_scd4x_serial(co2, &mut serial));
3665 let mut air = std::mem::zeroed::<PamojaScd4xMeasurement>();
3666 assert_eq!(pamoja_scd4x_measure(co2, &mut air), PamojaStatus::Ok);
3667 assert_eq!(air.co2_ppm, PAMOJA_SCD4X_SIM_CO2_PPM);
3668 assert!(pamoja_scd4x_serial(co2, &mut serial));
3669 assert_eq!(serial, PAMOJA_SCD4X_SIM_SERIAL);
3670 let mut ready = false;
3671 assert_eq!(pamoja_scd4x_poll_ready(co2, &mut ready), PamojaStatus::Ok);
3672 assert!(ready);
3673 assert_eq!(
3674 pamoja_scd4x_set_sensor_altitude(co2, 1_600),
3675 PamojaStatus::Ok
3676 );
3677
3678 pamoja_sht3x_free(sht);
3679 pamoja_scd4x_free(co2);
3680 pamoja_i2c_bus_free(bus);
3681 }
3682 }
3683
3684 #[test]
3685 fn a_thermometer_reads_the_file_the_kernel_serves() {
3686 let raw = ds18b20::temperature_from_celsius(21.5, ds18b20::Resolution::Bits12);
3687 let bytes = ds18b20::Scratchpad::new(raw, ds18b20::Resolution::Bits12, 75, -10).to_bytes();
3688 let hex: Vec<String> = bytes.iter().map(|byte| format!("{byte:02x}")).collect();
3689 let text = format!("{} : crc={:02x} YES\n", hex.join(" "), bytes[8]);
3690
3691 let dir = std::env::temp_dir().join(format!("pamoja-ffi-w1-{}", std::process::id()));
3692 let device = dir.join("28-000005e2fdc3");
3693 std::fs::create_dir_all(&device).unwrap();
3694 std::fs::write(device.join("w1_slave"), &text).unwrap();
3695
3696 unsafe {
3697 let devices = CString::new(dir.to_string_lossy().into_owned()).unwrap();
3698 let mut found = ptr::null_mut();
3699 assert_eq!(
3700 pamoja_ds18b20_discover(devices.as_ptr(), &mut found),
3701 PamojaStatus::Ok
3702 );
3703 assert_eq!(pamoja_ds18b20_thermometers_len(found), 1);
3704 let probe = pamoja_ds18b20_thermometers_get(found, 0);
3705 assert!(pamoja_ds18b20_thermometers_get(found, 1).is_null());
3706 pamoja_ds18b20_thermometers_free(found);
3707
3708 let mut reading = std::mem::zeroed::<PamojaDs18b20Reading>();
3709 assert_eq!(
3710 pamoja_ds18b20_thermometer_read(probe, &mut reading),
3711 PamojaStatus::Ok
3712 );
3713 assert_eq!(reading.micro_celsius, 21_500_000);
3714 let path = pamoja_ds18b20_thermometer_path(probe);
3715 let path_text = CStr::from_ptr(crate::pamoja_string_data(path))
3716 .to_string_lossy()
3717 .into_owned();
3718 assert!(path_text.ends_with("w1_slave"), "{path_text}");
3719 crate::pamoja_string_free(path);
3720 let serial = pamoja_ds18b20_thermometer_serial(probe);
3721 assert_eq!(
3722 CStr::from_ptr(crate::pamoja_string_data(serial)).to_str(),
3723 Ok("000005e2fdc3")
3724 );
3725 crate::pamoja_string_free(serial);
3726 assert!(pamoja_ds18b20_thermometer_serial(ptr::null()).is_null());
3727 pamoja_ds18b20_thermometer_free(probe);
3728
3729 let text = CString::new(text).unwrap();
3730 assert_eq!(
3731 crate::sensors::pamoja_ds18b20_parse_w1_slave(text.as_ptr(), &mut reading),
3732 PamojaStatus::Ok
3733 );
3734
3735 let serial = CString::new("0000deadbeef").unwrap();
3736 let mut missing = ptr::null_mut();
3737 assert_eq!(
3738 pamoja_ds18b20_thermometer_new(serial.as_ptr(), &mut missing),
3739 PamojaStatus::Ok
3740 );
3741 assert_eq!(
3742 pamoja_ds18b20_thermometer_read(missing, &mut reading),
3743 PamojaStatus::Io
3744 );
3745 assert!(
3746 last_error().starts_with("reading the w1_slave file"),
3747 "{}",
3748 last_error()
3749 );
3750 pamoja_ds18b20_thermometer_free(missing);
3751 }
3752 std::fs::remove_dir_all(&dir).unwrap();
3753 }
3754}