1#![no_std]
2#![doc = include_str!("../README.md")]
3
4pub mod calibration;
5pub mod error;
6pub mod gas;
7
8pub use calibration::rs_r0_to_ppm;
9pub use error::Error;
10pub use gas::{Channel, Gas};
11
12use calibration::rs_r0_to_ppm as convert;
13
14#[derive(Debug, Clone, Copy)]
16#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
17pub struct ChannelReading {
18 pub red: f32,
20 pub ox: f32,
22 pub nh3: f32,
24}
25
26#[derive(Debug, Clone, Copy)]
28#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
29pub struct GasReading {
30 pub gas: Gas,
32 pub ppm: f32,
34}
35
36pub fn voltage_to_rs_r0(v_adc: f32, v_cc: f32, r_load: f32, r0: f32) -> Result<f32, Error> {
46 if v_adc <= 0.0 || v_adc > v_cc {
47 return Err(Error::InvalidVoltage);
48 }
49 if r_load <= 0.0 || r0 <= 0.0 {
50 return Err(Error::InvalidLoadResistance);
51 }
52 let rs = r_load * (v_cc - v_adc) / v_adc;
53 Ok(rs / r0)
54}
55
56pub fn measure(reading: &ChannelReading, gas: Gas) -> Result<GasReading, Error> {
61 let rs_r0 = match gas.channel() {
62 Channel::Red => reading.red,
63 Channel::Ox => reading.ox,
64 Channel::Nh3 => reading.nh3,
65 };
66 let ppm = convert(gas, rs_r0)?;
67 Ok(GasReading { gas, ppm })
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_voltage_to_rs_r0() {
76 let ratio = voltage_to_rs_r0(1.65, 3.3, 10_000.0, 10_000.0).unwrap();
77 assert!((ratio - 1.0).abs() < 0.01, "expected 1.0, got {ratio}");
78 }
79
80 #[test]
81 fn test_voltage_to_rs_r0_zero_voltage() {
82 let result = voltage_to_rs_r0(0.0, 3.3, 10_000.0, 10_000.0);
83 assert_eq!(result, Err(Error::InvalidVoltage));
84 }
85
86 #[test]
87 fn test_voltage_exceeds_vcc() {
88 let result = voltage_to_rs_r0(3.5, 3.3, 10_000.0, 10_000.0);
89 assert_eq!(result, Err(Error::InvalidVoltage));
90 }
91
92 #[test]
93 fn test_full_pipeline_co() {
94 let v_adc = 3.3 * 56_000.0 / (125_000.0 + 56_000.0);
97 let ratio = voltage_to_rs_r0(v_adc, 3.3, 56_000.0, 500_000.0).unwrap();
98 let reading = ChannelReading {
99 red: ratio,
100 ox: 1.0,
101 nh3: 1.0,
102 };
103 let result = measure(&reading, Gas::CarbonMonoxide).unwrap();
104 assert!(
105 (result.ppm - 100.0).abs() < 20.0,
106 "expected ~100 ppm CO, got {}",
107 result.ppm
108 );
109 }
110}