#![no_std]
#![allow(non_snake_case, clippy::needless_doctest_main)]
#![feature(unsize)]
use embedded_hal::blocking::i2c::{Write, WriteRead};
pub mod rtd;
mod storage;
pub use rtd::{Rtd, RtdType, Wires};
const PH_TEMP_C: f32 = -0.05694; const DISCRETE_PH_JUMP_THRESH: f32 = 0.2;
const DISCRETE_ORP_JUMP_THRESH: f32 = 30.;
const PH_STD: f32 = 0.1;
const ORP_STD: f32 = 10.;
const ADC_ADDR_1: u8 = 0x48;
const ADC_ADDR_2: u8 = 0x49;
const CFG_REG: u8 = 0x1;
const CONV_REG: u8 = 0x0;
const PH_ORP_CMD: u16 = 0b1000_0101_1000_0000;
const T_CMD: u16 = 0b1110_0101_1000_0000;
#[derive(Debug, Clone, Copy)]
pub enum CalSlot {
One,
Two,
Three,
}
#[derive(Debug, Clone, Copy)]
pub enum TempSource {
OnBoard,
OffBoard(f32),
}
#[derive(Debug, Clone, Copy)]
pub struct CalPt {
pub V: f32, pub pH: f32,
pub T: f32, }
impl CalPt {
pub fn new(V: f32, pH: f32, T: f32) -> Self {
Self { V, pH, T }
}
}
#[derive(Debug, Clone, Copy)]
pub struct CalPtOrp {
pub V: f32, pub ORP: f32, }
impl CalPtOrp {
pub fn new(V: f32, ORP: f32) -> Self {
Self { V, ORP }
}
}
#[derive(Debug, Clone, Copy)]
pub struct CalPtT {
pub V: f32, pub T: f32, }
impl CalPtT {
pub fn new(V: f32, T: f32) -> Self {
Self { V, T }
}
}
#[derive(Debug, Clone, Copy)]
pub struct CalPtEc {
pub reading: f32, pub ec: f32, pub T: f32, }
impl CalPtEc {
pub fn new(reading: f32, ec: f32, T: f32) -> Self {
Self { reading, ec, T }
}
}
pub struct PhSensor {
pub addr: u8,
pub cal_1: CalPt,
pub cal_2: CalPt,
pub cal_3: Option<CalPt>,
}
impl PhSensor {
pub fn new() -> Self {
Self {
addr: ADC_ADDR_1,
cal_1: CalPt::new(0., 7., 23.),
cal_2: CalPt::new(0.17, 4., 23.),
cal_3: None,
}
}
pub fn new_alt_addr() -> Self {
Self {
addr: ADC_ADDR_2,
..Self::new()
}
}
pub fn cal_nitrate_default(&mut self) {
self.cal_1 = CalPt::new(0.25, -2. * 62_000., 23.);
self.cal_2 = CalPt::new(0.4, -5. * 62_000., 23.);
self.cal_3 = None;
}
pub fn cal_phosphate_default(&mut self) {
self.cal_1 = CalPt::new(0.25, -2. * 62_000., 23.);
self.cal_2 = CalPt::new(0.4, -5. * 62_000., 23.);
self.cal_3 = None;
}
pub fn cal_potassium_default(&mut self) {
self.cal_1 = CalPt::new(0.25, -2. * 62_000., 23.);
self.cal_2 = CalPt::new(0.4, -5. * 62_000., 23.);
self.cal_3 = None;
}
pub fn read<I2C, E>(&mut self, t: TempSource, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
let T = match t {
TempSource::OnBoard => {
temp_from_voltage(voltage_from_adc(take_reading(self.addr, T_CMD, i2c)))
}
TempSource::OffBoard(t_) => t_,
};
let pH = ph_from_voltage(
voltage_from_adc(take_reading(self.addr, PH_ORP_CMD, i2c)),
T,
&self.cal_1,
&self.cal_2,
&self.cal_3,
);
pH
}
pub fn read_voltage<I2C, E>(&mut self, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
voltage_from_adc(take_reading(self.addr, PH_ORP_CMD, i2c))
}
pub fn read_temp<I2C, E>(&mut self, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
temp_from_voltage(voltage_from_adc(take_reading(self.addr, T_CMD, i2c)))
}
pub fn calibrate<I2C, E>(
&mut self,
slot: CalSlot,
pH: f32,
t: TempSource,
i2c: &mut I2C,
) -> (f32, f32)
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
let T = match t {
TempSource::OnBoard => {
temp_from_voltage(voltage_from_adc(take_reading(self.addr, T_CMD, i2c)))
}
TempSource::OffBoard(t_) => t_,
};
let V = voltage_from_adc(take_reading(self.addr, PH_ORP_CMD, i2c));
let pt = CalPt::new(V, pH, T);
match slot {
CalSlot::One => self.cal_1 = pt,
CalSlot::Two => self.cal_2 = pt,
CalSlot::Three => self.cal_3 = Some(pt),
}
(V, T)
}
pub fn calibrate_all(&mut self, pt0: CalPt, pt1: CalPt, pt2: Option<CalPt>) {
self.cal_1 = pt0;
self.cal_2 = pt1;
self.cal_3 = pt2;
}
pub fn reset_calibration(&mut self) {
self.cal_1 = CalPt::new(0., 7., 25.);
self.cal_2 = CalPt::new(0.17, 4., 25.);
self.cal_3 = None;
}
}
pub struct OrpSensor {
pub addr: u8,
pub cal: CalPtOrp,
}
impl OrpSensor {
pub fn new() -> Self {
Self {
addr: ADC_ADDR_1,
cal: CalPtOrp::new(0.4, 400.),
}
}
pub fn new_alt_addr() -> Self {
Self {
addr: ADC_ADDR_2,
..Self::new()
}
}
pub fn read<I2C, E>(&mut self, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
let orp = orp_from_voltage(
voltage_from_adc(take_reading(self.addr, PH_ORP_CMD, i2c)),
&self.cal,
);
orp
}
pub fn read_voltage<I2C, E>(&mut self, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
voltage_from_adc(take_reading(self.addr, PH_ORP_CMD, i2c))
}
pub fn read_temp<I2C, E>(&mut self, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
temp_from_voltage(voltage_from_adc(take_reading(self.addr, T_CMD, i2c)))
}
pub fn calibrate<I2C, E>(&mut self, ORP: f32, i2c: &mut I2C) -> f32
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
let V = voltage_from_adc(take_reading(self.addr, PH_ORP_CMD, i2c));
self.cal = CalPtOrp::new(V, ORP);
V
}
pub fn calibrate_all(&mut self, pt: CalPtOrp) {
self.cal = pt;
}
pub fn reset_calibration(&mut self) {
self.cal = CalPtOrp::new(0.4, 400.);
}
}
#[derive(Copy, Clone, Debug)]
pub enum SensorError {
Bus,
NotConnected,
BadMeasurement,
}
#[derive(Debug, Clone)]
pub struct Readings {
pub T: Result<f32, SensorError>,
pub pH: Result<f32, SensorError>,
pub ORP: Result<f32, SensorError>,
pub ec: Result<f32, SensorError>,
}
pub fn voltage_from_adc(digi: i16) -> f32 {
let vref = 2.048;
(digi as f32 / 32_768.) * vref
}
fn lg(pt0: (f32, f32), pt1: (f32, f32), pt2: (f32, f32), X: f32) -> f32 {
let mut result = 0.;
let x = [pt0.0, pt1.0, pt2.0];
let y = [pt0.1, pt1.1, pt2.1];
for j in 0..3 {
let mut c = 1.;
for i in 0..3 {
if j == i {
continue;
}
c *= (X - x[i]) / (x[j] - x[i]);
}
result += y[j] * c;
}
result
}
fn ph_from_voltage(V: f32, T: f32, cal_0: &CalPt, cal_1: &CalPt, cal_2: &Option<CalPt>) -> f32 {
let T_diff = T - cal_0.T;
let T_comp = PH_TEMP_C * T_diff;
match cal_2 {
Some(c2) => {
let result = lg((cal_0.V, cal_0.pH), (cal_1.V, cal_1.pH), (c2.V, c2.pH), V);
result + T_comp * V
}
None => {
let a = (cal_1.pH - cal_0.pH) / (cal_1.V - cal_0.V);
let b = cal_1.pH - a * cal_1.V;
(a + T_comp) * V + b
}
}
}
fn orp_from_voltage(V: f32, cal: &CalPtOrp) -> f32 {
let a = cal.ORP / cal.V;
let b = cal.ORP - a * cal.V;
a * V + b
}
pub fn temp_from_voltage(V: f32) -> f32 {
100. * V - 60.
}
fn take_reading<I2C, E>(addr: u8, cmd: u16, i2c: &mut I2C) -> i16
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
let mut result_buf: [u8; 2] = [0, 0];
i2c.write(addr, &[CFG_REG, (cmd >> 8) as u8, cmd as u8])
.ok();
let mut converting = true;
let mut buf = [0, 0];
while converting {
i2c.write_read(addr, &[CFG_REG], &mut buf).ok();
converting = buf[0] >> 7 == 0;
}
i2c.write_read(addr, &[CONV_REG], &mut result_buf).ok();
i16::from_be_bytes([result_buf[0], result_buf[1]])
}