splitting wip
This commit is contained in:
parent
c429c829b4
commit
fc1991523a
@ -1,27 +1,42 @@
|
||||
use crate::config::{NetworkConfig, PlantControllerConfig};
|
||||
use crate::hal::{CONSECUTIVE_WATERING_PLANT, LAST_WATERING_TIMESTAMP, LOW_VOLTAGE_DETECTED, PLANT_COUNT, RESTART_TO_CONF};
|
||||
use crate::hal::PLANT_COUNT;
|
||||
use crate::log::{log, LogMessage};
|
||||
use crate::STAY_ALIVE;
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use chrono::{DateTime, Utc};
|
||||
use embedded_svc::ipv4::IpInfo;
|
||||
use embedded_svc::wifi::{AccessPointConfiguration, AuthMethod, ClientConfiguration, Configuration};
|
||||
use embedded_svc::mqtt::client::QoS::{AtLeastOnce, ExactlyOnce};
|
||||
use embedded_svc::wifi::{AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration};
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_hal::gpio::PinDriver;
|
||||
use esp_idf_hal::gpio::{Level, PinDriver};
|
||||
use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
|
||||
use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
|
||||
use esp_idf_svc::wifi::EspWifi;
|
||||
use esp_idf_sys::{esp_spiffs_info, vTaskDelay};
|
||||
use serde::Serialize;
|
||||
use std::ffi::CString;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::result::Result::Ok as OkStd;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use embedded_svc::mqtt::client::QoS::{AtLeastOnce, ExactlyOnce};
|
||||
use esp_idf_hal::i2c::I2cDriver;
|
||||
use serde::Serialize;
|
||||
use crate::STAY_ALIVE;
|
||||
use esp_idf_svc::sntp;
|
||||
use esp_idf_svc::sntp::SyncStatus;
|
||||
use esp_idf_svc::systime::EspSystemTime;
|
||||
|
||||
|
||||
#[link_section = ".rtc.data"]
|
||||
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
|
||||
#[link_section = ".rtc.data"]
|
||||
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
|
||||
#[link_section = ".rtc.data"]
|
||||
static mut LOW_VOLTAGE_DETECTED: bool = false;
|
||||
#[link_section = ".rtc.data"]
|
||||
static mut RESTART_TO_CONF: bool = false;
|
||||
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct FileInfo {
|
||||
@ -61,8 +76,84 @@ impl ESP<'_> {
|
||||
const CONFIG_FILE: &'static str = "/spiffs/config.cfg";
|
||||
const BASE_PATH: &'static str = "/spiffs";
|
||||
|
||||
pub(crate) fn mode_override_pressed(&mut self) -> bool {
|
||||
self.boot_button.get_level() == Level::Low
|
||||
}
|
||||
pub(crate) fn sntp(&mut self, max_wait_ms: u32) -> anyhow::Result<DateTime<Utc>> {
|
||||
let sntp = sntp::EspSntp::new_default()?;
|
||||
let mut counter = 0;
|
||||
while sntp.get_sync_status() != SyncStatus::Completed {
|
||||
self.delay.delay_ms(100);
|
||||
counter += 100;
|
||||
if counter > max_wait_ms {
|
||||
bail!("Reached sntp timeout, aborting")
|
||||
}
|
||||
}
|
||||
self.time()
|
||||
}
|
||||
pub(crate) fn time(&mut self) -> anyhow::Result<DateTime<Utc>> {
|
||||
let time = EspSystemTime {}.now().as_millis();
|
||||
let smaller_time = time as i64;
|
||||
let local_time = DateTime::from_timestamp_millis(smaller_time)
|
||||
.ok_or(anyhow!("could not convert timestamp"))?;
|
||||
anyhow::Ok(local_time)
|
||||
}
|
||||
pub(crate) fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
|
||||
self.wifi_driver.start_scan(
|
||||
&ScanConfig {
|
||||
scan_type: ScanType::Passive(Duration::from_secs(5)),
|
||||
show_hidden: false,
|
||||
..Default::default()
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
anyhow::Ok(self.wifi_driver.get_scan_result()?)
|
||||
}
|
||||
|
||||
pub(crate) fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
|
||||
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
|
||||
DateTime::from_timestamp_millis(ts)
|
||||
}
|
||||
pub(crate) fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>) {
|
||||
unsafe {
|
||||
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
|
||||
}
|
||||
}
|
||||
pub(crate) fn set_low_voltage_in_cycle(&mut self) {
|
||||
unsafe {
|
||||
LOW_VOLTAGE_DETECTED = true;
|
||||
}
|
||||
}
|
||||
pub(crate) fn clear_low_voltage_in_cycle(&mut self) {
|
||||
unsafe {
|
||||
LOW_VOLTAGE_DETECTED = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn low_voltage_in_cycle(&mut self) -> bool {
|
||||
unsafe {
|
||||
LOW_VOLTAGE_DETECTED
|
||||
}
|
||||
}
|
||||
pub(crate) fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
|
||||
unsafe {
|
||||
CONSECUTIVE_WATERING_PLANT[plant] = count;
|
||||
}
|
||||
}
|
||||
pub(crate) fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
|
||||
unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
|
||||
}
|
||||
pub(crate) fn get_restart_to_conf(&mut self) -> bool {
|
||||
unsafe { RESTART_TO_CONF }
|
||||
}
|
||||
pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) {
|
||||
unsafe {
|
||||
RESTART_TO_CONF = to_conf;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wifi_ap(&mut self) -> anyhow::Result<()> {
|
||||
let ssid = match self.get_config(){
|
||||
let ssid = match self.load_config(){
|
||||
Ok(config) => {
|
||||
config.network.ap_ssid.clone()
|
||||
}
|
||||
@ -145,12 +236,12 @@ impl ESP<'_> {
|
||||
log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}"));
|
||||
anyhow::Ok(address)
|
||||
}
|
||||
pub(crate) fn get_config(&mut self) -> anyhow::Result<PlantControllerConfig> {
|
||||
pub(crate) fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> {
|
||||
let cfg = File::open(Self::CONFIG_FILE)?;
|
||||
let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
|
||||
anyhow::Ok(config)
|
||||
}
|
||||
pub(crate) fn set_config(&mut self, config: &PlantControllerConfig) -> anyhow::Result<()> {
|
||||
pub(crate) fn save_config(&mut self, config: &PlantControllerConfig) -> anyhow::Result<()> {
|
||||
let mut cfg = File::create(Self::CONFIG_FILE)?;
|
||||
serde_json::to_writer(&mut cfg, &config)?;
|
||||
println!("Wrote config config {:?}", config);
|
||||
@ -227,7 +318,7 @@ impl ESP<'_> {
|
||||
filename: file.file_name().into_string().unwrap(),
|
||||
size: file
|
||||
.metadata()
|
||||
.and_then(|it| anyhow::Result::Ok(it.len()))
|
||||
.and_then(|it| Ok(it.len()))
|
||||
.unwrap_or_default()
|
||||
as usize,
|
||||
};
|
||||
|
99
rust/src/hal/initial_hal.rs
Normal file
99
rust/src/hal/initial_hal.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, Sensor};
|
||||
use anyhow::{bail, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use esp_idf_hal::gpio::{InputOutput, PinDriver};
|
||||
use crate::config::{BoardHardware, PlantControllerConfig};
|
||||
use crate::hal::battery::{BatteryInteraction, BatteryMonitor};
|
||||
use crate::hal::esp::ESP;
|
||||
|
||||
pub struct Initial<'a> {
|
||||
pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
pub(crate) esp: ESP<'a>,
|
||||
pub(crate) config: PlantControllerConfig,
|
||||
}
|
||||
|
||||
impl BoardInteraction<'_> for Initial<'_> {
|
||||
fn get_esp<'a>(&mut self) -> &mut ESP<'a> {
|
||||
&mut self.esp
|
||||
}
|
||||
|
||||
fn get_config(&mut self) -> &PlantControllerConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
fn get_battery_monitor(&mut self) -> Box<dyn BatteryInteraction> {
|
||||
let v = BatteryMonitor::Disabled {
|
||||
|
||||
};
|
||||
Box::new(v) as Box<dyn BatteryInteraction>
|
||||
}
|
||||
|
||||
fn set_charge_indicator(&mut self, charging: bool) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
||||
deep_sleep(duration_in_ms)
|
||||
}
|
||||
|
||||
fn get_backup_info(&mut self) -> Result<BackupHeader> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn get_backup_config(&mut self) -> Result<Vec<u8>> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn backup_config(&mut self, bytes: &[u8]) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn is_day(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn water_temperature_c(&mut self) -> Result<f32> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn tank_sensor_voltage(&mut self) -> Result<f32> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
fn light(&mut self, enable: bool) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn pump(&mut self, plant: usize, enable: bool) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
fn fault(&mut self, plant: usize, _enable: bool) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn general_fault(&mut self, enable: bool) {
|
||||
let _ = self.general_fault.set_state(enable.into());
|
||||
}
|
||||
|
||||
fn factory_reset(&mut self) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
fn test_pump(&mut self, plant: usize) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
|
||||
fn test(&mut self) -> Result<()> {
|
||||
bail!("Please configure board revision")
|
||||
}
|
||||
}
|
1543
rust/src/hal/mod.rs
1543
rust/src/hal/mod.rs
File diff suppressed because it is too large
Load Diff
621
rust/src/hal/v3_hal.rs
Normal file
621
rust/src/hal/v3_hal.rs
Normal file
@ -0,0 +1,621 @@
|
||||
use crate::hal::esp::ESP;
|
||||
use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, V3Constants, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25};
|
||||
use crate::log::{log, LogMessage};
|
||||
use anyhow::{anyhow, bail, Ok, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use ds18b20::Ds18b20;
|
||||
use ds323x::{DateTimeAccess, Ds323x};
|
||||
use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_bus::i2c::MutexDevice;
|
||||
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, PinDriver, Pull};
|
||||
use esp_idf_hal::i2c::I2cDriver;
|
||||
use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex};
|
||||
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
||||
use one_wire_bus::OneWire;
|
||||
use plant_ctrl2::sipo::ShiftRegister40;
|
||||
use std::result::Result::Ok as OkStd;
|
||||
use esp_idf_hal::adc::{attenuation, Resolution};
|
||||
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
|
||||
use crate::config::PlantControllerConfig;
|
||||
use crate::hal::battery::{BatteryInteraction, BatteryMonitor};
|
||||
|
||||
const PUMP8_BIT: usize = 0;
|
||||
const PUMP1_BIT: usize = 1;
|
||||
const PUMP2_BIT: usize = 2;
|
||||
const PUMP3_BIT: usize = 3;
|
||||
const PUMP4_BIT: usize = 4;
|
||||
const PUMP5_BIT: usize = 5;
|
||||
const PUMP6_BIT: usize = 6;
|
||||
const PUMP7_BIT: usize = 7;
|
||||
const MS_0: usize = 8;
|
||||
const MS_4: usize = 9;
|
||||
const MS_2: usize = 10;
|
||||
const MS_3: usize = 11;
|
||||
const MS_1: usize = 13;
|
||||
const SENSOR_ON: usize = 12;
|
||||
|
||||
const SENSOR_A_1: u8 = 7;
|
||||
const SENSOR_A_2: u8 = 6;
|
||||
const SENSOR_A_3: u8 = 5;
|
||||
const SENSOR_A_4: u8 = 4;
|
||||
const SENSOR_A_5: u8 = 3;
|
||||
const SENSOR_A_6: u8 = 2;
|
||||
const SENSOR_A_7: u8 = 1;
|
||||
const SENSOR_A_8: u8 = 0;
|
||||
|
||||
const SENSOR_B_1: u8 = 8;
|
||||
const SENSOR_B_2: u8 = 9;
|
||||
const SENSOR_B_3: u8 = 10;
|
||||
const SENSOR_B_4: u8 = 11;
|
||||
const SENSOR_B_5: u8 = 12;
|
||||
const SENSOR_B_6: u8 = 13;
|
||||
const SENSOR_B_7: u8 = 14;
|
||||
const SENSOR_B_8: u8 = 15;
|
||||
|
||||
const CHARGING: usize = 14;
|
||||
const AWAKE: usize = 15;
|
||||
|
||||
const FAULT_3: usize = 16;
|
||||
const FAULT_8: usize = 17;
|
||||
const FAULT_7: usize = 18;
|
||||
const FAULT_6: usize = 19;
|
||||
const FAULT_5: usize = 20;
|
||||
const FAULT_4: usize = 21;
|
||||
const FAULT_1: usize = 22;
|
||||
const FAULT_2: usize = 23;
|
||||
|
||||
pub struct V3<'a> {
|
||||
config: PlantControllerConfig,
|
||||
battery_monitor: Box<dyn BatteryInteraction>,
|
||||
esp: ESP<'a>,
|
||||
shift_register: ShiftRegister40<
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
>,
|
||||
shift_register_enable_invert:
|
||||
PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Output>,
|
||||
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
|
||||
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
|
||||
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
main_pump: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
signal_counter: PcntDriver<'a>,
|
||||
one_wire_bus: OneWire<PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>>,
|
||||
rtc:
|
||||
Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
|
||||
eeprom: Eeprom24x<
|
||||
MutexDevice<'a, I2cDriver<'a>>,
|
||||
eeprom24x::page_size::B32,
|
||||
eeprom24x::addr_size::TwoBytes,
|
||||
eeprom24x::unique_serial::No,
|
||||
>,
|
||||
}
|
||||
|
||||
pub(crate) fn create_v3(peripherals: FreePeripherals, esp: ESP, config: PlantControllerConfig, battery_monitor: Box<dyn BatteryInteraction>) -> Result<Box<dyn BoardInteraction + '_>> {
|
||||
let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?;
|
||||
clock.set_pull(Pull::Floating)?;
|
||||
let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?;
|
||||
latch.set_pull(Pull::Floating)?;
|
||||
let mut data = PinDriver::input_output(peripherals.gpio23.downgrade())?;
|
||||
data.set_pull(Pull::Floating)?;
|
||||
let shift_register = ShiftRegister40::new(clock, latch, data);
|
||||
//disable all
|
||||
for mut pin in shift_register.decompose() {
|
||||
pin.set_low()?;
|
||||
}
|
||||
|
||||
let awake = &mut shift_register.decompose()[AWAKE];
|
||||
awake.set_high()?;
|
||||
|
||||
let charging = &mut shift_register.decompose()[CHARGING];
|
||||
charging.set_high()?;
|
||||
|
||||
let ms0 = &mut shift_register.decompose()[MS_0];
|
||||
ms0.set_low()?;
|
||||
let ms1 = &mut shift_register.decompose()[MS_1];
|
||||
ms1.set_low()?;
|
||||
let ms2 = &mut shift_register.decompose()[MS_2];
|
||||
ms2.set_low()?;
|
||||
let ms3 = &mut shift_register.decompose()[MS_3];
|
||||
ms3.set_low()?;
|
||||
|
||||
let ms4 = &mut shift_register.decompose()[MS_4];
|
||||
ms4.set_high()?;
|
||||
|
||||
println!("Init battery driver");
|
||||
|
||||
println!("Init rtc driver");
|
||||
let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
|
||||
|
||||
println!("Init rtc eeprom driver");
|
||||
let mut eeprom = {
|
||||
Eeprom24x::new_24x32(
|
||||
MutexDevice::new(&I2C_DRIVER),
|
||||
SlaveAddr::Alternative(true, true, true),
|
||||
)
|
||||
};
|
||||
|
||||
let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?;
|
||||
one_wire_pin.set_pull(Pull::Floating)?;
|
||||
|
||||
let rtc_time = rtc.datetime();
|
||||
match rtc_time {
|
||||
OkStd(tt) => {
|
||||
println!("Rtc Module reports time at UTC {}", tt);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Rtc Module could not be read {:?}", err);
|
||||
}
|
||||
}
|
||||
match eeprom.read_byte(0) {
|
||||
OkStd(byte) => {
|
||||
println!("Read first byte with status {}", byte);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Eeprom could not read first byte {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let mut signal_counter = PcntDriver::new(
|
||||
peripherals.pcnt0,
|
||||
Some(peripherals.gpio22),
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
)?;
|
||||
|
||||
signal_counter.channel_config(
|
||||
PcntChannel::Channel0,
|
||||
PinIndex::Pin0,
|
||||
PinIndex::Pin1,
|
||||
&PcntChannelConfig {
|
||||
lctrl_mode: PcntControlMode::Keep,
|
||||
hctrl_mode: PcntControlMode::Keep,
|
||||
pos_mode: PcntCountMode::Increment,
|
||||
neg_mode: PcntCountMode::Hold,
|
||||
counter_h_lim: i16::MAX,
|
||||
counter_l_lim: 0,
|
||||
},
|
||||
)?;
|
||||
|
||||
let adc_config = AdcChannelConfig {
|
||||
attenuation: attenuation::DB_11,
|
||||
resolution: Resolution::Resolution12Bit,
|
||||
calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
|
||||
};
|
||||
let tank_driver = AdcDriver::new(peripherals.adc1)?;
|
||||
let tank_channel: AdcChannelDriver<Gpio5, AdcDriver<esp_idf_hal::adc::ADC1>> =
|
||||
AdcChannelDriver::new(tank_driver, peripherals.gpio5, &adc_config)?;
|
||||
|
||||
let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?;
|
||||
solar_is_day.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?;
|
||||
light.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut main_pump = PinDriver::input_output(peripherals.gpio2.downgrade())?;
|
||||
main_pump.set_pull(Pull::Floating)?;
|
||||
main_pump.set_low()?;
|
||||
let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?;
|
||||
tank_power.set_pull(Pull::Floating)?;
|
||||
let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?;
|
||||
general_fault.set_pull(Pull::Floating)?;
|
||||
general_fault.set_low()?;
|
||||
|
||||
let one_wire_bus = OneWire::new(one_wire_pin)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
|
||||
let mut shift_register_enable_invert = PinDriver::output(peripherals.gpio21.downgrade())?;
|
||||
|
||||
unsafe { gpio_hold_dis(shift_register_enable_invert.pin()) };
|
||||
shift_register_enable_invert.set_low()?;
|
||||
unsafe { gpio_hold_en(shift_register_enable_invert.pin()) };
|
||||
|
||||
Ok(Box::new(V3 {
|
||||
config,
|
||||
battery_monitor,
|
||||
esp,
|
||||
shift_register,
|
||||
shift_register_enable_invert,
|
||||
tank_channel,
|
||||
solar_is_day,
|
||||
light,
|
||||
main_pump,
|
||||
tank_power,
|
||||
general_fault,
|
||||
signal_counter,
|
||||
one_wire_bus,
|
||||
rtc,
|
||||
eeprom,
|
||||
}))
|
||||
}
|
||||
|
||||
impl BoardInteraction<'_> for V3<'_> {
|
||||
fn get_esp(&mut self) -> &mut ESP<'static> {
|
||||
&mut self.esp
|
||||
}
|
||||
|
||||
fn get_config(&mut self) -> &PlantControllerConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
fn get_battery_monitor(&mut self) -> &mut Box<(dyn BatteryInteraction + 'static)> {
|
||||
&mut self.battery_monitor
|
||||
}
|
||||
|
||||
fn set_charge_indicator(&mut self, charging: bool) -> Result<()> {
|
||||
Ok(self.shift_register.decompose()[CHARGING].set_state(charging.into())?)
|
||||
}
|
||||
|
||||
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
||||
let _ = self.shift_register.decompose()[AWAKE].set_low();
|
||||
deep_sleep(duration_in_ms)
|
||||
}
|
||||
|
||||
fn get_backup_info(&mut self) -> Result<BackupHeader> {
|
||||
let store = bincode::serialize(&BackupHeader::default())?.len();
|
||||
let mut header_page_buffer = vec![0_u8; store];
|
||||
|
||||
self.eeprom
|
||||
.read_data(0, &mut header_page_buffer)
|
||||
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
|
||||
|
||||
println!("Raw header is {:?} with size {}", header_page_buffer, store);
|
||||
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn get_backup_config(&mut self) -> Result<Vec<u8>> {
|
||||
let store = bincode::serialize(&BackupHeader::default())?.len();
|
||||
let mut header_page_buffer = vec![0_u8; store];
|
||||
|
||||
self.eeprom
|
||||
.read_data(0, &mut header_page_buffer)
|
||||
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
|
||||
|
||||
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
|
||||
|
||||
//skip page 0, used by the header
|
||||
let data_start_address = self.eeprom.page_size() as u32;
|
||||
let mut data_buffer = vec![0_u8; header.size];
|
||||
self.eeprom
|
||||
.read_data(data_start_address, &mut data_buffer)
|
||||
.map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?;
|
||||
|
||||
let checksum = X25.checksum(&data_buffer);
|
||||
if checksum != header.crc16 {
|
||||
bail!(
|
||||
"Invalid checksum, got {} but expected {}",
|
||||
checksum,
|
||||
header.crc16
|
||||
);
|
||||
}
|
||||
|
||||
Ok(data_buffer)
|
||||
}
|
||||
|
||||
fn backup_config(&mut self, bytes: &[u8]) -> Result<()> {
|
||||
let time = self.get_rtc_time()?.timestamp_millis();
|
||||
|
||||
let delay = Delay::new_default();
|
||||
|
||||
let checksum = X25.checksum(bytes);
|
||||
let page_size = self.eeprom.page_size();
|
||||
|
||||
let header = BackupHeader {
|
||||
crc16: checksum,
|
||||
timestamp: time,
|
||||
size: bytes.len(),
|
||||
};
|
||||
|
||||
let encoded = bincode::serialize(&header)?;
|
||||
if encoded.len() > page_size {
|
||||
bail!(
|
||||
"Size limit reached header is {}, but firest page is only {}",
|
||||
encoded.len(),
|
||||
page_size
|
||||
)
|
||||
}
|
||||
let as_u8: &[u8] = &encoded;
|
||||
|
||||
match self.eeprom.write_page(0, as_u8) {
|
||||
OkStd(_) => {}
|
||||
Err(err) => bail!("Error writing eeprom {:?}", err),
|
||||
};
|
||||
delay.delay_ms(5);
|
||||
|
||||
let to_write = bytes.chunks(page_size);
|
||||
|
||||
let mut lastiter = 0;
|
||||
let mut current_page = 1;
|
||||
for chunk in to_write {
|
||||
let address = current_page * page_size as u32;
|
||||
self.eeprom
|
||||
.write_page(address, chunk)
|
||||
.map_err(|err| anyhow!("Error writing eeprom {:?}", err))?;
|
||||
current_page += 1;
|
||||
|
||||
let iter = (current_page % 8) as usize;
|
||||
if iter != lastiter {
|
||||
for i in 0..PLANT_COUNT {
|
||||
let _ = self.fault(i, iter == i);
|
||||
}
|
||||
lastiter = iter;
|
||||
}
|
||||
|
||||
delay.delay_ms(5);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_day(&self) -> bool {
|
||||
self.solar_is_day.get_level().into()
|
||||
}
|
||||
|
||||
fn water_temperature_c(&mut self) -> Result<f32> {
|
||||
self.one_wire_bus
|
||||
.reset(&mut self.esp.delay)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
let first = self.one_wire_bus.devices(false, &mut self.esp.delay).next();
|
||||
if first.is_none() {
|
||||
bail!("Not found any one wire Ds18b20");
|
||||
}
|
||||
let device_address = first
|
||||
.unwrap()
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
|
||||
let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
|
||||
water_temp_sensor
|
||||
.start_temp_measurement(&mut self.one_wire_bus, &mut self.esp.delay)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay);
|
||||
let sensor_data = water_temp_sensor
|
||||
.read_data(&mut self.one_wire_bus, &mut self.esp.delay)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
if sensor_data.temperature == 85_f32 {
|
||||
bail!("Ds18b20 dummy temperature returned");
|
||||
}
|
||||
Ok(sensor_data.temperature / 10_f32)
|
||||
}
|
||||
|
||||
fn tank_sensor_voltage(&mut self) -> Result<f32> {
|
||||
self.tank_power.set_high()?;
|
||||
//let stabilize
|
||||
self.esp.delay.delay_ms(100);
|
||||
|
||||
let mut store = [0_u16; TANK_MULTI_SAMPLE];
|
||||
for multisample in 0..TANK_MULTI_SAMPLE {
|
||||
let value = self.tank_channel.read()?;
|
||||
store[multisample] = value;
|
||||
}
|
||||
self.tank_power.set_low()?;
|
||||
|
||||
store.sort();
|
||||
let median_mv = store[6] as f32 / 1000_f32;
|
||||
Ok(median_mv)
|
||||
}
|
||||
fn light(&mut self, enable: bool) -> Result<()> {
|
||||
unsafe { gpio_hold_dis(self.light.pin()) };
|
||||
self.light.set_state(enable.into())?;
|
||||
unsafe { gpio_hold_en(self.light.pin()) };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pump(&mut self, plant: usize, enable: bool) -> Result<()> {
|
||||
if enable {
|
||||
self.main_pump.set_high()?;
|
||||
}
|
||||
|
||||
let index = match plant {
|
||||
0 => PUMP1_BIT,
|
||||
1 => PUMP2_BIT,
|
||||
2 => PUMP3_BIT,
|
||||
3 => PUMP4_BIT,
|
||||
4 => PUMP5_BIT,
|
||||
5 => PUMP6_BIT,
|
||||
6 => PUMP7_BIT,
|
||||
7 => PUMP8_BIT,
|
||||
_ => bail!("Invalid pump {plant}",),
|
||||
};
|
||||
//currently infallible error, keep for future as result anyway
|
||||
self.shift_register.decompose()[index].set_state(enable.into())?;
|
||||
|
||||
if !enable {
|
||||
self.main_pump.set_low()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fault(&mut self, plant: usize, enable: bool) -> Result<()> {
|
||||
let index = match plant {
|
||||
0 => FAULT_1,
|
||||
1 => FAULT_2,
|
||||
2 => FAULT_3,
|
||||
3 => FAULT_4,
|
||||
4 => FAULT_5,
|
||||
5 => FAULT_6,
|
||||
6 => FAULT_7,
|
||||
7 => FAULT_8,
|
||||
_ => panic!("Invalid plant id {}", plant),
|
||||
};
|
||||
self.shift_register.decompose()[index].set_state(enable.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32> {
|
||||
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
|
||||
for repeat in 0..REPEAT_MOIST_MEASURE {
|
||||
self.signal_counter.counter_pause()?;
|
||||
self.signal_counter.counter_clear()?;
|
||||
//Disable all
|
||||
self.shift_register.decompose()[MS_4].set_high()?;
|
||||
|
||||
let sensor_channel = match sensor {
|
||||
Sensor::A => match plant {
|
||||
0 => SENSOR_A_1,
|
||||
1 => SENSOR_A_2,
|
||||
2 => SENSOR_A_3,
|
||||
3 => SENSOR_A_4,
|
||||
4 => SENSOR_A_5,
|
||||
5 => SENSOR_A_6,
|
||||
6 => SENSOR_A_7,
|
||||
7 => SENSOR_A_8,
|
||||
_ => bail!("Invalid plant id {}", plant),
|
||||
},
|
||||
Sensor::B => match plant {
|
||||
0 => SENSOR_B_1,
|
||||
1 => SENSOR_B_2,
|
||||
2 => SENSOR_B_3,
|
||||
3 => SENSOR_B_4,
|
||||
4 => SENSOR_B_5,
|
||||
5 => SENSOR_B_6,
|
||||
6 => SENSOR_B_7,
|
||||
7 => SENSOR_B_8,
|
||||
_ => bail!("Invalid plant id {}", plant),
|
||||
},
|
||||
};
|
||||
|
||||
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
|
||||
let pin_0 = &mut self.shift_register.decompose()[MS_0];
|
||||
let pin_1 = &mut self.shift_register.decompose()[MS_1];
|
||||
let pin_2 = &mut self.shift_register.decompose()[MS_2];
|
||||
let pin_3 = &mut self.shift_register.decompose()[MS_3];
|
||||
if is_bit_set(0) {
|
||||
pin_0.set_high()?;
|
||||
} else {
|
||||
pin_0.set_low()?;
|
||||
}
|
||||
if is_bit_set(1) {
|
||||
pin_1.set_high()?;
|
||||
} else {
|
||||
pin_1.set_low()?;
|
||||
}
|
||||
if is_bit_set(2) {
|
||||
pin_2.set_high()?;
|
||||
} else {
|
||||
pin_2.set_low()?;
|
||||
}
|
||||
if is_bit_set(3) {
|
||||
pin_3.set_high()?;
|
||||
} else {
|
||||
pin_3.set_low()?;
|
||||
}
|
||||
|
||||
self.shift_register.decompose()[MS_4].set_low()?;
|
||||
self.shift_register.decompose()[SENSOR_ON].set_high()?;
|
||||
|
||||
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
|
||||
let factor = 1000f32 / measurement as f32;
|
||||
|
||||
//give some time to stabilize
|
||||
self.esp.delay.delay_ms(10);
|
||||
self.signal_counter.counter_resume()?;
|
||||
self.esp.delay.delay_ms(measurement);
|
||||
self.signal_counter.counter_pause()?;
|
||||
self.shift_register.decompose()[MS_4].set_high()?;
|
||||
self.shift_register.decompose()[SENSOR_ON].set_low()?;
|
||||
self.esp.delay.delay_ms(10);
|
||||
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
||||
let hz = unscaled as f32 * factor;
|
||||
log(
|
||||
LogMessage::RawMeasure,
|
||||
unscaled as u32,
|
||||
hz as u32,
|
||||
&plant.to_string(),
|
||||
&format!("{sensor:?}"),
|
||||
);
|
||||
results[repeat] = hz;
|
||||
}
|
||||
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
|
||||
|
||||
let mid = results.len() / 2;
|
||||
let median = results[mid];
|
||||
Ok(median)
|
||||
}
|
||||
|
||||
fn general_fault(&mut self, enable: bool) {
|
||||
unsafe { gpio_hold_dis(self.general_fault.pin()) };
|
||||
self.general_fault.set_state(enable.into()).unwrap();
|
||||
unsafe { gpio_hold_en(self.general_fault.pin()) };
|
||||
}
|
||||
|
||||
fn factory_reset(&mut self) -> Result<()> {
|
||||
println!("factory resetting");
|
||||
self.esp.delete_config()?;
|
||||
//destroy backup header
|
||||
let dummy: [u8; 0] = [];
|
||||
self.backup_config(&dummy)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
|
||||
match self.rtc.datetime() {
|
||||
OkStd(rtc_time) => Ok(rtc_time.and_utc()),
|
||||
Err(err) => {
|
||||
bail!("Error getting rtc time {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> Result<()> {
|
||||
let naive_time = time.naive_utc();
|
||||
match self.rtc.set_datetime(&naive_time) {
|
||||
OkStd(_) => Ok(()),
|
||||
Err(err) => {
|
||||
bail!("Error getting rtc time {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pump(&mut self, plant: usize) -> Result<()> {
|
||||
self.pump(plant, true)?;
|
||||
unsafe { vTaskDelay(30000) };
|
||||
self.pump(plant, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test(&mut self) -> Result<()> {
|
||||
self.general_fault(true);
|
||||
self.esp.delay.delay_ms(100);
|
||||
self.general_fault(false);
|
||||
self.esp.delay.delay_ms(100);
|
||||
self.light(true)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
self.light(false)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
for i in 0..PLANT_COUNT {
|
||||
self.fault(i, true)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
self.fault(i, false)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
}
|
||||
for i in 0..PLANT_COUNT {
|
||||
self.pump(i, true)?;
|
||||
self.esp.delay.delay_ms(100);
|
||||
self.pump(i, false)?;
|
||||
self.esp.delay.delay_ms(100);
|
||||
}
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let a = self.measure_moisture_hz(plant, Sensor::A);
|
||||
let b = self.measure_moisture_hz(plant, Sensor::B);
|
||||
let aa = match a {
|
||||
OkStd(a) => a as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
let bb = match b {
|
||||
OkStd(b) => b as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
|
||||
}
|
||||
self.esp.delay.delay_ms(10);
|
||||
Ok(())
|
||||
}
|
||||
}
|
530
rust/src/hal/v4_hal.rs
Normal file
530
rust/src/hal/v4_hal.rs
Normal file
@ -0,0 +1,530 @@
|
||||
use anyhow::{anyhow, bail};
|
||||
use chrono::{DateTime, Utc};
|
||||
use ds18b20::Ds18b20;
|
||||
use ds323x::{DateTimeAccess, Ds323x};
|
||||
use eeprom24x::{Eeprom24x, Eeprom24xTrait, SlaveAddr};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_bus::i2c::MutexDevice;
|
||||
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio5, IOPin, InputOutput, Output, PinDriver, Pull};
|
||||
use esp_idf_hal::i2c::I2cDriver;
|
||||
use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex};
|
||||
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
||||
use one_wire_bus::OneWire;
|
||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
||||
use crate::hal::{deep_sleep, BackupHeader, BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, REPEAT_MOIST_MEASURE, TANK_MULTI_SAMPLE, X25};
|
||||
use crate::hal::esp::ESP;
|
||||
use std::result::Result::Ok as OkStd;
|
||||
use esp_idf_hal::adc::{attenuation, Resolution};
|
||||
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
|
||||
use crate::config::PlantControllerConfig;
|
||||
use crate::hal::battery::{BatteryInteraction, BatteryMonitor};
|
||||
use crate::log::{log, LogMessage};
|
||||
|
||||
const MS0: u8 = 1_u8;
|
||||
const MS1: u8 = 0_u8;
|
||||
const MS2: u8 = 3_u8;
|
||||
const MS3: u8 = 4_u8;
|
||||
const MS4: u8 = 2_u8;
|
||||
const SENSOR_ON: u8 = 5_u8;
|
||||
|
||||
pub struct V4<'a> {
|
||||
esp: ESP<'a>,
|
||||
battery_monitor: Box<dyn BatteryInteraction>,
|
||||
config: PlantControllerConfig,
|
||||
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, esp_idf_hal::adc::ADC1>>,
|
||||
solar_is_day: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, esp_idf_hal::gpio::Input>,
|
||||
signal_counter: PcntDriver<'a>,
|
||||
charge_indicator: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
awake: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, Output>,
|
||||
light: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
tank_power: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
one_wire_bus: OneWire<PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>>,
|
||||
rtc:
|
||||
Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
|
||||
eeprom: Eeprom24x<
|
||||
MutexDevice<'a, I2cDriver<'a>>,
|
||||
eeprom24x::page_size::B32,
|
||||
eeprom24x::addr_size::TwoBytes,
|
||||
eeprom24x::unique_serial::No,
|
||||
>,
|
||||
general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>,
|
||||
pump_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
|
||||
sensor_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn create_v4(peripherals: FreePeripherals, esp:ESP, config:PlantControllerConfig, battery_monitor: Box<dyn BatteryInteraction>) -> anyhow::Result<Box<dyn BoardInteraction + '_>> {
|
||||
let mut awake = PinDriver::output(peripherals.gpio15.downgrade())?;
|
||||
awake.set_high()?;
|
||||
|
||||
let mut general_fault = PinDriver::input_output(peripherals.gpio6.downgrade())?;
|
||||
general_fault.set_pull(Pull::Floating)?;
|
||||
general_fault.set_low()?;
|
||||
|
||||
println!("Init rtc driver");
|
||||
let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
|
||||
|
||||
println!("Init rtc eeprom driver");
|
||||
let mut eeprom = {
|
||||
Eeprom24x::new_24x32(
|
||||
MutexDevice::new(&I2C_DRIVER),
|
||||
SlaveAddr::Alternative(true, true, true),
|
||||
)
|
||||
};
|
||||
|
||||
let mut one_wire_pin = PinDriver::input_output_od(peripherals.gpio18.downgrade())?;
|
||||
one_wire_pin.set_pull(Pull::Floating)?;
|
||||
|
||||
let one_wire_bus = OneWire::new(one_wire_pin)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
|
||||
let rtc_time = rtc.datetime();
|
||||
match rtc_time {
|
||||
OkStd(tt) => {
|
||||
println!("Rtc Module reports time at UTC {}", tt);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Rtc Module could not be read {:?}", err);
|
||||
}
|
||||
}
|
||||
match eeprom.read_byte(0) {
|
||||
OkStd(byte) => {
|
||||
println!("Read first byte with status {}", byte);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Eeprom could not read first byte {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let mut signal_counter = PcntDriver::new(
|
||||
peripherals.pcnt0,
|
||||
Some(peripherals.gpio22),
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
Option::<AnyInputPin>::None,
|
||||
)?;
|
||||
|
||||
signal_counter.channel_config(
|
||||
PcntChannel::Channel0,
|
||||
PinIndex::Pin0,
|
||||
PinIndex::Pin1,
|
||||
&PcntChannelConfig {
|
||||
lctrl_mode: PcntControlMode::Keep,
|
||||
hctrl_mode: PcntControlMode::Keep,
|
||||
pos_mode: PcntCountMode::Increment,
|
||||
neg_mode: PcntCountMode::Hold,
|
||||
counter_h_lim: i16::MAX,
|
||||
counter_l_lim: 0,
|
||||
},
|
||||
)?;
|
||||
|
||||
let adc_config = AdcChannelConfig {
|
||||
attenuation: attenuation::DB_11,
|
||||
resolution: Resolution::Resolution12Bit,
|
||||
calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
|
||||
};
|
||||
let tank_driver = AdcDriver::new(peripherals.adc1)?;
|
||||
let tank_channel: AdcChannelDriver<Gpio5, AdcDriver<esp_idf_hal::adc::ADC1>> =
|
||||
AdcChannelDriver::new(tank_driver, peripherals.gpio5, &adc_config)?;
|
||||
|
||||
let mut solar_is_day = PinDriver::input(peripherals.gpio7.downgrade())?;
|
||||
solar_is_day.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut light = PinDriver::input_output(peripherals.gpio10.downgrade())?;
|
||||
light.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut tank_power = PinDriver::input_output(peripherals.gpio11.downgrade())?;
|
||||
tank_power.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut charge_indicator = PinDriver::input_output(peripherals.gpio3.downgrade())?;
|
||||
charge_indicator.set_pull(Pull::Floating)?;
|
||||
charge_indicator.set_low()?;
|
||||
|
||||
let mut pump_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 32);
|
||||
|
||||
//todo error handing if init error
|
||||
for pin in 0..8 {
|
||||
let _ = pump_expander.pin_into_output(GPIOBank::Bank0, pin);
|
||||
let _ = pump_expander.pin_into_output(GPIOBank::Bank1, pin);
|
||||
let _ = pump_expander.pin_set_low(GPIOBank::Bank0, pin);
|
||||
let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin);
|
||||
}
|
||||
|
||||
let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34);
|
||||
for pin in 0..8 {
|
||||
let _ = sensor_expander.pin_into_output(GPIOBank::Bank0, pin);
|
||||
let _ = sensor_expander.pin_into_output(GPIOBank::Bank1, pin);
|
||||
let _ = sensor_expander.pin_set_low(GPIOBank::Bank0, pin);
|
||||
let _ = sensor_expander.pin_set_low(GPIOBank::Bank1, pin);
|
||||
}
|
||||
|
||||
let v = V4 {
|
||||
esp,
|
||||
awake,
|
||||
tank_channel,
|
||||
solar_is_day,
|
||||
signal_counter,
|
||||
light,
|
||||
tank_power,
|
||||
one_wire_bus,
|
||||
rtc,
|
||||
eeprom,
|
||||
general_fault,
|
||||
pump_expander,
|
||||
sensor_expander,
|
||||
charge_indicator,
|
||||
config,
|
||||
battery_monitor,
|
||||
};
|
||||
Ok(Box::new(v))
|
||||
}
|
||||
|
||||
impl BoardInteraction<'_> for V4<'_> {
|
||||
fn get_esp(&mut self) -> &mut ESP<'_> {
|
||||
&mut self.esp
|
||||
}
|
||||
|
||||
fn get_config(&mut self) -> &PlantControllerConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction> {
|
||||
&mut self.battery_monitor
|
||||
}
|
||||
|
||||
fn set_charge_indicator(&mut self, charging: bool) -> anyhow::Result<()> {
|
||||
self.charge_indicator.set_state(charging.into()).expect("cannot fail");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
||||
self.awake.set_low().unwrap();
|
||||
deep_sleep(duration_in_ms);
|
||||
}
|
||||
|
||||
fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> {
|
||||
let store = bincode::serialize(&BackupHeader::default())?.len();
|
||||
let mut header_page_buffer = vec![0_u8; store];
|
||||
|
||||
self.eeprom
|
||||
.read_data(0, &mut header_page_buffer)
|
||||
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
|
||||
|
||||
println!("Raw header is {:?} with size {}", header_page_buffer, store);
|
||||
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
|
||||
anyhow::Ok(header)
|
||||
}
|
||||
|
||||
fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
let store = bincode::serialize(&BackupHeader::default())?.len();
|
||||
let mut header_page_buffer = vec![0_u8; store];
|
||||
|
||||
self.eeprom
|
||||
.read_data(0, &mut header_page_buffer)
|
||||
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
|
||||
|
||||
let header: BackupHeader = bincode::deserialize(&header_page_buffer)?;
|
||||
|
||||
//skip page 0, used by the header
|
||||
let data_start_address = self.eeprom.page_size() as u32;
|
||||
let mut data_buffer = vec![0_u8; header.size];
|
||||
self.eeprom
|
||||
.read_data(data_start_address, &mut data_buffer)
|
||||
.map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?;
|
||||
|
||||
let checksum = X25.checksum(&data_buffer);
|
||||
if checksum != header.crc16 {
|
||||
bail!(
|
||||
"Invalid checksum, got {} but expected {}",
|
||||
checksum,
|
||||
header.crc16
|
||||
);
|
||||
}
|
||||
|
||||
anyhow::Ok(data_buffer)
|
||||
}
|
||||
|
||||
fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
|
||||
let time = self.get_rtc_time()?.timestamp_millis();
|
||||
|
||||
let delay = Delay::new_default();
|
||||
|
||||
let checksum = X25.checksum(bytes);
|
||||
let page_size = self.eeprom.page_size();
|
||||
|
||||
let header = BackupHeader {
|
||||
crc16: checksum,
|
||||
timestamp: time,
|
||||
size: bytes.len(),
|
||||
};
|
||||
|
||||
let encoded = bincode::serialize(&header)?;
|
||||
if encoded.len() > page_size {
|
||||
bail!(
|
||||
"Size limit reached header is {}, but firest page is only {}",
|
||||
encoded.len(),
|
||||
page_size
|
||||
)
|
||||
}
|
||||
let as_u8: &[u8] = &encoded;
|
||||
|
||||
match self.eeprom.write_page(0, as_u8) {
|
||||
OkStd(_) => {}
|
||||
Err(err) => bail!("Error writing eeprom {:?}", err),
|
||||
};
|
||||
delay.delay_ms(5);
|
||||
|
||||
let to_write = bytes.chunks(page_size);
|
||||
|
||||
let mut lastiter = 0;
|
||||
let mut current_page = 1;
|
||||
for chunk in to_write {
|
||||
let address = current_page * page_size as u32;
|
||||
self.eeprom
|
||||
.write_page(address, chunk)
|
||||
.map_err(|err| anyhow!("Error writing eeprom {:?}", err))?;
|
||||
current_page += 1;
|
||||
|
||||
let iter = (current_page % 8) as usize;
|
||||
if iter != lastiter {
|
||||
for i in 0..PLANT_COUNT {
|
||||
let _ = self.fault(i, iter == i);
|
||||
}
|
||||
lastiter = iter;
|
||||
}
|
||||
|
||||
delay.delay_ms(5);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn is_day(&self) -> bool {
|
||||
self.solar_is_day.get_level().into()
|
||||
}
|
||||
|
||||
fn water_temperature_c(&mut self) -> anyhow::Result<f32> {
|
||||
self.one_wire_bus
|
||||
.reset(&mut self.esp.delay)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
let first = self.one_wire_bus.devices(false, &mut self.esp.delay).next();
|
||||
if first.is_none() {
|
||||
bail!("Not found any one wire Ds18b20");
|
||||
}
|
||||
let device_address = first
|
||||
.unwrap()
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
|
||||
let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
|
||||
water_temp_sensor
|
||||
.start_temp_measurement(&mut self.one_wire_bus, &mut self.esp.delay)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.esp.delay);
|
||||
let sensor_data = water_temp_sensor
|
||||
.read_data(&mut self.one_wire_bus, &mut self.esp.delay)
|
||||
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
|
||||
if sensor_data.temperature == 85_f32 {
|
||||
bail!("Ds18b20 dummy temperature returned");
|
||||
}
|
||||
anyhow::Ok(sensor_data.temperature / 10_f32)
|
||||
}
|
||||
|
||||
fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> {
|
||||
self.tank_power.set_high()?;
|
||||
//let stabilize
|
||||
self.esp.delay.delay_ms(100);
|
||||
|
||||
let mut store = [0_u16; TANK_MULTI_SAMPLE];
|
||||
for multisample in 0..TANK_MULTI_SAMPLE {
|
||||
let value = self.tank_channel.read()?;
|
||||
store[multisample] = value;
|
||||
}
|
||||
self.tank_power.set_low()?;
|
||||
|
||||
store.sort();
|
||||
let median_mv = store[6] as f32 / 1000_f32;
|
||||
anyhow::Ok(median_mv)
|
||||
}
|
||||
|
||||
fn light(&mut self, enable: bool) -> anyhow::Result<()> {
|
||||
unsafe { gpio_hold_dis(self.light.pin()) };
|
||||
self.light.set_state(enable.into())?;
|
||||
unsafe { gpio_hold_en(self.light.pin()) };
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn pump(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> {
|
||||
if enable {
|
||||
self.pump_expander.pin_set_high(GPIOBank::Bank0, plant.try_into()?)?;
|
||||
} else {
|
||||
self.pump_expander.pin_set_low(GPIOBank::Bank0, plant.try_into()?)?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn fault(&mut self, plant: usize, enable: bool) -> anyhow::Result<()> {
|
||||
if enable {
|
||||
self.pump_expander.pin_set_high(GPIOBank::Bank1, plant.try_into()?)?
|
||||
} else {
|
||||
self.pump_expander.pin_set_low(GPIOBank::Bank1, plant.try_into()?)?
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32> {
|
||||
let mut results = [0_f32; REPEAT_MOIST_MEASURE];
|
||||
for repeat in 0..REPEAT_MOIST_MEASURE {
|
||||
self.signal_counter.counter_pause()?;
|
||||
self.signal_counter.counter_clear()?;
|
||||
|
||||
|
||||
//Disable all
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
|
||||
|
||||
let sensor_channel = match sensor {
|
||||
Sensor::A => plant as u32,
|
||||
Sensor::B => (15 - plant) as u32,
|
||||
};
|
||||
|
||||
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
|
||||
if is_bit_set(0) {
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS0)?;
|
||||
} else {
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
|
||||
}
|
||||
if is_bit_set(1) {
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS1)?;
|
||||
} else {
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
|
||||
}
|
||||
if is_bit_set(2) {
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS2)?;
|
||||
} else {
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;
|
||||
}
|
||||
if is_bit_set(3) {
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS3)?;
|
||||
} else {
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?;
|
||||
}
|
||||
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?;
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?;
|
||||
|
||||
let delay = Delay::new_default();
|
||||
let measurement = 100; // TODO what is this scaling factor? what is its purpose?
|
||||
let factor = 1000f32 / measurement as f32;
|
||||
|
||||
//give some time to stabilize
|
||||
delay.delay_ms(10);
|
||||
self.signal_counter.counter_resume()?;
|
||||
delay.delay_ms(measurement);
|
||||
self.signal_counter.counter_pause()?;
|
||||
self.sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?;
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;
|
||||
self.sensor_expander.pin_set_low(GPIOBank::Bank0, MS3)?;
|
||||
delay.delay_ms(10);
|
||||
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
||||
let hz = unscaled as f32 * factor;
|
||||
log(
|
||||
LogMessage::RawMeasure,
|
||||
unscaled as u32,
|
||||
hz as u32,
|
||||
&plant.to_string(),
|
||||
&format!("{sensor:?}"),
|
||||
);
|
||||
results[repeat] = hz;
|
||||
}
|
||||
results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord
|
||||
|
||||
let mid = results.len() / 2;
|
||||
let median = results[mid];
|
||||
anyhow::Ok(median)
|
||||
}
|
||||
|
||||
fn general_fault(&mut self, enable: bool) {
|
||||
unsafe { gpio_hold_dis(self.general_fault.pin()) };
|
||||
self.general_fault.set_state(enable.into()).unwrap();
|
||||
unsafe { gpio_hold_en(self.general_fault.pin()) };
|
||||
}
|
||||
|
||||
fn factory_reset(&mut self) -> anyhow::Result<()> {
|
||||
println!("factory resetting");
|
||||
self.esp.delete_config()?;
|
||||
//destroy backup header
|
||||
let dummy: [u8; 0] = [];
|
||||
self.backup_config(&dummy)?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> {
|
||||
match self.rtc.datetime() {
|
||||
OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()),
|
||||
Err(err) => {
|
||||
bail!("Error getting rtc time {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> {
|
||||
let naive_time = time.naive_utc();
|
||||
match self.rtc.set_datetime(&naive_time) {
|
||||
OkStd(_) => anyhow::Ok(()),
|
||||
Err(err) => {
|
||||
bail!("Error getting rtc time {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_pump(&mut self, plant: usize) -> anyhow::Result<()> {
|
||||
self.pump(plant, true)?;
|
||||
self.esp.delay.delay_ms(30000);
|
||||
self.pump(plant, false)?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn test(&mut self) -> anyhow::Result<()> {
|
||||
self.general_fault(true);
|
||||
self.esp.delay.delay_ms(100);
|
||||
self.general_fault(false);
|
||||
self.esp.delay.delay_ms(500);
|
||||
self.light(true)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
self.light(false)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
for i in 0..PLANT_COUNT {
|
||||
self.fault(i, true)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
self.fault(i, false)?;
|
||||
self.esp.delay.delay_ms(500);
|
||||
}
|
||||
for i in 0..PLANT_COUNT {
|
||||
self.pump(i, true)?;
|
||||
self.esp.delay.delay_ms(100);
|
||||
self.pump(i, false)?;
|
||||
self.esp.delay.delay_ms(100);
|
||||
}
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let a = self.measure_moisture_hz(plant, Sensor::A);
|
||||
let b = self.measure_moisture_hz(plant, Sensor::B);
|
||||
let aa = match a {
|
||||
OkStd(a) => a as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
let bb = match b {
|
||||
OkStd(b) => b as u32,
|
||||
Err(_) => u32::MAX,
|
||||
};
|
||||
log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "");
|
||||
}
|
||||
self.esp.delay.delay_ms(10);
|
||||
anyhow::Ok(())
|
||||
}
|
||||
}
|
222
rust/src/main.rs
222
rust/src/main.rs
@ -28,9 +28,9 @@ mod tank;
|
||||
|
||||
use plant_state::PlantState;
|
||||
use tank::*;
|
||||
use crate::config::BoardVersion::INITIAL;
|
||||
use crate::hal::{BoardInteraction, PlantHal, HAL, PLANT_COUNT};
|
||||
use crate::hal::battery::BatteryInteraction;
|
||||
use crate::hal::BoardHal::Initial;
|
||||
pub static BOARD_ACCESS: Lazy<Mutex<HAL>> = Lazy::new(|| PlantHal::create().unwrap());
|
||||
pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
|
||||
|
||||
@ -151,14 +151,14 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
log(LogMessage::PartitionState, 0, 0, "", ota_state_string);
|
||||
|
||||
let mut board = BOARD_ACCESS.lock().expect("Could not lock board no other lock should be able to exist during startup!");
|
||||
board.general_fault(false);
|
||||
board.board_hal.general_fault(false);
|
||||
|
||||
let cur = board
|
||||
let cur = board.board_hal
|
||||
.get_rtc_time()
|
||||
.or_else(|err| {
|
||||
println!("rtc module error: {:?}", err);
|
||||
board.general_fault(true);
|
||||
board.time()
|
||||
board.board_hal.general_fault(true);
|
||||
board.board_hal.get_esp().time()
|
||||
})
|
||||
.map_err(|err| -> Result<(), _> {
|
||||
bail!("time error {}", err);
|
||||
@ -172,57 +172,52 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
println!("cur is {}", cur);
|
||||
match board.battery_monitor.average_current_milli_ampere() {
|
||||
match board.board_hal.get_battery_monitor().average_current_milli_ampere() {
|
||||
Ok(charging) => {
|
||||
board.set_charge_indicator(charging > 20)
|
||||
let _ = board.board_hal.set_charge_indicator(charging > 20);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
if board.get_restart_to_conf() {
|
||||
if board.board_hal.get_esp().get_restart_to_conf() {
|
||||
log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "");
|
||||
for _i in 0..2 {
|
||||
board.general_fault(true);
|
||||
board.board_hal.general_fault(true);
|
||||
Delay::new_default().delay_ms(100);
|
||||
board.general_fault(false);
|
||||
board.board_hal.general_fault(false);
|
||||
Delay::new_default().delay_ms(100);
|
||||
}
|
||||
to_config = true;
|
||||
board.general_fault(true);
|
||||
board.set_restart_to_conf(false);
|
||||
} else if board.mode_override_pressed() {
|
||||
board.general_fault(true);
|
||||
board.board_hal.general_fault(true);
|
||||
board.board_hal.get_esp().set_restart_to_conf(false);
|
||||
} else if board.board_hal.get_esp().mode_override_pressed() {
|
||||
board.board_hal.general_fault(true);
|
||||
log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "");
|
||||
for _i in 0..5 {
|
||||
board.general_fault(true);
|
||||
board.board_hal.general_fault(true);
|
||||
Delay::new_default().delay_ms(100);
|
||||
board.general_fault(false);
|
||||
board.board_hal.general_fault(false);
|
||||
Delay::new_default().delay_ms(100);
|
||||
}
|
||||
|
||||
if board.mode_override_pressed() {
|
||||
board.general_fault(true);
|
||||
if board.board_hal.get_esp().mode_override_pressed() {
|
||||
board.board_hal.general_fault(true);
|
||||
to_config = true;
|
||||
} else {
|
||||
board.general_fault(false);
|
||||
board.board_hal.general_fault(false);
|
||||
}
|
||||
}
|
||||
|
||||
match board.board_hal {
|
||||
Initial { .. } => {
|
||||
//config upload will trigger reboot and then switch to selected board_hal
|
||||
let _ = board.esp.wifi_ap();
|
||||
drop(board);
|
||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||
let _webserver = httpd(reboot_now.clone());
|
||||
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
|
||||
}
|
||||
_ => {}
|
||||
if board.board_hal.get_config().hardware.board == INITIAL {
|
||||
let _ = board.board_hal.get_esp().wifi_ap();
|
||||
drop(board);
|
||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||
let _webserver = httpd(reboot_now.clone());
|
||||
wait_infinity(WaitType::MissingConfig, reboot_now.clone());
|
||||
}
|
||||
|
||||
|
||||
println!("attempting to connect wifi");
|
||||
let network_mode = if board.config.network.ssid.is_some() {
|
||||
let network_mode = if board.board_hal.get_config().network.ssid.is_some() {
|
||||
try_connect_wifi_sntp_mqtt(&mut board)
|
||||
} else {
|
||||
println!("No wifi configured");
|
||||
@ -231,7 +226,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
|
||||
if matches!(network_mode, NetworkMode::OFFLINE) && to_config {
|
||||
println!("Could not connect to station and config mode forced, switching to ap mode!");
|
||||
match board.esp.wifi_ap() {
|
||||
match board.board_hal.get_esp().wifi_ap() {
|
||||
Ok(_) => {
|
||||
println!("Started ap, continuing")
|
||||
}
|
||||
@ -239,7 +234,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let timezone = match & board.config.timezone {
|
||||
let timezone = match & board.board_hal.get_config().timezone {
|
||||
Some(tz_str) => tz_str.parse::<Tz>().unwrap_or_else(|_| {
|
||||
println!("Invalid timezone '{}', falling back to UTC", tz_str);
|
||||
UTC
|
||||
@ -286,7 +281,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
let tank_state = determine_tank_state(&mut board);
|
||||
|
||||
if tank_state.is_enabled() {
|
||||
if let Some(err) = tank_state.got_error(&board.config.tank) {
|
||||
if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) {
|
||||
match err {
|
||||
TankError::SensorDisabled => { /* unreachable */ }
|
||||
TankError::SensorMissing(raw_value_mv) => log(
|
||||
@ -308,10 +303,10 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
// disabled cannot trigger this because of wrapping if is_enabled
|
||||
board.general_fault(true);
|
||||
} else if tank_state.warn_level(&board.config.tank).is_ok_and(|warn| warn) {
|
||||
board.board_hal.general_fault(true);
|
||||
} else if tank_state.warn_level(&board.board_hal.get_config().tank).is_ok_and(|warn| warn) {
|
||||
log(LogMessage::TankWaterLevelLow, 0, 0, "", "");
|
||||
board.general_fault(true);
|
||||
board.board_hal.general_fault(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,18 +327,15 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
|
||||
let pump_required = plantstate
|
||||
.iter()
|
||||
.zip(&board.config.plants)
|
||||
.zip(&board.board_hal.get_config().plants)
|
||||
.any(|(it, conf)| it.needs_to_be_watered(conf, &timezone_time))
|
||||
&& !water_frozen;
|
||||
if pump_required {
|
||||
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
|
||||
if !dry_run {
|
||||
board.any_pump(true)?; // enables main power output, eg for a central pump with valve setup or a main water valve for the risk affine
|
||||
}
|
||||
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.config.plants.clone()).enumerate() {
|
||||
for (plant_id, (state, plant_config)) in plantstate.iter().zip(&board.board_hal.get_config().plants.clone()).enumerate() {
|
||||
if state.needs_to_be_watered(plant_config, &timezone_time) {
|
||||
let pump_count = board.consecutive_pump_count(plant_id) + 1;
|
||||
board.store_consecutive_pump_count(plant_id, pump_count);
|
||||
let pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id) + 1;
|
||||
board.board_hal.get_esp().store_consecutive_pump_count(plant_id, pump_count);
|
||||
|
||||
let pump_ineffective = pump_count > plant_config.max_consecutive_pump_count as u32;
|
||||
if pump_ineffective {
|
||||
@ -354,7 +346,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
&(plant_id+1).to_string(),
|
||||
"",
|
||||
);
|
||||
board.fault(plant_id, true)?;
|
||||
board.board_hal.fault(plant_id, true)?;
|
||||
}
|
||||
log(
|
||||
LogMessage::PumpPlant,
|
||||
@ -363,71 +355,68 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
&dry_run.to_string(),
|
||||
"",
|
||||
);
|
||||
board.store_last_pump_time(plant_id, cur);
|
||||
board.last_pump_time(plant_id);
|
||||
board.board_hal.get_esp().store_last_pump_time(plant_id, cur);
|
||||
board.board_hal.get_esp().last_pump_time(plant_id);
|
||||
//state.active = true;
|
||||
|
||||
pump_info(&mut board, plant_id, true, pump_ineffective);
|
||||
|
||||
if !dry_run {
|
||||
board.pump(plant_id, true)?;
|
||||
board.board_hal.pump(plant_id, true)?;
|
||||
Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32);
|
||||
board.pump(plant_id, false)?;
|
||||
board.board_hal.pump(plant_id, false)?;
|
||||
}
|
||||
pump_info(&mut board, plant_id, false, pump_ineffective);
|
||||
|
||||
} else if !state.pump_in_timeout(plant_config, &timezone_time) {
|
||||
// plant does not need to be watered and is not in timeout
|
||||
// -> reset consecutive pump count
|
||||
board.store_consecutive_pump_count(plant_id, 0);
|
||||
board.board_hal.get_esp().store_consecutive_pump_count(plant_id, 0);
|
||||
}
|
||||
}
|
||||
if !dry_run {
|
||||
board.any_pump(false)?; // disable main power output, eg for a central pump with valve setup or a main water valve for the risk affine
|
||||
}
|
||||
}
|
||||
|
||||
let is_day = board.is_day();
|
||||
let state_of_charge = board.battery_monitor.state_charge_percent().unwrap_or(0);
|
||||
let is_day = board.board_hal.is_day();
|
||||
let state_of_charge = board.board_hal.get_battery_monitor().state_charge_percent().unwrap_or(0);
|
||||
|
||||
let mut light_state = LightState {
|
||||
enabled: board.config.night_lamp.enabled,
|
||||
enabled: board.board_hal.get_config().night_lamp.enabled,
|
||||
..Default::default()
|
||||
};
|
||||
if light_state.enabled {
|
||||
light_state.is_day = is_day;
|
||||
light_state.out_of_work_hour = !in_time_range(
|
||||
&timezone_time,
|
||||
board.config.night_lamp.night_lamp_hour_start,
|
||||
board.config.night_lamp.night_lamp_hour_end,
|
||||
board.board_hal.get_config().night_lamp.night_lamp_hour_start,
|
||||
board.board_hal.get_config().night_lamp.night_lamp_hour_end,
|
||||
);
|
||||
|
||||
if state_of_charge < board.config.night_lamp.low_soc_cutoff {
|
||||
board.set_low_voltage_in_cycle();
|
||||
} else if state_of_charge > board.config.night_lamp.low_soc_restore {
|
||||
board.clear_low_voltage_in_cycle();
|
||||
if state_of_charge < board.board_hal.get_config().night_lamp.low_soc_cutoff {
|
||||
board.board_hal.get_esp().set_low_voltage_in_cycle();
|
||||
} else if state_of_charge > board.board_hal.get_config().night_lamp.low_soc_restore {
|
||||
board.board_hal.get_esp().clear_low_voltage_in_cycle();
|
||||
}
|
||||
light_state.battery_low = board.low_voltage_in_cycle();
|
||||
light_state.battery_low = board.board_hal.get_esp().low_voltage_in_cycle();
|
||||
|
||||
if !light_state.out_of_work_hour {
|
||||
if board.config.night_lamp.night_lamp_only_when_dark {
|
||||
if board.board_hal.get_config().night_lamp.night_lamp_only_when_dark {
|
||||
if !light_state.is_day {
|
||||
if light_state.battery_low {
|
||||
board.light(false)?;
|
||||
board.board_hal.light(false)?;
|
||||
} else {
|
||||
light_state.active = true;
|
||||
board.light(true)?;
|
||||
board.board_hal.light(true)?;
|
||||
}
|
||||
}
|
||||
} else if light_state.battery_low {
|
||||
board.light(false)?;
|
||||
board.board_hal.light(false)?;
|
||||
} else {
|
||||
light_state.active = true;
|
||||
board.light(true)?;
|
||||
board.board_hal.light(true)?;
|
||||
}
|
||||
} else {
|
||||
light_state.active = false;
|
||||
board.light(false)?;
|
||||
board.board_hal.light(false)?;
|
||||
}
|
||||
|
||||
println!("Lightstate is {:?}", light_state);
|
||||
@ -435,7 +424,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
|
||||
match serde_json::to_string(&light_state) {
|
||||
Ok(state) => {
|
||||
let _ = board.esp.mqtt_publish( "/light", state.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/light", state.as_bytes());
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing lightstate {}", err);
|
||||
@ -443,16 +432,16 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
};
|
||||
|
||||
let deep_sleep_duration_minutes: u32 = if state_of_charge < 10 {
|
||||
let _ = board.esp.mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "low Volt 12h".as_bytes());
|
||||
12 * 60
|
||||
} else if is_day {
|
||||
let _ = board.esp.mqtt_publish( "/deepsleep", "normal 20m".as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "normal 20m".as_bytes());
|
||||
20
|
||||
} else {
|
||||
let _ = board.esp.mqtt_publish( "/deepsleep", "night 1h".as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/deepsleep", "night 1h".as_bytes());
|
||||
60
|
||||
};
|
||||
let _ = board.esp.mqtt_publish( "/state", "sleep".as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/state", "sleep".as_bytes());
|
||||
|
||||
//determine next event
|
||||
//is light out of work trigger soon?
|
||||
@ -472,15 +461,15 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
let _webserver = httpd(reboot_now.clone());
|
||||
wait_infinity(WaitType::MqttConfig, reboot_now.clone());
|
||||
}
|
||||
board.set_restart_to_conf(false);
|
||||
board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
|
||||
board.board_hal.get_esp().set_restart_to_conf(false);
|
||||
board.board_hal.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
|
||||
}
|
||||
|
||||
fn obtain_tank_temperature(board: &mut MutexGuard<HAL>) -> anyhow::Result<f32> {
|
||||
//multisample should be moved to water_temperature_c
|
||||
let mut attempt = 1;
|
||||
let water_temp: Result<f32, anyhow::Error> = loop {
|
||||
let temp = board.water_temperature_c();
|
||||
let temp = board.board_hal.water_temperature_c();
|
||||
match &temp {
|
||||
Ok(res) => {
|
||||
println!("Water temp is {}", res);
|
||||
@ -499,9 +488,9 @@ fn obtain_tank_temperature(board: &mut MutexGuard<HAL>) -> anyhow::Result<f32> {
|
||||
}
|
||||
|
||||
fn publish_tank_state(board: &mut MutexGuard<HAL>, tank_state: &TankState, water_temp: &anyhow::Result<f32>) {
|
||||
match serde_json::to_string(&tank_state.as_mqtt_info(&board.config.tank, water_temp)) {
|
||||
match serde_json::to_string(&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, water_temp)) {
|
||||
Ok(state) => {
|
||||
let _ = board.esp.mqtt_publish("/water", state.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish("/water", state.as_bytes());
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing tankstate {}", err);
|
||||
@ -510,13 +499,13 @@ fn publish_tank_state(board: &mut MutexGuard<HAL>, tank_state: &TankState, water
|
||||
}
|
||||
|
||||
fn publish_plant_states(board: &mut MutexGuard<HAL>, timezone_time: &DateTime<Tz>, plantstate: &[PlantState; 8]) {
|
||||
for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.config.plants.clone()).enumerate() {
|
||||
for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(& board.board_hal.get_config().plants.clone()).enumerate() {
|
||||
match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, &timezone_time)) {
|
||||
Ok(state) => {
|
||||
let plant_topic = format!("/plant{}", plant_id + 1);
|
||||
let _ = board.esp.mqtt_publish(&plant_topic, state.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish(&plant_topic, state.as_bytes());
|
||||
//reduce speed as else messages will be dropped
|
||||
board.esp.delay.delay_ms(200);
|
||||
board.board_hal.get_esp().delay.delay_ms(200);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing plant state {}", err);
|
||||
@ -526,43 +515,43 @@ fn publish_plant_states(board: &mut MutexGuard<HAL>, timezone_time: &DateTime<Tz
|
||||
}
|
||||
|
||||
fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard<HAL>, ip_address: &String, timezone_time: DateTime<Tz>) {
|
||||
let _ = board.esp.mqtt_publish("/firmware/address", ip_address.as_bytes());
|
||||
let _ = board.esp.mqtt_publish( "/firmware/githash", version.git_hash.as_bytes());
|
||||
let _ = board.esp.mqtt_publish(
|
||||
let _ = board.board_hal.get_esp().mqtt_publish("/firmware/address", ip_address.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/githash", version.git_hash.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish(
|
||||
"/firmware/buildtime",
|
||||
version.build_time.as_bytes(),
|
||||
);
|
||||
let _ = board.esp.mqtt_publish(
|
||||
let _ = board.board_hal.get_esp().mqtt_publish(
|
||||
"/firmware/last_online",
|
||||
timezone_time.to_rfc3339().as_bytes(),
|
||||
);
|
||||
let _ = board.esp.mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes());
|
||||
let _ = board.esp.mqtt_publish(
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/firmware/ota_state", ota_state_string.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish(
|
||||
"/firmware/partition_address",
|
||||
format!("{:#06x}", address).as_bytes(),
|
||||
);
|
||||
let _ = board.esp.mqtt_publish( "/state", "online".as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/state", "online".as_bytes());
|
||||
}
|
||||
|
||||
fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode{
|
||||
let nw_conf = &board.config.network.clone();
|
||||
match board.esp.wifi(nw_conf) {
|
||||
let nw_conf = &board.board_hal.get_config().network.clone();
|
||||
match board.board_hal.get_esp().wifi(nw_conf) {
|
||||
Ok(ip_info) => {
|
||||
let sntp_mode: SntpMode = match board.sntp(1000 * 10) {
|
||||
let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10) {
|
||||
Ok(new_time) => {
|
||||
println!("Using time from sntp");
|
||||
let _ = board.set_rtc_time(&new_time);
|
||||
let _ = board.board_hal.set_rtc_time(&new_time);
|
||||
SntpMode::SYNC {current: new_time}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("sntp error: {}", err);
|
||||
board.general_fault(true);
|
||||
board.board_hal.general_fault(true);
|
||||
SntpMode::OFFLINE
|
||||
}
|
||||
};
|
||||
let mqtt_connected = if let Some(_) = board.config.network.mqtt_url {
|
||||
let nw_config = &board.config.network.clone();
|
||||
match board.esp.mqtt(nw_config) {
|
||||
let mqtt_connected = if let Some(_) = board.board_hal.get_config().network.mqtt_url {
|
||||
let nw_config = &board.board_hal.get_config().network.clone();
|
||||
match board.board_hal.get_esp().mqtt(nw_config) {
|
||||
Ok(_) => {
|
||||
println!("Mqtt connection ready");
|
||||
true
|
||||
@ -583,13 +572,12 @@ fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<HAL>) -> NetworkMode{
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Offline mode");
|
||||
board.general_fault(true);
|
||||
board.board_hal.general_fault(true);
|
||||
NetworkMode::OFFLINE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO clean this up? better state
|
||||
fn pump_info(board: &mut MutexGuard<HAL>, plant_id: usize, pump_active: bool, pump_ineffective: bool) {
|
||||
let pump_info = PumpInfo {
|
||||
enabled: pump_active,
|
||||
@ -598,7 +586,7 @@ fn pump_info(board: &mut MutexGuard<HAL>, plant_id: usize, pump_active: bool, pu
|
||||
let pump_topic = format!("/pump{}", plant_id + 1);
|
||||
match serde_json::to_string(&pump_info) {
|
||||
Ok(state) => {
|
||||
let _ = board.esp.mqtt_publish(&pump_topic, state.as_bytes());
|
||||
let _ = board.board_hal.get_esp().mqtt_publish(&pump_topic, state.as_bytes());
|
||||
//reduce speed as else messages will be dropped
|
||||
Delay::new_default().delay_ms(200);
|
||||
}
|
||||
@ -611,8 +599,8 @@ fn pump_info(board: &mut MutexGuard<HAL>, plant_id: usize, pump_active: bool, pu
|
||||
fn publish_battery_state(
|
||||
board: &mut MutexGuard<'_, HAL<'_>>
|
||||
) {
|
||||
let state = board.battery_monitor.get_battery_state();
|
||||
let _ = board.esp.mqtt_publish( "/battery", state.as_bytes());
|
||||
let state = board.board_hal.get_battery_monitor().get_battery_state();
|
||||
let _ = board.board_hal.get_esp().mqtt_publish( "/battery", state.as_bytes());
|
||||
}
|
||||
|
||||
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
@ -622,9 +610,9 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
let mut lock = BOARD_ACCESS.lock().unwrap();
|
||||
if let Ok(charging) = lock.battery_monitor.average_current_milli_ampere() {
|
||||
lock.set_charge_indicator(charging > 20)
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
if let Ok(charging) = board.board_hal.get_battery_monitor().average_current_milli_ampere() {
|
||||
let _ = board.board_hal.set_charge_indicator(charging > 20);
|
||||
}
|
||||
match wait_type {
|
||||
WaitType::MissingConfig => {
|
||||
@ -632,36 +620,36 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
led_count %= 8;
|
||||
led_count += 1;
|
||||
for i in 0..8 {
|
||||
let _ = lock.fault(i, i < led_count);
|
||||
let _ = board.board_hal.fault(i, i < led_count);
|
||||
}
|
||||
}
|
||||
WaitType::ConfigButton => {
|
||||
// Alternating pattern: 1010 1010 -> 0101 0101
|
||||
pattern_step = (pattern_step + 1) % 2;
|
||||
for i in 0..8 {
|
||||
let _ = lock.fault(i, (i + pattern_step) % 2 == 0);
|
||||
let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0);
|
||||
}
|
||||
}
|
||||
WaitType::MqttConfig => {
|
||||
// Moving dot pattern
|
||||
pattern_step = (pattern_step + 1) % 8;
|
||||
for i in 0..8 {
|
||||
let _ = lock.fault(i, i == pattern_step);
|
||||
let _ = board.board_hal.fault(i, i == pattern_step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock.general_fault(true);
|
||||
drop(lock);
|
||||
board.board_hal.general_fault(true);
|
||||
drop(board);
|
||||
vTaskDelay(delay);
|
||||
let mut lock = BOARD_ACCESS.lock().unwrap();
|
||||
lock.general_fault(false);
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.board_hal.general_fault(false);
|
||||
|
||||
// Clear all LEDs
|
||||
for i in 0..8 {
|
||||
let _ = lock.fault(i, false);
|
||||
let _ = board.board_hal.fault(i, false);
|
||||
}
|
||||
drop(lock);
|
||||
drop(board);
|
||||
vTaskDelay(delay);
|
||||
|
||||
if wait_type == WaitType::MqttConfig
|
||||
@ -672,7 +660,7 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
//ensure clean http answer
|
||||
Delay::new_default().delay_ms(500);
|
||||
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
|
||||
BOARD_ACCESS.lock().unwrap().board_hal.deep_sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -685,8 +673,8 @@ fn main() {
|
||||
// timeout, this is just a fallback
|
||||
Ok(_) => {
|
||||
println!("Main app finished, restarting");
|
||||
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(false);
|
||||
BOARD_ACCESS.lock().unwrap().deep_sleep(1);
|
||||
BOARD_ACCESS.lock().unwrap().board_hal.get_esp().set_restart_to_conf(false);
|
||||
BOARD_ACCESS.lock().unwrap().board_hal.deep_sleep(1);
|
||||
}
|
||||
// if safe_main exists with an error, rollback to a known good ota version
|
||||
Err(err) => {
|
||||
|
@ -116,12 +116,12 @@ impl PlantState {
|
||||
plant_id: usize,
|
||||
board: &mut HAL
|
||||
) -> Self {
|
||||
let sensor_a = if board.config.plants[plant_id].sensor_a {
|
||||
match board.measure_moisture_hz(plant_id, Sensor::A) {
|
||||
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
|
||||
match board.board_hal.measure_moisture_hz(plant_id, Sensor::A) {
|
||||
Ok(raw) => match map_range_moisture(
|
||||
raw,
|
||||
board.config.plants[plant_id].moisture_sensor_min_frequency,
|
||||
board.config.plants[plant_id].moisture_sensor_max_frequency,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency,
|
||||
) {
|
||||
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
|
||||
raw_hz: raw,
|
||||
@ -137,12 +137,12 @@ impl PlantState {
|
||||
MoistureSensorState::Disabled
|
||||
};
|
||||
|
||||
let sensor_b = if board.config.plants[plant_id].sensor_b {
|
||||
match board.measure_moisture_hz(plant_id, Sensor::B) {
|
||||
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
|
||||
match board.board_hal.measure_moisture_hz(plant_id, Sensor::B) {
|
||||
Ok(raw) => match map_range_moisture(
|
||||
raw,
|
||||
board.config.plants[plant_id].moisture_sensor_min_frequency,
|
||||
board.config.plants[plant_id].moisture_sensor_max_frequency,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_max_frequency,
|
||||
) {
|
||||
Ok(moisture_percent) => MoistureSensorState::MoistureValue {
|
||||
raw_hz: raw,
|
||||
@ -158,8 +158,8 @@ impl PlantState {
|
||||
MoistureSensorState::Disabled
|
||||
};
|
||||
|
||||
let previous_pump = board.last_pump_time(plant_id);
|
||||
let consecutive_pump_count = board.consecutive_pump_count(plant_id);
|
||||
let previous_pump = board.board_hal.get_esp().last_pump_time(plant_id);
|
||||
let consecutive_pump_count = board.board_hal.get_esp().consecutive_pump_count(plant_id);
|
||||
let state = Self {
|
||||
sensor_a,
|
||||
sensor_b,
|
||||
@ -169,7 +169,7 @@ impl PlantState {
|
||||
},
|
||||
};
|
||||
if state.is_err() {
|
||||
let _ = board.fault(plant_id, true);
|
||||
let _ = board.board_hal.fault(plant_id, true);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
@ -158,8 +158,8 @@ impl TankState {
|
||||
pub fn determine_tank_state(
|
||||
board: &mut std::sync::MutexGuard<'_, HAL<'_>>
|
||||
) -> TankState {
|
||||
if board.config.tank.tank_sensor_enabled {
|
||||
match board.tank_sensor_voltage() {
|
||||
if board.board_hal.get_config().tank.tank_sensor_enabled {
|
||||
match board.board_hal.tank_sensor_voltage() {
|
||||
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
|
||||
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||
}
|
||||
|
@ -68,26 +68,26 @@ fn write_time(
|
||||
let actual_data = read_up_to_bytes_from_request(request, None)?;
|
||||
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
||||
let parsed = DateTime::parse_from_rfc3339(time.time).map_err(|err| anyhow::anyhow!(err))?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
|
||||
let now = timeval {
|
||||
tv_sec: parsed.to_utc().timestamp(),
|
||||
tv_usec: 0,
|
||||
};
|
||||
unsafe { settimeofday(&now, core::ptr::null_mut()) };
|
||||
board.set_rtc_time(&parsed.to_utc())?;
|
||||
board.board_hal.set_rtc_time(&parsed.to_utc())?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
fn get_time(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let native = board
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let native = board.board_hal.get_esp()
|
||||
.time()
|
||||
.map(|t| t.to_rfc3339())
|
||||
.unwrap_or("error".to_string());
|
||||
let rtc = board
|
||||
let rtc = board.board_hal
|
||||
.get_rtc_time()
|
||||
.map(|t| t.to_rfc3339())
|
||||
.unwrap_or("error".to_string());
|
||||
@ -158,8 +158,8 @@ fn get_live_moisture(
|
||||
fn get_config(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
let json = serde_json::to_string(&board.config)?;
|
||||
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
let json = serde_json::to_string(&board.board_hal.get_config())?;
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
|
||||
@ -167,16 +167,16 @@ fn backup_config(
|
||||
request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let all = read_up_to_bytes_from_request(request, Some(3072))?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.backup_config(&all)?;
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
board.board_hal.backup_config(&all)?;
|
||||
anyhow::Ok(Some("saved".to_owned()))
|
||||
}
|
||||
|
||||
fn get_backup_config(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let json = match board.get_backup_config() {
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let json = match board.board_hal.get_backup_config() {
|
||||
Ok(config) => from_utf8(&config)?.to_owned(),
|
||||
Err(err) => {
|
||||
println!("Error get backup config {:?}", err);
|
||||
@ -190,7 +190,7 @@ fn backup_info(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().expect("Should never fail");
|
||||
let header = board.get_backup_info();
|
||||
let header = board.board_hal.get_backup_info();
|
||||
let json = match header {
|
||||
Ok(h) => {
|
||||
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
||||
@ -218,18 +218,18 @@ fn set_config(
|
||||
let all = read_up_to_bytes_from_request(request, Some(3072))?;
|
||||
let config: PlantControllerConfig = serde_json::from_slice(&all)?;
|
||||
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.esp.set_config(&config)?;
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
board.board_hal.get_esp().save_config(&config)?;
|
||||
|
||||
board.config = config;
|
||||
//TODO fixme board.config = config;
|
||||
anyhow::Ok(Some("saved".to_owned()))
|
||||
}
|
||||
|
||||
fn get_battery_state(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let battery_state = board.battery_monitor.get_battery_state();
|
||||
let mut board = BOARD_ACCESS.lock().expect("board access");
|
||||
let battery_state = board.board_hal.get_battery_monitor().get_battery_state();
|
||||
let battery_json = serde_json::to_string(&battery_state)?;
|
||||
anyhow::Ok(Some(battery_json))
|
||||
}
|
||||
@ -259,7 +259,7 @@ fn pump_test(
|
||||
let actual_data = read_up_to_bytes_from_request(request, None)?;
|
||||
let pump_test: TestPump = serde_json::from_slice(&actual_data)?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.test_pump(pump_test.pump)?;
|
||||
board.board_hal.test_pump(pump_test.pump)?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
@ -269,9 +269,9 @@ fn tank_info(
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let tank_info = determine_tank_state(&mut board);
|
||||
//should be multsampled
|
||||
let water_temp = board.water_temperature_c();
|
||||
let water_temp = board.board_hal.water_temperature_c();
|
||||
Ok(Some(serde_json::to_string(
|
||||
&tank_info.as_mqtt_info(&board.config.tank, &water_temp),
|
||||
&tank_info.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
|
||||
)?))
|
||||
}
|
||||
|
||||
@ -281,7 +281,7 @@ fn night_lamp_test(
|
||||
let actual_data = read_up_to_bytes_from_request(request, None)?;
|
||||
let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.light(light_command.active)?;
|
||||
board.board_hal.light(light_command.active)?;
|
||||
anyhow::Ok(None)
|
||||
}
|
||||
|
||||
@ -289,7 +289,7 @@ fn wifi_scan(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let scan_result = board.wifi_scan()?;
|
||||
let scan_result = board.board_hal.get_esp().wifi_scan()?;
|
||||
let mut ssids: Vec<&String<32>> = Vec::new();
|
||||
scan_result.iter().for_each(|s| ssids.push(&s.ssid));
|
||||
let ssid_json = serde_json::to_string(&SSIDList { ssids })?;
|
||||
@ -300,8 +300,8 @@ fn wifi_scan(
|
||||
fn list_files(
|
||||
_request: &mut Request<&mut EspHttpConnection>,
|
||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||
let board = BOARD_ACCESS.lock().expect("It should be possible to lock the board for exclusive fs access");
|
||||
let result = board.esp.list_files();
|
||||
let mut board = BOARD_ACCESS.lock().expect("It should be possible to lock the board for exclusive fs access");
|
||||
let result = board.board_hal.get_esp().list_files();
|
||||
let file_list_json = serde_json::to_string(&result)?;
|
||||
anyhow::Ok(Some(file_list_json))
|
||||
}
|
||||
@ -323,12 +323,12 @@ fn ota(
|
||||
total_read += read;
|
||||
let to_write = &buffer[0..read];
|
||||
//delay for watchdog and wifi stuff
|
||||
board.esp.delay.delay_ms(1);
|
||||
board.board_hal.get_esp().delay.delay_ms(1);
|
||||
|
||||
let iter = (total_read / 1024) % 8;
|
||||
if iter != lastiter {
|
||||
for i in 0..PLANT_COUNT {
|
||||
let _ = board.fault(i, iter == i);
|
||||
let _ = board.board_hal.fault(i, iter == i);
|
||||
}
|
||||
lastiter = iter;
|
||||
}
|
||||
@ -345,7 +345,7 @@ fn ota(
|
||||
|
||||
let mut finalizer = ota.finalize()?;
|
||||
println!("changing boot partition");
|
||||
board.set_restart_to_conf(true);
|
||||
board.board_hal.get_esp().set_restart_to_conf(true);
|
||||
drop(board);
|
||||
finalizer.set_as_boot_partition()?;
|
||||
anyhow::Ok(None)
|
||||
@ -420,7 +420,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
.unwrap();
|
||||
server
|
||||
.fn_handler("/boardtest", Method::Post, move |_| {
|
||||
BOARD_ACCESS.lock().unwrap().test()
|
||||
BOARD_ACCESS.lock().unwrap().board_hal.test()
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
@ -472,7 +472,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
let reboot_now_for_reboot = reboot_now.clone();
|
||||
server
|
||||
.fn_handler("/reboot", Method::Post, move |_| {
|
||||
BOARD_ACCESS.lock().unwrap().set_restart_to_conf(true);
|
||||
BOARD_ACCESS.lock().unwrap().board_hal.get_esp().set_restart_to_conf(true);
|
||||
reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
@ -493,7 +493,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
let file_handle = BOARD_ACCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.esp
|
||||
.board_hal.get_esp()
|
||||
.get_file_handle(&filename, false);
|
||||
match file_handle {
|
||||
Ok(mut file_handle) => {
|
||||
@ -529,8 +529,8 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
server
|
||||
.fn_handler("/file", Method::Post, move |mut request| {
|
||||
let filename = query_param(request.uri(), "filename").unwrap();
|
||||
let mut lock = BOARD_ACCESS.lock().unwrap();
|
||||
let file_handle = lock.esp.get_file_handle(&filename, true);
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let file_handle = board.board_hal.get_esp().get_file_handle(&filename, true);
|
||||
match file_handle {
|
||||
//TODO get free filesystem size, check against during write if not to large
|
||||
Ok(mut file_handle) => {
|
||||
@ -542,7 +542,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
let iter = (total_read / 1024) % 8;
|
||||
if iter != lastiter {
|
||||
for i in 0..PLANT_COUNT {
|
||||
let _ = lock.fault(i, iter == i);
|
||||
let _ = board.board_hal.fault(i, iter == i);
|
||||
}
|
||||
lastiter = iter;
|
||||
}
|
||||
@ -564,7 +564,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
cors_response(request, 500, &error_text)?;
|
||||
}
|
||||
}
|
||||
drop(lock);
|
||||
drop(board);
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
@ -573,8 +573,8 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
.fn_handler("/file", Method::Delete, move |request| {
|
||||
let filename = query_param(request.uri(), "filename").unwrap();
|
||||
let copy = filename.clone();
|
||||
let board = BOARD_ACCESS.lock().unwrap();
|
||||
match board.esp.delete_file(&filename) {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
match board.board_hal.get_esp().delete_file(&filename) {
|
||||
Ok(_) => {
|
||||
let info = format!("Deleted file {copy}");
|
||||
cors_response(request, 200, &info)?;
|
||||
|
Loading…
x
Reference in New Issue
Block a user