#![no_std]
#![doc = include_str!("../README.md")]
pub mod calibration;
pub mod error;
pub mod gas;
pub use calibration::rs_r0_to_ppm;
pub use error::Error;
pub use gas::{Channel, Gas};
use calibration::rs_r0_to_ppm as convert;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub struct ChannelReading {
pub red: f32,
pub ox: f32,
pub nh3: f32,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub struct GasReading {
pub gas: Gas,
pub ppm: f32,
}
pub fn voltage_to_rs_r0(v_adc: f32, v_cc: f32, r_load: f32, r0: f32) -> Result<f32, Error> {
if v_adc <= 0.0 || v_adc > v_cc {
return Err(Error::InvalidVoltage);
}
if r_load <= 0.0 || r0 <= 0.0 {
return Err(Error::InvalidLoadResistance);
}
let rs = r_load * (v_cc - v_adc) / v_adc;
Ok(rs / r0)
}
pub fn measure(reading: &ChannelReading, gas: Gas) -> Result<GasReading, Error> {
let rs_r0 = match gas.channel() {
Channel::Red => reading.red,
Channel::Ox => reading.ox,
Channel::Nh3 => reading.nh3,
};
let ppm = convert(gas, rs_r0)?;
Ok(GasReading { gas, ppm })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_voltage_to_rs_r0() {
let ratio = voltage_to_rs_r0(1.65, 3.3, 10_000.0, 10_000.0).unwrap();
assert!((ratio - 1.0).abs() < 0.01, "expected 1.0, got {ratio}");
}
#[test]
fn test_voltage_to_rs_r0_zero_voltage() {
let result = voltage_to_rs_r0(0.0, 3.3, 10_000.0, 10_000.0);
assert_eq!(result, Err(Error::InvalidVoltage));
}
#[test]
fn test_voltage_exceeds_vcc() {
let result = voltage_to_rs_r0(3.5, 3.3, 10_000.0, 10_000.0);
assert_eq!(result, Err(Error::InvalidVoltage));
}
#[test]
fn test_full_pipeline_co() {
let v_adc = 3.3 * 56_000.0 / (125_000.0 + 56_000.0);
let ratio = voltage_to_rs_r0(v_adc, 3.3, 56_000.0, 500_000.0).unwrap();
let reading = ChannelReading {
red: ratio,
ox: 1.0,
nh3: 1.0,
};
let result = measure(&reading, Gas::CarbonMonoxide).unwrap();
assert!(
(result.ppm - 100.0).abs() < 20.0,
"expected ~100 ppm CO, got {}",
result.ppm
);
}
}