fix rtc storage

This commit is contained in:
2024-02-15 23:00:05 +01:00
parent 060a1cc32d
commit 680d1c3aaf
12 changed files with 8891 additions and 7909 deletions

View File

@@ -1,13 +1,15 @@
use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
//mod config;
use chrono_tz::Europe::Berlin;
use embedded_svc::wifi::{
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
};
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::AtLeastOnce;
use esp_idf_svc::mqtt::client::QoS::ExactlyOnce;
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
use esp_idf_svc::wifi::EspWifi;
@@ -16,19 +18,19 @@ use plant_ctrl2::sipo::ShiftRegister40;
use anyhow::anyhow;
use anyhow::{bail, Ok, Result};
use serde::{Deserialize, Serialize};
use std::ffi::CString;
use std::fs::File;
use std::path::Path;
use chrono::{DateTime, NaiveDateTime, Utc};
use ds18b20::Ds18b20;
use std::result::Result::Ok as OkStd;
use std::str::FromStr;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use chrono::{DateTime, NaiveDateTime, Utc};
use ds18b20::Ds18b20;
use embedded_hal::digital::v2::OutputPin;
use embedded_hal::digital::OutputPin;
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, InputOutput, Level, PinDriver, Pull};
@@ -39,10 +41,9 @@ 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::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
use esp_idf_sys::{esp, 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;
@@ -51,8 +52,8 @@ const PINS_PER_PLANT: usize = 5;
const PLANT_PUMP_OFFSET: usize = 0;
const PLANT_FAULT_OFFSET: usize = 1;
const PLANT_MOIST_PUMP_OFFSET: usize = 2;
const PLANT_MOIST_B_OFFSET: usize = 3;
const PLANT_MOIST_A_OFFSET: usize = 4;
const PLANT_MOIST_A_OFFSET: usize = 3;
const PLANT_MOIST_B_OFFSET: usize = 4;
const SPIFFS_PARTITION_NAME: &str = "storage";
const WIFI_CONFIG_FILE: &str = "/spiffs/wifi.cfg";
@@ -67,43 +68,6 @@ static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
#[link_section = ".rtc.data"]
static mut LOW_VOLTAGE_DETECTED: bool = false;
#[derive(Serialize, Deserialize, Debug)]
pub struct BatteryState {
pub state_charge_percent: u8,
max_error_percent: u8,
remaining_milli_ampere_hour: u32,
max_milli_ampere_hour: u32,
design_milli_ampere_hour: u32,
voltage_milli_volt: u16,
average_current_milli_ampere: u16,
temperature_tenth_kelvin: u32,
average_time_to_empty_minute: u16,
average_time_to_full_minute: u16,
average_discharge_power_cycle_milli_watt: u16,
cycle_count: u16,
state_health_percent: u8,
}
impl Default for BatteryState {
fn default() -> Self {
BatteryState {
state_charge_percent: 50,
max_error_percent: 100,
remaining_milli_ampere_hour: 100,
max_milli_ampere_hour: 200,
design_milli_ampere_hour: 200,
voltage_milli_volt: 12,
average_current_milli_ampere: 50,
temperature_tenth_kelvin: 1337,
average_time_to_empty_minute: 123,
average_time_to_full_minute: 123,
average_discharge_power_cycle_milli_watt: 123,
cycle_count: 123,
state_health_percent: 90,
}
}
}
pub struct FileSystemSizeInfo {
pub total_size: usize,
pub used_size: usize,
@@ -125,12 +89,24 @@ pub enum Sensor {
}
pub trait PlantCtrlBoardInteraction {
fn time(&mut self) -> Result<chrono::DateTime<Utc>>;
fn wifi(&mut self, ssid: &str, password: Option<&str>, max_wait: u32) -> Result<()>;
fn wifi(
&mut self,
ssid: heapless::String<32>,
password: Option<heapless::String<64>>,
max_wait: u32,
) -> Result<()>;
fn sntp(&mut self, max_wait: u32) -> Result<chrono::DateTime<Utc>>;
fn mount_file_system(&mut self) -> Result<()>;
fn file_system_size(&mut self) -> Result<FileSystemSizeInfo>;
fn battery_state(&mut self) -> Result<BatteryState>;
fn state_charge_percent(&mut self) -> Result<u8>;
fn remaining_milli_ampere_hour(&mut self) -> Result<u16>;
fn max_milli_ampere_hour(&mut self) -> Result<u16>;
fn design_milli_ampere_hour(&mut self) -> Result<u16>;
fn voltage_milli_volt(&mut self) -> Result<u16>;
fn average_current_milli_ampere(&mut self) -> Result<i16>;
fn cycle_count(&mut self) -> Result<u16>;
fn state_health_percent(&mut self) -> Result<u8>;
fn general_fault(&mut self, enable: bool);
@@ -168,6 +144,7 @@ pub trait PlantCtrlBoardInteraction {
fn test(&mut self) -> Result<()>;
fn is_wifi_config_file_existant(&mut self) -> bool;
fn mqtt(&mut self, config: &Config) -> Result<()>;
fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()>;
}
pub trait CreatePlantHal<'a> {
@@ -182,8 +159,6 @@ pub struct PlantCtrlBoard<'a> {
PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
>,
consecutive_watering_plant: Mutex<[u32; PLANT_COUNT]>,
last_watering_timestamp: Mutex<[i64; PLANT_COUNT]>,
low_voltage_detected: Mutex<bool>,
tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
@@ -197,17 +172,10 @@ pub struct PlantCtrlBoard<'a> {
pub wifi_driver: EspWifi<'a>,
one_wire_bus: OneWire<PinDriver<'a, Gpio4, esp_idf_hal::gpio::InputOutput>>,
mqtt_client: Option<EspMqttClient<'a>>,
battery_driver: Bq34z100g1Driver<I2cDriver<'a>, Delay>,
}
impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
fn battery_state(&mut self) -> Result<BatteryState> {
let state = BatteryState {
..Default::default()
};
Ok(state)
}
fn is_day(&self) -> bool {
self.solar_is_day.get_level().into()
}
@@ -281,11 +249,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
}
fn set_low_voltage_in_cycle(&mut self) {
*self.low_voltage_detected.get_mut().unwrap() = true;
unsafe {
LOW_VOLTAGE_DETECTED = true;
}
}
fn clear_low_voltage_in_cycle(&mut self) {
*self.low_voltage_detected.get_mut().unwrap() = false;
unsafe {
LOW_VOLTAGE_DETECTED = false;
}
}
fn light(&mut self, enable: bool) -> Result<()> {
@@ -311,15 +283,21 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
}
fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>) {
self.last_watering_timestamp.get_mut().unwrap()[plant] = time.timestamp_millis();
unsafe {
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
}
}
fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
self.consecutive_watering_plant.get_mut().unwrap()[plant] = count;
unsafe {
CONSECUTIVE_WATERING_PLANT[plant] = count;
}
}
fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
return self.consecutive_watering_plant.get_mut().unwrap()[plant];
unsafe {
return CONSECUTIVE_WATERING_PLANT[plant];
}
}
fn fault(&self, plant: usize, enable: bool) {
@@ -330,7 +308,9 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
}
fn low_voltage_in_cycle(&mut self) -> bool {
return *self.low_voltage_detected.get_mut().unwrap();
unsafe {
return LOW_VOLTAGE_DETECTED;
}
}
fn any_pump(&mut self, enable: bool) -> Result<()> {
@@ -376,7 +356,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
let delay = Delay::new_default();
let measurement = 100;
let factor = 1000 / 100;
let factor = 1000 as f32 / measurement as f32;
self.shift_register.decompose()[index].set_high().unwrap();
//give some time to stabilize
@@ -386,7 +366,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
self.signal_counter.counter_pause()?;
self.shift_register.decompose()[index].set_low().unwrap();
let unscaled = self.signal_counter.get_counter_value()? as i32;
let hz = unscaled * factor;
let hz = (unscaled as f32 * factor) as i32;
println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
Ok(hz)
}
@@ -397,7 +377,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
fn wifi_ap(&mut self) -> Result<()> {
let apconfig = AccessPointConfiguration {
ssid: "PlantCtrl".into(),
ssid: heapless::String::from_str("PlantCtrl").unwrap(),
auth_method: AuthMethod::None,
ssid_hidden: false,
..Default::default()
@@ -409,14 +389,19 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
Ok(())
}
fn wifi(&mut self, ssid: &str, password: Option<&str>, max_wait: u32) -> Result<()> {
fn wifi(
&mut self,
ssid: heapless::String<32>,
password: Option<heapless::String<64>>,
max_wait: u32,
) -> Result<()> {
match password {
Some(pw) => {
//TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
self.wifi_driver.set_configuration(&Configuration::Client(
ClientConfiguration {
ssid: ssid.into(),
password: pw.into(),
ssid: ssid,
password: pw,
..Default::default()
},
))?;
@@ -424,7 +409,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
None => {
self.wifi_driver
.set_configuration(&Configuration::Client(ClientConfiguration {
ssid: ssid.into(),
ssid: ssid,
auth_method: AuthMethod::None,
..Default::default()
}))
@@ -439,7 +424,6 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
let mut counter = 0_u32;
while !self.wifi_driver.is_connected()? {
println!("Waiting for station connection");
//TODO blink status?
delay.delay_ms(250);
counter += 250;
if counter > max_wait {
@@ -539,7 +523,11 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
fn get_config(&mut self) -> Result<config::Config> {
let cfg = File::open(CONFIG_FILE)?;
let config: Config = serde_json::from_reader(cfg)?;
let mut config: Config = serde_json::from_reader(cfg)?;
//remove duplicate end of topic
if config.base_topic.ends_with("/") {
config.base_topic.pop();
}
Ok(config)
}
@@ -627,8 +615,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
}
fn mqtt(&mut self, config: &Config) -> Result<()> {
//FIXME testament
let last_will_topic = format!("{}/state", config.base_topic);
let mqtt_client_config = MqttClientConfiguration {
lwt: Some(LwtConfiguration {
topic: &last_will_topic,
payload: "lost".as_bytes(),
qos: AtLeastOnce,
retain: true,
}),
//room for improvement
..Default::default()
};
@@ -643,32 +638,32 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
let round_trip_topic_copy = round_trip_topic.clone();
let round_trip_ok_copy = round_trip_ok.clone();
let mut client =
EspMqttClient::new(&config.mqtt_url, &mqtt_client_config, move |handler| {
match handler {
Err(err) => println!("Ignoring damaged message {}", err),
core::result::Result::Ok(event) => {
match event {
embedded_svc::mqtt::client::Event::Received(msg) => {
let data = String::from_utf8_lossy(msg.data());
if let Some(topic) = msg.topic() {
//todo use enums
if topic.eq(round_trip_topic_copy.as_str()) {
round_trip_ok_copy
.store(true, std::sync::atomic::Ordering::Relaxed);
} else if topic.eq(stay_alive_topic_copy.as_str()) {
let value = data.eq_ignore_ascii_case("true")
|| data.eq_ignore_ascii_case("1");
println!("Received stay alive with value {}", value);
STAY_ALIVE
.store(value, std::sync::atomic::Ordering::Relaxed);
} else {
println!("Unknown topic recieved {}", topic);
}
}
EspMqttClient::new_cb(&config.mqtt_url, &mqtt_client_config, move |event| {
let payload = event.payload();
match payload {
embedded_svc::mqtt::client::EventPayload::Received {
id: _,
topic,
data,
details: _,
} => {
let data = String::from_utf8_lossy(data);
if let Some(topic) = topic {
//todo use enums
if topic.eq(round_trip_topic_copy.as_str()) {
round_trip_ok_copy
.store(true, std::sync::atomic::Ordering::Relaxed);
} else if topic.eq(stay_alive_topic_copy.as_str()) {
let value = data.eq_ignore_ascii_case("true")
|| data.eq_ignore_ascii_case("1");
println!("Received stay alive with value {}", value);
STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
} else {
println!("Unknown topic recieved {}", topic);
}
_ => {}
}
}
_ => {}
}
})?;
//subscribe to roundtrip
@@ -698,15 +693,111 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
}
bail!("Mqtt did not complete roundtrip in time");
}
fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> {
if !subtopic.starts_with("/") {
println!("Subtopic without / at start {}", subtopic);
bail!("Subtopic without / at start {}", subtopic);
}
if subtopic.len() > 192 {
println!("Subtopic exceeds 192 chars {}", subtopic);
bail!("Subtopic exceeds 192 chars {}", subtopic);
}
if self.mqtt_client.is_none() {
println!("Not connected to mqtt");
bail!("Not connected to mqtt");
}
let client = self.mqtt_client.as_mut().unwrap();
let mut full_topic: heapless::String<256> = heapless::String::new();
if full_topic.push_str(&config.base_topic).is_err() {
println!("Some error assembling full_topic 1");
bail!("Some error assembling full_topic 1")
};
if full_topic.push_str(subtopic).is_err() {
println!("Some error assembling full_topic 2");
bail!("Some error assembling full_topic 2")
};
client.publish(
&full_topic,
embedded_svc::mqtt::client::QoS::ExactlyOnce,
true,
message,
)?;
return Ok(());
}
fn state_charge_percent(&mut self) -> Result<u8> {
match self.battery_driver.state_of_charge() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading SoC {:?}", err),
}
}
fn remaining_milli_ampere_hour(&mut self) -> Result<u16> {
match self.battery_driver.remaining_capacity() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading Remaining Capacity {:?}", err),
}
}
fn max_milli_ampere_hour(&mut self) -> Result<u16> {
match self.battery_driver.full_charge_capacity() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading Full Charge Capacity {:?}", err),
}
}
fn design_milli_ampere_hour(&mut self) -> Result<u16> {
match self.battery_driver.design_capacity() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading Design Capacity {:?}", err),
}
}
fn voltage_milli_volt(&mut self) -> Result<u16> {
return match self.battery_driver.voltage() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading voltage {:?}", err),
};
}
fn average_current_milli_ampere(&mut self) -> Result<i16> {
match self.battery_driver.average_current() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading Average Current {:?}", err),
}
}
fn cycle_count(&mut self) -> Result<u16> {
match self.battery_driver.cycle_count() {
OkStd(r) => Ok(r),
Err(err) => bail!("Error reading Cycle Count {:?}", err),
}
}
fn state_health_percent(&mut self) -> Result<u8> {
match self.battery_driver.state_of_health() {
OkStd(r) => Ok(r as u8),
Err(err) => bail!("Error reading State of Health {:?}", err),
}
}
}
fn print_battery(
battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>,
) -> Result<(), Bq34Z100Error<I2cError>> {
let fwversion = battery_driver.fw_version()?;
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
println!("Firmeware {:?}", e);
0
});
println!("fw version is {}", fwversion);
let design_capacity = battery_driver.design_capacity()?;
let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| {
println!("Design capacity {:?}", e);
0
});
println!("Design Capacity {}", design_capacity);
if design_capacity == 1000 {
println!("Still stock configuring battery, readouts are likely to be wrong!");
@@ -715,15 +806,39 @@ fn print_battery(
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 chem_id = battery_driver.chem_id().unwrap_or_else(|e| {
println!("Chemid {:?}", e);
0
});
let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| {
println!("Bat Temp {:?}", e);
0
});
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()?;
let voltage = battery_driver.voltage().unwrap_or_else(|e| {
println!("Bat volt {:?}", e);
0
});
let current = battery_driver.current().unwrap_or_else(|e| {
println!("Bat current {:?}", e);
0
});
let state = battery_driver.state_of_charge().unwrap_or_else(|e| {
println!("Bat Soc {:?}", e);
0
});
let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| {
println!("Bat Charge Volt {:?}", e);
0
});
let charge_current = battery_driver.charge_current().unwrap_or_else(|e| {
println!("Bat Charge Current {:?}", e);
0
});
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
let _ = battery_driver.unsealed();
let _ = battery_driver.it_enable();
return Result::Ok(());
}
@@ -741,8 +856,8 @@ impl CreatePlantHal<'_> for PlantHal {
let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap();
//let i2c_port = driver.port();
//esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
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,
@@ -789,6 +904,30 @@ impl CreatePlantHal<'_> for PlantHal {
};
} else {
println!("Keeping RTC store");
unsafe {
println!(
"Current low voltage detection is {:?}",
LOW_VOLTAGE_DETECTED
);
for i in 0..PLANT_COUNT {
let smaller_time = LAST_WATERING_TIMESTAMP[i];
let local_time = NaiveDateTime::from_timestamp_millis(smaller_time)
.ok_or(anyhow!("could not convert timestamp"))?;
let utc_time = local_time.and_utc();
let europe_time = utc_time.with_timezone(&Berlin);
println!(
"LAST_WATERING_TIMESTAMP[{}] = {} as europe {}",
i, LAST_WATERING_TIMESTAMP[i], europe_time
);
}
for i in 0..PLANT_COUNT {
println!(
"CONSECUTIVE_WATERING_PLANT[{}] = {}",
i, CONSECUTIVE_WATERING_PLANT[i]
);
}
}
}
let mut counter_unit1 = PcntDriver::new(
@@ -806,10 +945,10 @@ impl CreatePlantHal<'_> for PlantHal {
PinIndex::Pin0,
PinIndex::Pin1,
&PcntChannelConfig {
lctrl_mode: PcntControlMode::Reverse,
lctrl_mode: PcntControlMode::Keep,
hctrl_mode: PcntControlMode::Keep,
pos_mode: PcntCountMode::Decrement,
neg_mode: PcntCountMode::Increment,
pos_mode: PcntCountMode::Increment,
neg_mode: PcntCountMode::Hold,
counter_h_lim: i16::MAX,
counter_l_lim: 0,
},
@@ -827,8 +966,6 @@ 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 adc_config = esp_idf_hal::adc::config::Config {
@@ -867,8 +1004,6 @@ impl CreatePlantHal<'_> for PlantHal {
}
let rv = Mutex::new(PlantCtrlBoard {
shift_register,
last_watering_timestamp,
consecutive_watering_plant,
low_voltage_detected,
tank_driver,
tank_channel,
@@ -882,6 +1017,7 @@ impl CreatePlantHal<'_> for PlantHal {
signal_counter: counter_unit1,
wifi_driver,
mqtt_client: None,
battery_driver,
});
Ok(rv)
}