use embedded_hal::blocking::delay::DelayUs;
use rppal::i2c::{self, Error as I2CError, I2c};
#[macro_use]
extern crate log;
use thiserror::Error;
mod regs;
const STD_PROCESSING_DELAY_MICROS: u16 = 125;
const SENSOR_START_ADDR: u16 = 0x36;
const SENSOR_END_ADDR: u16 = 0x39;
pub type Result<T> = std::result::Result<T, SoilSensErr>;
pub struct SoilSensor<D: DelayUs<u16>> {
channel: I2c,
delay: D,
}
impl<D: DelayUs<u16>> SoilSensor<D> {
pub fn init(mut delay: D) -> Result<Self> {
let mut channel = i2c::I2c::new()?;
let mut hw_found: bool = false;
for adr in SENSOR_START_ADDR..=SENSOR_END_ADDR {
channel.set_slave_address(adr)?;
debug!("Connecting to adr: {:#X}", adr);
match init::channel_init(&mut delay, &mut channel) {
Ok(()) => {
hw_found = true;
break;
}
Err(SoilSensErr::HardwareMismatch(..)) => continue,
Err(SoilSensErr::InvalidSlaveAddress(..)) => continue,
Err(e) => return Err(e),
}
}
if !hw_found {
return Err(SoilSensErr::HwNotFound);
}
Ok(SoilSensor { channel, delay })
}
pub fn init_with_channel(mut delay: D, channel: I2c) -> Result<Self> {
let mut channel = channel;
init::channel_init(&mut delay, &mut channel)?;
Ok(SoilSensor { channel, delay })
}
pub fn get_temp(&mut self) -> Result<f32> {
let l_reg = regs::base::SEESAW_STATUS_BASE;
let h_reg = regs::func::SEESAW_STATUS_TEMP;
let delay = STD_PROCESSING_DELAY_MICROS;
let mut buffer = [0u8; 4];
self.read(l_reg, h_reg, &mut buffer[..], delay)?;
let tmp_val = i32::from_be_bytes(buffer) as f32;
let temp_celsius = (1.0 / (1u32 << 16) as f32) * tmp_val;
Ok(temp_celsius)
}
pub fn get_capacitance(&mut self) -> Result<u16> {
let l_reg: u8 = regs::base::SEESAW_TOUCH_BASE;
let h_reg: u8 = regs::touch::SEESAW_TOUCH_CHANNEL_OFFSET;
let mut buff = [0u8; 2];
let mut retry_counter = 0;
while retry_counter < 3 {
self.delay.delay_us(1000);
if let Err(e) = self.read(l_reg, h_reg, &mut buff, 5000) {
debug!("Error reading capacitance: {}. Retry: {}", e, retry_counter + 1);
retry_counter += 1;
continue;
}
let cap = u16::from_be_bytes(buff);
if cap < u16::max_value() {
return Ok(cap);
}
}
Err(SoilSensErr::MoistureReadErr)
}
fn read(&mut self, reg_low: u8, reg_high: u8, buff: &mut [u8], delay_us: u16) -> Result<()> {
self.channel.write(&[reg_low, reg_high])?;
self.delay.delay_us(delay_us);
self.channel.read(buff)?;
debug!("Received: {:?}", buff);
Ok(())
}
}
mod init {
use super::*;
pub fn channel_init<D: DelayUs<u16>>(delay: &mut D, chan: &mut I2c) -> Result<()> {
match try_read_chan(chan, delay) {
Ok(resp) => {
debug!("Found device with HW id: {}", resp);
if resp != regs::SEESAW_HW_ID_CODE {
return Err(SoilSensErr::HardwareMismatch(regs::SEESAW_HW_ID_CODE, resp));
} else {
debug!("HW ID match: exp {}, got: {}", resp, regs::SEESAW_HW_ID_CODE);
return Ok(());
}
}
Err(SoilSensErr::I2C {
source: I2CError::InvalidSlaveAddress(adr),
}) => {
debug!("Invalid address: {}", adr);
return Err(SoilSensErr::InvalidSlaveAddress(adr));
}
Err(e) => {
debug!("Unexpected err: {}", e);
return Err(e);
}
}
}
fn try_read_chan<D: DelayUs<u16>>(chan: &mut I2c, delay: &mut D) -> Result<u8> {
let reg_high = regs::base::SEESAW_STATUS_BASE;
let reg_low = regs::func::SEESAW_STATUS_HW_ID;
chan.write(&[reg_high, reg_low])?;
let mut buffer = [0];
delay.delay_us(STD_PROCESSING_DELAY_MICROS);
chan.read(&mut buffer)?;
debug!("Got: {:?}", buffer);
Ok(buffer[0])
}
}
#[derive(Debug, Error)]
pub enum SoilSensErr {
#[error("Couldn't get a valid reading from the moisture sensor.")]
MoistureReadErr,
#[error("Couldn't connect to the sensor.")]
HwNotFound,
#[error("Invalid Hardware ID. Expected {0}, got {1}")]
HardwareMismatch(u8, u8),
#[error("invalid slave address: {0:#X}")]
InvalidSlaveAddress(u16),
#[error("I2C connection error. {source}")]
I2C {
#[from]
source: i2c::Error,
},
}