proceed with bq34z100 extraction
This commit is contained in:
1835
rust/src/bq34z100.rs
1835
rust/src/bq34z100.rs
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@ pub struct Config {
|
||||
pub tank_sensor_enabled: bool,
|
||||
pub tank_useable_ml: u32,
|
||||
pub tank_warn_percent: u8,
|
||||
pub tank_empty_mv: u16,
|
||||
pub tank_full_mv: u16,
|
||||
pub tank_empty_percent: u16,
|
||||
pub tank_full_percent: u16,
|
||||
|
||||
pub night_lamp_hour_start: u8,
|
||||
pub night_lamp_hour_end: u8,
|
||||
@@ -38,8 +38,8 @@ impl Default for Config {
|
||||
plants: [Plant::default(); PLANT_COUNT],
|
||||
max_consecutive_pump_count: 15,
|
||||
tank_useable_ml: 5000,
|
||||
tank_empty_mv: 0100_u16,
|
||||
tank_full_mv: 3300_u16,
|
||||
tank_empty_percent: 0_u16,
|
||||
tank_full_percent: 100_u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
283
rust/src/main.rs
283
rust/src/main.rs
@@ -3,11 +3,13 @@ use std::{
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use chrono::{Datelike, Duration, NaiveDateTime, Timelike, DateTime};
|
||||
use anyhow::{bail, Result};
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike};
|
||||
use chrono_tz::{Europe::Berlin, Tz};
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_sys::{esp_restart, vTaskDelay, CONFIG_FREERTOS_HZ, esp_deep_sleep};
|
||||
use esp_idf_sys::{
|
||||
esp_deep_sleep, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay, CONFIG_FREERTOS_HZ
|
||||
};
|
||||
use esp_ota::rollback_and_reboot;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -51,11 +53,11 @@ enum WaitType {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Default)]
|
||||
struct LightState{
|
||||
struct LightState {
|
||||
active: bool,
|
||||
out_of_work_hour: bool,
|
||||
battery_low: bool,
|
||||
is_day: bool
|
||||
is_day: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Default)]
|
||||
@@ -75,7 +77,7 @@ struct PlantState {
|
||||
sensor_error_a: bool,
|
||||
sensor_error_b: bool,
|
||||
sensor_error_p: bool,
|
||||
out_of_work_hour: bool
|
||||
out_of_work_hour: bool,
|
||||
}
|
||||
|
||||
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
||||
@@ -122,10 +124,18 @@ pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
|
||||
|
||||
fn map_range(from_range: (f32, f32), s: f32) -> Result<f32> {
|
||||
if s < from_range.0 {
|
||||
bail!("Value out of range, min {} but current is {}", from_range.0, s);
|
||||
bail!(
|
||||
"Value out of range, min {} but current is {}",
|
||||
from_range.0,
|
||||
s
|
||||
);
|
||||
}
|
||||
if s > from_range.1 {
|
||||
bail!("Value out of range, max {} but current is {}", from_range.1, s);
|
||||
bail!(
|
||||
"Value out of range, max {} but current is {}",
|
||||
from_range.1,
|
||||
s
|
||||
);
|
||||
}
|
||||
return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0));
|
||||
}
|
||||
@@ -141,7 +151,7 @@ fn map_range_moisture(s: f32) -> Result<u8> {
|
||||
return Ok(tmp as u8);
|
||||
}
|
||||
|
||||
fn in_time_range(cur: DateTime<Tz>, start:u8, end:u8) -> bool{
|
||||
fn in_time_range(cur: DateTime<Tz>, start: u8, end: u8) -> bool {
|
||||
let curhour = cur.hour() as u8;
|
||||
//eg 10-14
|
||||
if start < end {
|
||||
@@ -152,40 +162,60 @@ fn in_time_range(cur: DateTime<Tz>, start:u8, end:u8) -> bool{
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_next_plant(plantstate: &mut [PlantState;PLANT_COUNT],cur: DateTime<Tz>, enough_water: bool, water_frozen: bool, tank_sensor_error: bool, config: &Config, board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>) -> Option<usize> {
|
||||
fn determine_next_plant(
|
||||
plantstate: &mut [PlantState; PLANT_COUNT],
|
||||
cur: DateTime<Tz>,
|
||||
enough_water: bool,
|
||||
water_frozen: bool,
|
||||
tank_sensor_error: bool,
|
||||
config: &Config,
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
) -> Option<usize> {
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let state = &mut plantstate[plant];
|
||||
let plant_config = config.plants[plant];
|
||||
match plant_config.mode {
|
||||
config::Mode::OFF => {
|
||||
|
||||
},
|
||||
config::Mode::OFF => {}
|
||||
config::Mode::TargetMoisture => {
|
||||
match board.measure_moisture_hz(plant, plant_hal::Sensor::A).and_then (|moist| map_range_moisture(moist as f32)) {
|
||||
match board
|
||||
.measure_moisture_hz(plant, plant_hal::Sensor::A)
|
||||
.and_then(|moist| map_range_moisture(moist as f32))
|
||||
{
|
||||
Ok(a) => state.a = Some(a),
|
||||
Err(err) => {
|
||||
board.fault(plant, true);
|
||||
println!("Could not determine Moisture A for plant {} due to {}", plant, err);
|
||||
state.a = None;
|
||||
println!(
|
||||
"Could not determine Moisture A for plant {} due to {}",
|
||||
plant, err
|
||||
);
|
||||
state.a = None;
|
||||
state.sensor_error_a = true;
|
||||
}
|
||||
}
|
||||
match board.measure_moisture_hz(plant, plant_hal::Sensor::B).and_then (|moist| map_range_moisture(moist as f32)) {
|
||||
match board
|
||||
.measure_moisture_hz(plant, plant_hal::Sensor::B)
|
||||
.and_then(|moist| map_range_moisture(moist as f32))
|
||||
{
|
||||
Ok(b) => state.b = Some(b),
|
||||
Err(err) => {
|
||||
board.fault(plant, true);
|
||||
println!("Could not determine Moisture B for plant {} due to {}", plant, err);
|
||||
state.b = None;
|
||||
println!(
|
||||
"Could not determine Moisture B for plant {} due to {}",
|
||||
plant, err
|
||||
);
|
||||
state.b = None;
|
||||
state.sensor_error_b = true;
|
||||
}
|
||||
}
|
||||
//FIXME how to average analyze whatever?
|
||||
//FIXME how to average analyze whatever?
|
||||
let a_low = state.a.is_some() && state.a.unwrap() < plant_config.target_moisture;
|
||||
let b_low = state.b.is_some() && state.b.unwrap() < plant_config.target_moisture;
|
||||
|
||||
|
||||
if a_low || b_low {
|
||||
state.dry = true;
|
||||
if tank_sensor_error && !config.tank_allow_pumping_if_sensor_error || !enough_water {
|
||||
if tank_sensor_error && !config.tank_allow_pumping_if_sensor_error
|
||||
|| !enough_water
|
||||
{
|
||||
state.no_water = true;
|
||||
}
|
||||
}
|
||||
@@ -194,20 +224,24 @@ fn determine_next_plant(plantstate: &mut [PlantState;PLANT_COUNT],cur: DateTime<
|
||||
if next_pump > cur {
|
||||
state.cooldown = true;
|
||||
}
|
||||
if !in_time_range(cur, plant_config.pump_hour_start, plant_config.pump_hour_end) {
|
||||
if !in_time_range(
|
||||
cur,
|
||||
plant_config.pump_hour_start,
|
||||
plant_config.pump_hour_end,
|
||||
) {
|
||||
state.out_of_work_hour = true;
|
||||
}
|
||||
if water_frozen {
|
||||
state.frozen = true;
|
||||
}
|
||||
if state.dry && !state.no_water && !state.cooldown && !state.out_of_work_hour{
|
||||
if state.dry && !state.no_water && !state.cooldown && !state.out_of_work_hour {
|
||||
if water_frozen {
|
||||
state.frozen = true;
|
||||
} else {
|
||||
state.do_water = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
config::Mode::TimerOnly => {
|
||||
let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
|
||||
let next_pump = board.last_pump_time(plant) + duration;
|
||||
@@ -220,14 +254,18 @@ fn determine_next_plant(plantstate: &mut [PlantState;PLANT_COUNT],cur: DateTime<
|
||||
state.do_water = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
config::Mode::TimerAndDeadzone => {
|
||||
let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
|
||||
let next_pump = board.last_pump_time(plant) + duration;
|
||||
if next_pump > cur {
|
||||
state.cooldown = true;
|
||||
}
|
||||
if !in_time_range(cur, plant_config.pump_hour_start, plant_config.pump_hour_end) {
|
||||
}
|
||||
if !in_time_range(
|
||||
cur,
|
||||
plant_config.pump_hour_start,
|
||||
plant_config.pump_hour_end,
|
||||
) {
|
||||
state.out_of_work_hour = true;
|
||||
}
|
||||
if !state.cooldown && !state.out_of_work_hour {
|
||||
@@ -237,10 +275,10 @@ fn determine_next_plant(plantstate: &mut [PlantState;PLANT_COUNT],cur: DateTime<
|
||||
state.do_water = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
//FIXME publish state here!
|
||||
if state.do_water{
|
||||
if state.do_water {
|
||||
if board.consecutive_pump_count(plant) > config.max_consecutive_pump_count.into() {
|
||||
state.not_effective = true;
|
||||
board.fault(plant, true);
|
||||
@@ -252,13 +290,16 @@ fn determine_next_plant(plantstate: &mut [PlantState;PLANT_COUNT],cur: DateTime<
|
||||
}
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let state = &plantstate[plant];
|
||||
println!("Checking for water plant {} with state {}", plant, state.do_water);
|
||||
println!(
|
||||
"Checking for water plant {} with state {}",
|
||||
plant, state.do_water
|
||||
);
|
||||
if state.do_water {
|
||||
return Some(plant);
|
||||
}
|
||||
}
|
||||
println!("No plant needs water");
|
||||
return None
|
||||
return None;
|
||||
}
|
||||
|
||||
fn safe_main() -> Result<()> {
|
||||
@@ -284,23 +325,23 @@ fn safe_main() -> Result<()> {
|
||||
|
||||
let partition_state: embedded_svc::ota::SlotState = embedded_svc::ota::SlotState::Unknown;
|
||||
match esp_idf_svc::ota::EspOta::new() {
|
||||
Ok(ota) => {
|
||||
//match ota.get_running_slot(){
|
||||
// Ok(slot) => {
|
||||
// partition_state = slot.state;
|
||||
// println!(
|
||||
// "Booting from {} with state {:?}",
|
||||
// slot.label, partition_state
|
||||
// );
|
||||
//},
|
||||
// Err(err) => {
|
||||
// println!("Error getting running slot {}", err);
|
||||
// },
|
||||
//}
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error obtaining ota info {}", err);
|
||||
},
|
||||
Ok(ota) => {
|
||||
//match ota.get_running_slot(){
|
||||
// Ok(slot) => {
|
||||
// partition_state = slot.state;
|
||||
// println!(
|
||||
// "Booting from {} with state {:?}",
|
||||
// slot.label, partition_state
|
||||
// );
|
||||
//},
|
||||
// Err(err) => {
|
||||
// println!("Error getting running slot {}", err);
|
||||
// },
|
||||
//}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error obtaining ota info {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Board hal init");
|
||||
@@ -402,7 +443,7 @@ fn safe_main() -> Result<()> {
|
||||
}
|
||||
|
||||
if online_mode == OnlineMode::Wifi {
|
||||
match board.sntp(1000 * 120) {
|
||||
match board.sntp(1000 * 5) {
|
||||
Ok(new_time) => {
|
||||
cur = new_time;
|
||||
online_mode = OnlineMode::SnTp;
|
||||
@@ -458,37 +499,43 @@ fn safe_main() -> Result<()> {
|
||||
if config.tank_sensor_enabled {
|
||||
let mut tank_value_r = 0;
|
||||
|
||||
let success = board.tank_sensor_mv().and_then(|raw| {
|
||||
tank_value_r = raw;
|
||||
return map_range(
|
||||
(config.tank_empty_mv as f32, config.tank_full_mv as f32),
|
||||
raw as f32,
|
||||
);
|
||||
}).and_then(|percent| {
|
||||
let left_ml = ((percent / 100_f32) * config.tank_useable_ml as f32) as u32;
|
||||
println!(
|
||||
"Tank sensor returned mv {} as {}% leaving {} ml useable",
|
||||
tank_value_r, percent as u8, left_ml
|
||||
);
|
||||
if config.tank_warn_percent > percent as u8 {
|
||||
board.general_fault(true);
|
||||
println!(
|
||||
"Low water, current percent is {}, minimum warn level is {}",
|
||||
percent as u8, config.tank_warn_percent
|
||||
let success = board
|
||||
.tank_sensor_percent()
|
||||
.and_then(|raw| {
|
||||
tank_value_r = raw;
|
||||
return map_range(
|
||||
(
|
||||
config.tank_empty_percent as f32,
|
||||
config.tank_full_percent as f32,
|
||||
),
|
||||
raw as f32,
|
||||
);
|
||||
}
|
||||
if config.tank_warn_percent <= 0 {
|
||||
enough_water = false;
|
||||
}
|
||||
return Ok(());
|
||||
});
|
||||
})
|
||||
.and_then(|percent| {
|
||||
let left_ml = (percent * config.tank_useable_ml as f32) as u32;
|
||||
println!(
|
||||
"Tank sensor returned mv {} as {}% leaving {} ml useable",
|
||||
tank_value_r, percent as u8, left_ml
|
||||
);
|
||||
if config.tank_warn_percent > percent as u8 {
|
||||
board.general_fault(true);
|
||||
println!(
|
||||
"Low water, current percent is {}, minimum warn level is {}",
|
||||
percent as u8, config.tank_warn_percent
|
||||
);
|
||||
}
|
||||
if config.tank_warn_percent <= 0 {
|
||||
enough_water = false;
|
||||
}
|
||||
return Ok(());
|
||||
});
|
||||
match success {
|
||||
Err(err) => {
|
||||
println!("Could not determine tank value due to {}", err);
|
||||
board.general_fault(true);
|
||||
tank_sensor_error = true;
|
||||
}
|
||||
Ok(_) => {},
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,17 +550,25 @@ fn safe_main() -> Result<()> {
|
||||
water_frozen = true;
|
||||
}
|
||||
break;
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Could not get water temp {}", err)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut plantstate = [PlantState {
|
||||
..Default::default()
|
||||
}; PLANT_COUNT];
|
||||
let plant_to_pump = determine_next_plant(&mut plantstate, europe_time, enough_water, water_frozen, tank_sensor_error, &config, &mut board);
|
||||
let plant_to_pump = determine_next_plant(
|
||||
&mut plantstate,
|
||||
europe_time,
|
||||
enough_water,
|
||||
water_frozen,
|
||||
tank_sensor_error,
|
||||
&config,
|
||||
&mut board,
|
||||
);
|
||||
|
||||
if STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
drop(board);
|
||||
@@ -521,72 +576,94 @@ fn safe_main() -> Result<()> {
|
||||
let _webserver = httpd(reboot_now.clone());
|
||||
wait_infinity(WaitType::StayAlive, reboot_now.clone());
|
||||
}
|
||||
|
||||
|
||||
match plant_to_pump {
|
||||
Some(plant) => {
|
||||
let mut state = plantstate[plant];
|
||||
let consecutive_pump_count = board.consecutive_pump_count(plant) + 1;
|
||||
board.store_consecutive_pump_count(plant, consecutive_pump_count);
|
||||
let plant_config = config.plants[plant];
|
||||
println!("Trying to pump for {}s with pump {} now", plant_config.pump_time_s,plant);
|
||||
|
||||
println!(
|
||||
"Trying to pump for {}s with pump {} now",
|
||||
plant_config.pump_time_s, plant
|
||||
);
|
||||
|
||||
board.any_pump(true)?;
|
||||
board.store_last_pump_time(plant, cur);
|
||||
board.pump(plant, true)?;
|
||||
board.last_pump_time(plant);
|
||||
state.active = true;
|
||||
//FIXME do periodic pump test here and state update
|
||||
unsafe { vTaskDelay(plant_config.pump_time_s as u32*CONFIG_FREERTOS_HZ) };
|
||||
match map_range_moisture(board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32) {
|
||||
unsafe { vTaskDelay(plant_config.pump_time_s as u32 * CONFIG_FREERTOS_HZ) };
|
||||
board.pump(plant, false)?;
|
||||
match map_range_moisture(
|
||||
board.measure_moisture_hz(plant, plant_hal::Sensor::PUMP)? as f32
|
||||
) {
|
||||
Ok(p) => state.after_p = Some(p),
|
||||
Err(err) => {
|
||||
board.fault(plant, true);
|
||||
println!("Could not determine Moisture P after for plant {} due to {}", plant, err);
|
||||
state.after_p = None;
|
||||
println!(
|
||||
"Could not determine Moisture P after for plant {} due to {}",
|
||||
plant, err
|
||||
);
|
||||
state.after_p = None;
|
||||
state.sensor_error_p = true;
|
||||
}
|
||||
}
|
||||
if state.after_p.is_none() || state.p.is_none() || state.after_p.unwrap() < state.p.unwrap() + 5 {
|
||||
if state.after_p.is_none()
|
||||
|| state.p.is_none()
|
||||
|| state.after_p.unwrap() < state.p.unwrap() + 5
|
||||
{
|
||||
state.pump_error = true;
|
||||
board.fault(plant, true);
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
println!("Nothing to do");
|
||||
}
|
||||
,
|
||||
}
|
||||
|
||||
let mut light_state = LightState{ ..Default::default() };
|
||||
let mut light_state = LightState {
|
||||
..Default::default()
|
||||
};
|
||||
light_state.is_day = board.is_day();
|
||||
light_state.out_of_work_hour = !in_time_range(europe_time, config.night_lamp_hour_start, config.night_lamp_hour_end);
|
||||
light_state.out_of_work_hour = !in_time_range(
|
||||
europe_time,
|
||||
config.night_lamp_hour_start,
|
||||
config.night_lamp_hour_end,
|
||||
);
|
||||
if !light_state.out_of_work_hour {
|
||||
if config.night_lamp_only_when_dark {
|
||||
if !light_state.is_day {
|
||||
light_state.active = true;
|
||||
board.light(true).unwrap();
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
light_state.active = true;
|
||||
board.light(true).unwrap();
|
||||
}
|
||||
} else {
|
||||
light_state.active = false;
|
||||
board.light(false).unwrap();
|
||||
}
|
||||
println!("Lightstate is {:?}", light_state);
|
||||
|
||||
//check if during light time
|
||||
//lightstate += out of worktime
|
||||
//check battery level
|
||||
//lightstate += battery empty
|
||||
//check solar level if config requires
|
||||
//lightstate += stillday
|
||||
//if no preventing lightstate, enable light
|
||||
//lightstate = active
|
||||
|
||||
//deepsleep here?
|
||||
unsafe { esp_deep_sleep(1000*1000*10) };
|
||||
//check if during light time
|
||||
//lightstate += out of worktime
|
||||
//check battery level
|
||||
//lightstate += battery empty
|
||||
//check solar level if config requires
|
||||
//lightstate += stillday
|
||||
//if no preventing lightstate, enable light
|
||||
//lightstate = active
|
||||
|
||||
//relatch
|
||||
unsafe{gpio_deep_sleep_hold_dis()};
|
||||
unsafe { gpio_deep_sleep_hold_en() };
|
||||
unsafe { esp_deep_sleep(1000 * 1000 * 20) };
|
||||
}
|
||||
|
||||
fn main(){
|
||||
fn main() {
|
||||
let result = safe_main();
|
||||
result.unwrap();
|
||||
}
|
||||
|
@@ -1,12 +1,9 @@
|
||||
//mod config;
|
||||
|
||||
use bit_field::BitField;
|
||||
use embedded_hal::blocking::i2c::Operation;
|
||||
use embedded_svc::wifi::{
|
||||
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
|
||||
};
|
||||
|
||||
use esp_idf_hal::i2c::{I2cConfig, I2cDriver, APBTickType};
|
||||
use esp_idf_hal::i2c::{I2cConfig, I2cDriver, I2cError};
|
||||
use esp_idf_hal::units::FromValueType;
|
||||
use esp_idf_svc::eventloop::EspSystemEventLoop;
|
||||
use esp_idf_svc::mqtt::client::QoS::ExactlyOnce;
|
||||
@@ -14,7 +11,7 @@ use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
|
||||
use esp_idf_svc::nvs::EspDefaultNvsPartition;
|
||||
use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
|
||||
use esp_idf_svc::wifi::EspWifi;
|
||||
use measurements::{Measurement, Temperature};
|
||||
use measurements::Temperature;
|
||||
use plant_ctrl2::sipo::ShiftRegister40;
|
||||
|
||||
use anyhow::anyhow;
|
||||
@@ -34,7 +31,7 @@ use ds18b20::Ds18b20;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, Level, PinDriver, Pull, InputOutput};
|
||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, InputOutput, Level, PinDriver, Pull};
|
||||
use esp_idf_hal::pcnt::{
|
||||
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
|
||||
};
|
||||
@@ -42,12 +39,12 @@ use esp_idf_hal::prelude::Peripherals;
|
||||
use esp_idf_hal::reset::ResetReason;
|
||||
use esp_idf_svc::sntp::{self, SyncStatus};
|
||||
use esp_idf_svc::systime::EspSystemTime;
|
||||
use esp_idf_sys::{vTaskDelay, EspError, esp};
|
||||
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
||||
use one_wire_bus::OneWire;
|
||||
|
||||
use crate::bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
|
||||
use crate::config::{self, Config, WifiConfig};
|
||||
use crate::STAY_ALIVE;
|
||||
use crate::bq34z100::{Bq34z100g1Driver, Bq34z100g1};
|
||||
|
||||
pub const PLANT_COUNT: usize = 8;
|
||||
const PINS_PER_PLANT: usize = 5;
|
||||
@@ -61,6 +58,8 @@ const SPIFFS_PARTITION_NAME: &str = "storage";
|
||||
const WIFI_CONFIG_FILE: &str = "/spiffs/wifi.cfg";
|
||||
const CONFIG_FILE: &str = "/spiffs/config.cfg";
|
||||
|
||||
const TANK_MULTI_SAMPLE: usize = 11;
|
||||
|
||||
#[link_section = ".rtc.data"]
|
||||
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
|
||||
#[link_section = ".rtc.data"]
|
||||
@@ -137,7 +136,7 @@ pub trait PlantCtrlBoardInteraction {
|
||||
|
||||
fn is_day(&self) -> bool;
|
||||
fn water_temperature_c(&mut self) -> Result<f32>;
|
||||
fn tank_sensor_mv(&mut self) -> Result<u16>;
|
||||
fn tank_sensor_percent(&mut self) -> Result<u16>;
|
||||
|
||||
fn set_low_voltage_in_cycle(&mut self);
|
||||
fn clear_low_voltage_in_cycle(&mut self);
|
||||
@@ -202,7 +201,11 @@ pub struct PlantCtrlBoard<'a> {
|
||||
|
||||
impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
fn battery_state(&mut self) -> Result<BatteryState> {
|
||||
Ok(BatteryState::default())
|
||||
let state = BatteryState {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn is_day(&self) -> bool {
|
||||
@@ -236,17 +239,45 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
if sensor_data.temperature == 85_f32 {
|
||||
bail!("Ds18b20 dummy temperature returned");
|
||||
}
|
||||
Ok(sensor_data.temperature/10_f32)
|
||||
Ok(sensor_data.temperature / 10_f32)
|
||||
}
|
||||
|
||||
fn tank_sensor_mv(&mut self) -> Result<u16> {
|
||||
fn tank_sensor_percent(&mut self) -> Result<u16> {
|
||||
let delay = Delay::new_default();
|
||||
self.tank_power.set_high()?;
|
||||
//let stabilize
|
||||
delay.delay_ms(100);
|
||||
let value = self.tank_driver.read(&mut self.tank_channel)?;
|
||||
self.tank_power.set_low()?;
|
||||
Ok(value)
|
||||
unsafe {
|
||||
vTaskDelay(100);
|
||||
}
|
||||
|
||||
let mut store = [0_u16; TANK_MULTI_SAMPLE];
|
||||
for multisample in 0..TANK_MULTI_SAMPLE {
|
||||
let value = self.tank_driver.read(&mut self.tank_channel)?;
|
||||
store[multisample] = value;
|
||||
}
|
||||
store.sort();
|
||||
let median = store[6] as f32 / 1000_f32;
|
||||
let config_open_voltage_mv = 3.0;
|
||||
if config_open_voltage_mv < median {
|
||||
self.tank_power.set_low()?;
|
||||
bail!(
|
||||
"Tank sensor missing, open loop voltage {} on tank sensor input {}",
|
||||
config_open_voltage_mv,
|
||||
median
|
||||
);
|
||||
}
|
||||
|
||||
let r2 = median * 50.0 / (3.3 - median);
|
||||
let mut percent = r2 / 190_f32 * 100_f32;
|
||||
percent = percent.clamp(0.0, 100.0);
|
||||
|
||||
let quantizised = quantize_to_next_5_percent(percent as f64) as u16;
|
||||
println!(
|
||||
"Tank sensor raw {} percent {} quantized {}",
|
||||
median, percent, quantizised
|
||||
);
|
||||
return Ok(quantizised);
|
||||
}
|
||||
|
||||
fn set_low_voltage_in_cycle(&mut self) {
|
||||
@@ -258,7 +289,9 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -667,40 +700,69 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_battery(
|
||||
battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>,
|
||||
) -> Result<(), Bq34Z100Error<I2cError>> {
|
||||
let fwversion = battery_driver.fw_version()?;
|
||||
println!("fw version is {}", fwversion);
|
||||
|
||||
let design_capacity = battery_driver.design_capacity()?;
|
||||
println!("Design Capacity {}", design_capacity);
|
||||
if design_capacity == 1000 {
|
||||
println!("Still stock configuring battery, readouts are likely to be wrong!");
|
||||
}
|
||||
|
||||
let flags = battery_driver.get_flags_decoded()?;
|
||||
println!("Flags {:?}", flags);
|
||||
|
||||
let chem_id = battery_driver.chem_id()?;
|
||||
let bat_temp = battery_driver.internal_temperature()?;
|
||||
let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius();
|
||||
let voltage = battery_driver.voltage()?;
|
||||
let current = battery_driver.current()?;
|
||||
let state = battery_driver.state_of_charge()?;
|
||||
let charge_voltage = battery_driver.charge_voltage()?;
|
||||
let charge_current = battery_driver.charge_current()?;
|
||||
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
|
||||
return Result::Ok(());
|
||||
}
|
||||
|
||||
impl CreatePlantHal<'_> for PlantHal {
|
||||
fn create() -> Result<Mutex<PlantCtrlBoard<'static>>> {
|
||||
let peripherals = Peripherals::take()?;
|
||||
|
||||
|
||||
let i2c = peripherals.i2c1;
|
||||
let config = I2cConfig::new()
|
||||
.scl_enable_pullup(false)
|
||||
.sda_enable_pullup(false)
|
||||
.baudrate(10_u32.kHz().into());
|
||||
.scl_enable_pullup(false)
|
||||
.sda_enable_pullup(false)
|
||||
.baudrate(10_u32.kHz().into());
|
||||
let scl = peripherals.pins.gpio16;
|
||||
let sda = peripherals.pins.gpio17;
|
||||
|
||||
|
||||
let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap();
|
||||
let i2c_port = driver.port();
|
||||
let mut battery_driver :Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver{
|
||||
i2c :driver,
|
||||
|
||||
//let i2c_port = driver.port();
|
||||
//esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
|
||||
|
||||
let mut battery_driver: Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver {
|
||||
i2c: driver,
|
||||
delay: Delay::new_default(),
|
||||
flash_block_data : [0;32],
|
||||
};
|
||||
flash_block_data: [0; 32],
|
||||
};
|
||||
|
||||
let mut clock = PinDriver::input_output(peripherals.pins.gpio21)?;
|
||||
clock.set_pull(Pull::Floating);
|
||||
clock.set_pull(Pull::Floating).unwrap();
|
||||
let mut latch = PinDriver::input_output(peripherals.pins.gpio22)?;
|
||||
latch.set_pull(Pull::Floating);
|
||||
latch.set_pull(Pull::Floating).unwrap();
|
||||
let mut data = PinDriver::input_output(peripherals.pins.gpio19)?;
|
||||
data.set_pull(Pull::Floating);
|
||||
data.set_pull(Pull::Floating).unwrap();
|
||||
let shift_register = ShiftRegister40::new(clock.into(), latch.into(), data.into());
|
||||
for mut pin in shift_register.decompose() {
|
||||
pin.set_low().unwrap();
|
||||
}
|
||||
|
||||
let mut one_wire_pin = PinDriver::input_output_od(peripherals.pins.gpio4)?;
|
||||
one_wire_pin.set_pull(Pull::Floating);
|
||||
one_wire_pin.set_pull(Pull::Floating).unwrap();
|
||||
//TODO make to none if not possible to init
|
||||
|
||||
//init,reset rtc memory depending on cause
|
||||
@@ -765,12 +827,15 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
let nvs = EspDefaultNvsPartition::take()?;
|
||||
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
|
||||
|
||||
|
||||
let last_watering_timestamp = Mutex::new(unsafe { LAST_WATERING_TIMESTAMP });
|
||||
let consecutive_watering_plant = Mutex::new(unsafe { CONSECUTIVE_WATERING_PLANT });
|
||||
let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED });
|
||||
let tank_driver =
|
||||
AdcDriver::new(peripherals.adc1, &esp_idf_hal::adc::config::Config::new())?;
|
||||
|
||||
let adc_config = esp_idf_hal::adc::config::Config {
|
||||
resolution: esp_idf_hal::adc::config::Resolution::Resolution12Bit,
|
||||
calibration: true,
|
||||
};
|
||||
let tank_driver = AdcDriver::new(peripherals.adc1, &adc_config)?;
|
||||
let tank_channel: AdcChannelDriver<'_, { attenuation::DB_11 }, Gpio39> =
|
||||
AdcChannelDriver::new(peripherals.pins.gpio39)?;
|
||||
|
||||
@@ -779,8 +844,10 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
|
||||
let mut boot_button = PinDriver::input(peripherals.pins.gpio0)?;
|
||||
boot_button.set_pull(Pull::Floating)?;
|
||||
|
||||
let mut light = PinDriver::input_output(peripherals.pins.gpio26)?;
|
||||
light.set_pull(Pull::Floating)?;
|
||||
light.set_pull(Pull::Floating).unwrap();
|
||||
|
||||
let mut main_pump = PinDriver::input_output(peripherals.pins.gpio23)?;
|
||||
main_pump.set_pull(Pull::Floating)?;
|
||||
main_pump.set_low()?;
|
||||
@@ -794,143 +861,10 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
|
||||
println!("After stuff");
|
||||
|
||||
esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
|
||||
|
||||
let fwversion = battery_driver.fw_version();
|
||||
println!("fw version is {}", fwversion);
|
||||
|
||||
let design_capacity = battery_driver.design_capacity();
|
||||
println!("Design Capacity {}", design_capacity);
|
||||
if(design_capacity == 1000){
|
||||
println!("Still stock configuring battery");
|
||||
let status = print_battery(&mut battery_driver);
|
||||
if status.is_err() {
|
||||
println!("Error communicating with battery!! {:?}", status.err());
|
||||
}
|
||||
|
||||
//battery_driver.update_design_capacity(5999);
|
||||
|
||||
|
||||
//let mut success = battery_driver.update_design_capacity(6000);
|
||||
//if (!success){
|
||||
// bail!("Error updating capacity");
|
||||
//}
|
||||
|
||||
//success = battery_driver.update_q_max(6000);
|
||||
//if (!success){
|
||||
// bail!("Error updating max q");
|
||||
//}
|
||||
|
||||
//let energy = 25600;
|
||||
//success = battery_driver.update_design_energy(energy, 3);
|
||||
//if (!success){
|
||||
// bail!("Error updating design energy");
|
||||
//}
|
||||
|
||||
//success = battery_driver.update_cell_charge_voltage_range(3650,3650,3650);
|
||||
//if (!success){
|
||||
// bail!("Error updating cell charge voltage");
|
||||
//}
|
||||
|
||||
//success = battery_driver.update_number_of_series_cells(4);
|
||||
//if (!success){
|
||||
// bail!("Error updating number of series");
|
||||
//}
|
||||
|
||||
//charge termination here
|
||||
|
||||
|
||||
|
||||
// //RESCAP CAL_EN SCALED RSVD VOLTSEL IWAKE RSNS1 RSNS0
|
||||
// //RFACTSTEP SLEEP RMFCC NiDT NiDV QPCCLEAR GNDSEL TEMPS
|
||||
// let mut conf: u16 = 0;
|
||||
// //RESCAP
|
||||
// conf.set_bit(15, true);
|
||||
// //CAL_EN
|
||||
// conf.set_bit(14, true);
|
||||
// //SCALED
|
||||
// conf.set_bit(13, false);
|
||||
// //RSVD
|
||||
// conf.set_bit(12, false);
|
||||
// //VOLTSEL
|
||||
// conf.set_bit(11, true);
|
||||
// //IWAKE
|
||||
// conf.set_bit(10, false);
|
||||
// //RSNS1
|
||||
// conf.set_bit(9, false);
|
||||
// //RSNS0
|
||||
// conf.set_bit(8, true);
|
||||
|
||||
// //RFACTSTEP
|
||||
// conf.set_bit(7, true);
|
||||
// //SLEEP
|
||||
// conf.set_bit(6, true);
|
||||
// //RMFCC
|
||||
// conf.set_bit(5, true);
|
||||
// //NiDT
|
||||
// conf.set_bit(4, false);
|
||||
// //NiDV
|
||||
// conf.set_bit(3, false);
|
||||
// //QPCCLEAR
|
||||
// conf.set_bit(2, false);
|
||||
// //GNDSEL
|
||||
// conf.set_bit(1, true);
|
||||
// //TEMPS
|
||||
// conf.set_bit(0, false);
|
||||
|
||||
|
||||
|
||||
// let mut success = battery_driver.update_pack_configuration(conf);
|
||||
// if (!success){
|
||||
// bail!("Error updating pack config");
|
||||
// }
|
||||
|
||||
// let mut success = battery_driver.update_charge_termination_parameters(100, 25, 100, 40, 99, 95, 100, 96);
|
||||
// if (!success){
|
||||
// bail!("Error updating pack config");
|
||||
// }
|
||||
|
||||
//calibration here
|
||||
|
||||
//println!("Cc offset");
|
||||
//battery_driver.calibrate_cc_offset();
|
||||
//println!("board offset");
|
||||
//battery_driver.calibrate_board_offset();
|
||||
//println!("voltage divider");
|
||||
//battery_driver.calibrate_voltage_divider(15000.0, 4);
|
||||
|
||||
//battery_driver.calibrate_sense_resistor(1530);
|
||||
//let mut data = 0_u8;
|
||||
//data.set_bit(0, true); //led mode
|
||||
//data.set_bit(1, false); // led mode
|
||||
//data.set_bit(2, false); //led mode
|
||||
|
||||
//data.set_bit(3, true); //led always on
|
||||
|
||||
|
||||
//battery_driver.set_led_mode(data);
|
||||
//battery_driver.unsealed();
|
||||
battery_driver.it_enable();
|
||||
|
||||
loop {
|
||||
|
||||
let flags = battery_driver.get_flags_decoded();
|
||||
println!("Flags {:?}", flags);
|
||||
|
||||
let chem_id = battery_driver.chem_id();
|
||||
let bat_temp = battery_driver.internal_temperature();
|
||||
let temp_c = Temperature::from_kelvin(bat_temp as f64/10_f64).as_celsius();
|
||||
let voltage = battery_driver.voltage();
|
||||
let current = battery_driver.current();
|
||||
let state = battery_driver.state_of_charge();
|
||||
let charge_voltage = battery_driver.charge_voltage();
|
||||
let charge_current = battery_driver.charge_current();
|
||||
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
|
||||
|
||||
unsafe{
|
||||
vTaskDelay(1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
let rv = Mutex::new(PlantCtrlBoard {
|
||||
shift_register,
|
||||
last_watering_timestamp,
|
||||
@@ -952,3 +886,21 @@ impl CreatePlantHal<'_> for PlantHal {
|
||||
Ok(rv)
|
||||
}
|
||||
}
|
||||
|
||||
fn quantize_to_next_5_percent(value: f64) -> i32 {
|
||||
// Multiply by 100 to work with integer values
|
||||
let multiplied_value = (value * 100.0).round() as i32;
|
||||
|
||||
// Calculate the remainder when divided by 5
|
||||
let remainder = multiplied_value % 5;
|
||||
|
||||
// If the remainder is greater than or equal to half of 5, round up to the next 5%
|
||||
let rounded_value = if remainder >= 2 {
|
||||
multiplied_value + (5 - remainder)
|
||||
} else {
|
||||
multiplied_value - remainder
|
||||
};
|
||||
|
||||
// Divide by 100 to get back to a float
|
||||
rounded_value / 100
|
||||
}
|
||||
|
@@ -43,12 +43,12 @@
|
||||
Tank Warn below mL
|
||||
</div>
|
||||
<div>
|
||||
<input type="number" min="1" max="500000" id="tank_empty_mv">
|
||||
Tank Empty Voltage (mv)
|
||||
<input type="number" min="0" max="100" id="tank_empty_percent">
|
||||
Tank Empty Percent (% max move)
|
||||
</div>
|
||||
<div>
|
||||
<input type="number" min="1" max="500000" id="tank_full_mv">
|
||||
Tank Full Voltage (mv)
|
||||
<input type="number" min="0" max="100" id="tank_full_percent">
|
||||
Tank Full Percent (% max move)
|
||||
</div>
|
||||
|
||||
<h3>Light:</h3>
|
||||
|
Reference in New Issue
Block a user