adjust rust code to new config file, fix bq34z100 flasher
This commit is contained in:
parent
c89a617d9d
commit
74f9c17461
@ -1,68 +1,77 @@
|
||||
use std::{array::from_fn, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::PLANT_COUNT;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct Config {
|
||||
pub struct NetworkConfig {
|
||||
pub ap_ssid: heapless::String<32>,
|
||||
|
||||
pub ssid: Option<heapless::String<32>>,
|
||||
pub password: Option<heapless::String<64>>,
|
||||
|
||||
pub mqtt_url: Option<heapless::String<128>>,
|
||||
pub base_topic: Option<heapless::String<64>>,
|
||||
pub max_consecutive_pump_count: u8,
|
||||
}
|
||||
impl Default for NetworkConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ap_ssid: heapless::String::from_str("PlantCtrl Init").unwrap(),
|
||||
ssid: None,
|
||||
password: None,
|
||||
mqtt_url: None,
|
||||
base_topic: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub tank_allow_pumping_if_sensor_error: bool,
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct NightLampConfig {
|
||||
pub night_lamp_hour_start: u8,
|
||||
pub night_lamp_hour_end: u8,
|
||||
pub night_lamp_only_when_dark: bool,
|
||||
}
|
||||
impl Default for NightLampConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
night_lamp_hour_start: 19,
|
||||
night_lamp_hour_end: 2,
|
||||
night_lamp_only_when_dark: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct TankConfig {
|
||||
pub tank_sensor_enabled: bool,
|
||||
pub tank_allow_pumping_if_sensor_error: bool,
|
||||
pub tank_useable_ml: u32,
|
||||
pub tank_warn_percent: u8,
|
||||
pub tank_empty_percent: u8,
|
||||
pub tank_full_percent: u8,
|
||||
|
||||
pub night_lamp_hour_start: u8,
|
||||
pub night_lamp_hour_end: u8,
|
||||
pub night_lamp_only_when_dark: bool,
|
||||
|
||||
pub plants: [Plant; PLANT_COUNT],
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
impl Default for TankConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ap_ssid: heapless::String::from_str("Plantctrl").unwrap(),
|
||||
ssid: None,
|
||||
password: None,
|
||||
|
||||
base_topic: Some(heapless::String::from_str("plant/one").unwrap()),
|
||||
mqtt_url: Some(heapless::String::from_str("mqtt://192.168.1.1:1883").unwrap()),
|
||||
|
||||
tank_sensor_enabled: false,
|
||||
tank_allow_pumping_if_sensor_error: true,
|
||||
tank_sensor_enabled: true,
|
||||
tank_warn_percent: 50,
|
||||
night_lamp_hour_start: 21,
|
||||
night_lamp_hour_end: 2,
|
||||
night_lamp_only_when_dark: true,
|
||||
plants: from_fn(|_i| Plant::default()),
|
||||
max_consecutive_pump_count: 15,
|
||||
tank_useable_ml: 5000,
|
||||
tank_empty_percent: 0_u8,
|
||||
tank_full_percent: 100_u8,
|
||||
tank_useable_ml: 50000,
|
||||
tank_warn_percent: 40,
|
||||
tank_empty_percent: 5,
|
||||
tank_full_percent: 95,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum Mode {
|
||||
OFF,
|
||||
TargetMoisture,
|
||||
TimerOnly,
|
||||
TimerAndDeadzone,
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
|
||||
pub struct PlantControllerConfig {
|
||||
pub network: NetworkConfig,
|
||||
pub tank: TankConfig,
|
||||
pub night_lamp: NightLampConfig,
|
||||
pub plants: [PlantConfig; PLANT_COUNT],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct Plant {
|
||||
pub struct PlantConfig {
|
||||
pub mode: Mode,
|
||||
pub target_moisture: u8,
|
||||
pub pump_time_s: u16,
|
||||
@ -70,17 +79,27 @@ pub struct Plant {
|
||||
pub pump_hour_start: u8,
|
||||
pub pump_hour_end: u8,
|
||||
pub sensor_b: bool,
|
||||
pub max_consecutive_pump_count: u8,
|
||||
}
|
||||
impl Default for Plant {
|
||||
impl Default for PlantConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
target_moisture: 40,
|
||||
pump_time_s: 60,
|
||||
pump_cooldown_min: 60,
|
||||
pump_hour_start: 8,
|
||||
pump_hour_end: 20,
|
||||
mode: Mode::OFF,
|
||||
target_moisture: 40,
|
||||
pump_time_s: 30,
|
||||
pump_cooldown_min: 60,
|
||||
pump_hour_start: 9,
|
||||
pump_hour_end: 20,
|
||||
sensor_b: false,
|
||||
max_consecutive_pump_count: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub enum Mode {
|
||||
OFF,
|
||||
TargetMoisture,
|
||||
TimerOnly,
|
||||
TimerAndDeadzone,
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
config::PlantControllerConfig,
|
||||
espota::{mark_app_valid, rollback_and_reboot},
|
||||
webserver::webserver::httpd,
|
||||
};
|
||||
@ -250,7 +250,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let config: Config;
|
||||
let config: PlantControllerConfig;
|
||||
match board.get_config() {
|
||||
Ok(valid) => {
|
||||
config = valid;
|
||||
@ -271,8 +271,12 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
let mut sntp = false;
|
||||
println!("attempting to connect wifi");
|
||||
let mut ip_address: Option<String> = None;
|
||||
if config.ssid.is_some() {
|
||||
match board.wifi(config.ssid.clone().unwrap(), config.password.clone(), 10000) {
|
||||
if config.network.ssid.is_some() {
|
||||
match board.wifi(
|
||||
config.network.ssid.clone().unwrap(),
|
||||
config.network.password.clone(),
|
||||
10000,
|
||||
) {
|
||||
Ok(ip_info) => {
|
||||
ip_address = Some(ip_info.ip.to_string());
|
||||
wifi = true;
|
||||
@ -412,16 +416,16 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
let mut did_pump = false;
|
||||
match plant_to_pump {
|
||||
Some(plant) => {
|
||||
let plant_config = &config.plants[plant];
|
||||
|
||||
let state = &mut plantstate[plant];
|
||||
state.consecutive_pump_count = board.consecutive_pump_count(plant) + 1;
|
||||
board.store_consecutive_pump_count(plant, state.consecutive_pump_count);
|
||||
if state.consecutive_pump_count > config.max_consecutive_pump_count.into() {
|
||||
if state.consecutive_pump_count > plant_config.max_consecutive_pump_count.into() {
|
||||
state.not_effective = true;
|
||||
board.fault(plant, true);
|
||||
}
|
||||
|
||||
let plant_config = &config.plants[plant];
|
||||
|
||||
println!(
|
||||
"Trying to pump for {}s with pump {} now",
|
||||
plant_config.pump_time_s, plant
|
||||
@ -454,8 +458,8 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
light_state.is_day = is_day;
|
||||
light_state.out_of_work_hour = !in_time_range(
|
||||
&timezone_time,
|
||||
config.night_lamp_hour_start,
|
||||
config.night_lamp_hour_end,
|
||||
config.night_lamp.night_lamp_hour_start,
|
||||
config.night_lamp.night_lamp_hour_end,
|
||||
);
|
||||
|
||||
let state_of_charge = board.state_charge_percent().unwrap_or(0);
|
||||
@ -467,7 +471,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
light_state.battery_low = board.low_voltage_in_cycle();
|
||||
|
||||
if !light_state.out_of_work_hour {
|
||||
if config.night_lamp_only_when_dark {
|
||||
if config.night_lamp.night_lamp_only_when_dark {
|
||||
if !light_state.is_day {
|
||||
if light_state.battery_low {
|
||||
board.light(false).unwrap();
|
||||
@ -537,7 +541,7 @@ fn safe_main() -> anyhow::Result<()> {
|
||||
|
||||
fn publish_battery_state(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
) {
|
||||
let bat = BatteryState {
|
||||
voltage_milli_volt: &to_string(&board.voltage_milli_volt()),
|
||||
@ -560,9 +564,9 @@ fn publish_battery_state(
|
||||
|
||||
fn determine_tank_state(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
) -> TankState {
|
||||
if config.tank_sensor_enabled {
|
||||
if config.tank.tank_sensor_enabled {
|
||||
let mut rv: TankState = TankState {
|
||||
..Default::default()
|
||||
};
|
||||
@ -572,30 +576,30 @@ fn determine_tank_state(
|
||||
rv.raw = raw;
|
||||
return map_range(
|
||||
(
|
||||
config.tank_empty_percent as f32,
|
||||
config.tank_full_percent as f32,
|
||||
config.tank.tank_empty_percent as f32,
|
||||
config.tank.tank_full_percent as f32,
|
||||
),
|
||||
raw as f32,
|
||||
);
|
||||
})
|
||||
.and_then(|percent| {
|
||||
rv.left_ml = ((percent * config.tank_useable_ml as f32) / 100_f32) as u32;
|
||||
rv.left_ml = ((percent * config.tank.tank_useable_ml as f32) / 100_f32) as u32;
|
||||
println!(
|
||||
"Tank sensor returned mv {} as {}% leaving {} ml useable",
|
||||
rv.raw, percent as u8, rv.left_ml
|
||||
);
|
||||
if config.tank_warn_percent > percent as u8 {
|
||||
if config.tank.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
|
||||
percent as u8, config.tank.tank_warn_percent
|
||||
);
|
||||
rv.warn_level = true;
|
||||
}
|
||||
if config.tank_empty_percent < percent as u8 {
|
||||
if config.tank.tank_empty_percent < percent as u8 {
|
||||
println!(
|
||||
"Enough water, current percent is {}, minimum empty level is {}",
|
||||
percent as u8, config.tank_empty_percent
|
||||
percent as u8, config.tank.tank_empty_percent
|
||||
);
|
||||
rv.enough_water = true;
|
||||
}
|
||||
@ -624,7 +628,7 @@ fn determine_state_target_moisture_for_plant(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
plant: usize,
|
||||
state: &mut PlantState,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
tank_state: &TankState,
|
||||
cur: DateTime<Tz>,
|
||||
) {
|
||||
@ -671,7 +675,7 @@ fn determine_state_target_moisture_for_plant(
|
||||
|
||||
if a_low || b_low {
|
||||
state.dry = true;
|
||||
if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error {
|
||||
if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error {
|
||||
//ignore is ok
|
||||
} else if !tank_state.enough_water {
|
||||
state.no_water = true;
|
||||
@ -714,7 +718,7 @@ fn determine_state_timer_only_for_plant(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
plant: usize,
|
||||
state: &mut PlantState,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
tank_state: &TankState,
|
||||
cur: DateTime<Tz>,
|
||||
) {
|
||||
@ -730,7 +734,7 @@ fn determine_state_timer_only_for_plant(
|
||||
state.next_pump = Some(europe_time);
|
||||
state.cooldown = true;
|
||||
} else {
|
||||
if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error {
|
||||
if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error {
|
||||
state.do_water = true;
|
||||
} else if !tank_state.enough_water {
|
||||
state.no_water = true;
|
||||
@ -752,7 +756,7 @@ fn determine_state_timer_and_deadzone_for_plant(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
plant: usize,
|
||||
state: &mut PlantState,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
tank_state: &TankState,
|
||||
cur: DateTime<Tz>,
|
||||
) {
|
||||
@ -776,7 +780,7 @@ fn determine_state_timer_and_deadzone_for_plant(
|
||||
state.out_of_work_hour = true;
|
||||
}
|
||||
if !state.cooldown && !state.out_of_work_hour {
|
||||
if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error {
|
||||
if tank_state.sensor_error && !config.tank.tank_allow_pumping_if_sensor_error {
|
||||
state.do_water = true;
|
||||
} else if !tank_state.enough_water {
|
||||
state.no_water = true;
|
||||
@ -799,7 +803,7 @@ fn determine_next_plant(
|
||||
cur: DateTime<Tz>,
|
||||
tank_state: &TankState,
|
||||
water_frozen: bool,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
) -> Option<usize> {
|
||||
for plant in 0..PLANT_COUNT {
|
||||
@ -850,7 +854,7 @@ fn determine_next_plant(
|
||||
fn update_plant_state(
|
||||
plantstate: &mut [PlantState; PLANT_COUNT],
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
config: &Config,
|
||||
config: &PlantControllerConfig,
|
||||
) {
|
||||
for plant in 0..PLANT_COUNT {
|
||||
let state = &plantstate[plant];
|
||||
|
@ -2,7 +2,6 @@ use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
|
||||
|
||||
use ds323x::{DateTimeAccess, Ds323x};
|
||||
|
||||
use eeprom24x::page_size::No;
|
||||
use eeprom24x::{Eeprom24x, SlaveAddr};
|
||||
use embedded_hal_bus::i2c::MutexDevice;
|
||||
use embedded_svc::wifi::{
|
||||
@ -14,7 +13,6 @@ use esp_idf_hal::adc::{attenuation, Resolution};
|
||||
use esp_idf_hal::i2c::{APBTickType, I2cConfig, I2cDriver, I2cError};
|
||||
use esp_idf_hal::units::FromValueType;
|
||||
use esp_idf_svc::eventloop::EspSystemEventLoop;
|
||||
use esp_idf_svc::io::vfs;
|
||||
use esp_idf_svc::ipv4::IpInfo;
|
||||
use esp_idf_svc::mqtt::client::QoS::AtLeastOnce;
|
||||
use esp_idf_svc::mqtt::client::QoS::ExactlyOnce;
|
||||
@ -30,8 +28,7 @@ use anyhow::{anyhow, Context};
|
||||
use anyhow::{bail, Ok, Result};
|
||||
use serde::Serialize;
|
||||
use std::ffi::CString;
|
||||
use std::fs::{self, DirEntry, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::fs::{self, File};
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
@ -52,10 +49,10 @@ 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::{esp, esp_spiffs_check, f_opendir, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
||||
use esp_idf_sys::{esp, esp_spiffs_check, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
|
||||
use one_wire_bus::OneWire;
|
||||
|
||||
use crate::config::{self, Config};
|
||||
use crate::config::{self, PlantControllerConfig};
|
||||
use crate::{plant_hal, STAY_ALIVE};
|
||||
|
||||
//Only support for 8 right now!
|
||||
@ -173,7 +170,7 @@ pub struct PlantCtrlBoard<'a> {
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct FileInfo {
|
||||
filename: String,
|
||||
size:usize
|
||||
size: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
@ -193,13 +190,11 @@ impl PlantCtrlBoard<'_> {
|
||||
};
|
||||
|
||||
let mut file_system_corrupt = match error {
|
||||
OkStd(_) => {
|
||||
None
|
||||
},
|
||||
OkStd(_) => None,
|
||||
Err(err) => {
|
||||
println!("Corrupt spiffs {err:?}");
|
||||
Some(format!("{err:?}"))
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let mut iter_error = None;
|
||||
@ -216,37 +211,41 @@ impl PlantCtrlBoard<'_> {
|
||||
OkStd(file) => {
|
||||
let f = FileInfo {
|
||||
filename: file.file_name().into_string().unwrap(),
|
||||
size: file.metadata().and_then(|it| core::result::Result::Ok(it.len())).unwrap_or_default() as usize
|
||||
size: file
|
||||
.metadata()
|
||||
.and_then(|it| core::result::Result::Ok(it.len()))
|
||||
.unwrap_or_default()
|
||||
as usize,
|
||||
};
|
||||
println!("fileinfo {f:?}");
|
||||
result.push(f);
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
iter_error = Some(format!("{err:?}"));
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
file_system_corrupt = Some(format!("{err:?}"));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return FileList {
|
||||
file_system_corrupt,
|
||||
files: result,
|
||||
iter_error
|
||||
iter_error,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn delete_file(&self, filename: &str) -> Result<()> {
|
||||
let filepath = Path::new(BASE_PATH).join(Path::new(filename));
|
||||
match (fs::remove_file(filepath)){
|
||||
match fs::remove_file(filepath) {
|
||||
OkStd(_) => Ok(()),
|
||||
Err(err) => {
|
||||
bail!(format!("{err:?}"))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +255,7 @@ impl PlantCtrlBoard<'_> {
|
||||
File::create(filepath)?
|
||||
} else {
|
||||
File::open(filepath)?
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn is_day(&self) -> bool {
|
||||
@ -495,7 +494,10 @@ impl PlantCtrlBoard<'_> {
|
||||
delay.delay_ms(10);
|
||||
let unscaled = self.signal_counter.get_counter_value()? as i32;
|
||||
let hz = (unscaled as f32 * factor) as i32;
|
||||
println!("raw measure unscaled {} hz {}, plant {} sensor {:?}",unscaled, hz, plant, sensor);
|
||||
println!(
|
||||
"raw measure unscaled {} hz {}, plant {} sensor {:?}",
|
||||
unscaled, hz, plant, sensor
|
||||
);
|
||||
results[repeat] = hz;
|
||||
//println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
|
||||
}
|
||||
@ -661,13 +663,13 @@ impl PlantCtrlBoard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config(&mut self) -> Result<config::Config> {
|
||||
pub fn get_config(&mut self) -> Result<config::PlantControllerConfig> {
|
||||
let cfg = File::open(CONFIG_FILE)?;
|
||||
let config: Config = serde_json::from_reader(cfg)?;
|
||||
let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn set_config(&mut self, config: &Config) -> Result<()> {
|
||||
pub fn set_config(&mut self, config: &PlantControllerConfig) -> Result<()> {
|
||||
let mut cfg = File::create(CONFIG_FILE)?;
|
||||
serde_json::to_writer(&mut cfg, &config)?;
|
||||
println!("Wrote config config {:?}", config);
|
||||
@ -735,9 +737,17 @@ impl PlantCtrlBoard<'_> {
|
||||
config.exists()
|
||||
}
|
||||
|
||||
pub fn mqtt(&mut self, config: &Config) -> Result<()> {
|
||||
let base_topic = config.base_topic.as_ref().context("missing base topic")?;
|
||||
let mqtt_url = config.mqtt_url.as_ref().context("missing mqtt url")?;
|
||||
pub fn mqtt(&mut self, config: &PlantControllerConfig) -> Result<()> {
|
||||
let base_topic = config
|
||||
.network
|
||||
.base_topic
|
||||
.as_ref()
|
||||
.context("missing base topic")?;
|
||||
let mqtt_url = config
|
||||
.network
|
||||
.mqtt_url
|
||||
.as_ref()
|
||||
.context("missing mqtt url")?;
|
||||
|
||||
let last_will_topic = format!("{}/state", base_topic);
|
||||
let mqtt_client_config = MqttClientConfiguration {
|
||||
@ -865,7 +875,12 @@ impl PlantCtrlBoard<'_> {
|
||||
bail!("Mqtt did not fire connection callback in time");
|
||||
}
|
||||
|
||||
pub fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> {
|
||||
pub fn mqtt_publish(
|
||||
&mut self,
|
||||
config: &PlantControllerConfig,
|
||||
subtopic: &str,
|
||||
message: &[u8],
|
||||
) -> Result<()> {
|
||||
if self.mqtt_client.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
@ -880,7 +895,7 @@ impl PlantCtrlBoard<'_> {
|
||||
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.as_ref().unwrap())
|
||||
.push_str(&config.network.base_topic.as_ref().unwrap())
|
||||
.is_err()
|
||||
{
|
||||
println!("Some error assembling full_topic 1");
|
||||
|
@ -1,20 +1,23 @@
|
||||
//offer ota and config mode
|
||||
|
||||
use std::{
|
||||
collections::VecDeque, fs, io::{BufRead, Read, Write}, str::from_utf8, sync::{atomic::AtomicBool, Arc}
|
||||
io::{BufRead, BufReader, Read, Write},
|
||||
str::from_utf8,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use crate::{espota::OtaUpdate, map_range_moisture, plant_hal::FileInfo, BOARD_ACCESS};
|
||||
use anyhow::bail;
|
||||
use chrono::DateTime;
|
||||
use url::Url;
|
||||
use core::result::Result::Ok;
|
||||
use embedded_svc::http::Method;
|
||||
use esp_idf_hal::delay::Delay;
|
||||
use esp_idf_svc::{http::server::{Configuration, EspHttpConnection, EspHttpServer, Request}};
|
||||
use esp_idf_svc::http::server::{Configuration, EspHttpConnection, EspHttpServer, Request};
|
||||
use heapless::String;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::PlantControllerConfig;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SSIDList<'a> {
|
||||
@ -87,8 +90,8 @@ fn get_data(
|
||||
match a_pct {
|
||||
Ok(result) => {
|
||||
a.push(result);
|
||||
},
|
||||
Err(err) => {
|
||||
}
|
||||
Err(_) => {
|
||||
a.push(200);
|
||||
}
|
||||
}
|
||||
@ -97,19 +100,18 @@ fn get_data(
|
||||
match b_pct {
|
||||
Ok(result) => {
|
||||
b.push(result);
|
||||
},
|
||||
Err(err) => {
|
||||
}
|
||||
Err(_) => {
|
||||
b.push(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let data = LoadData {
|
||||
rtc: rtc.as_str(),
|
||||
native: native.as_str(),
|
||||
moisture_a: a,
|
||||
moisture_b: b
|
||||
moisture_b: b,
|
||||
};
|
||||
let json = serde_json::to_string(&data)?;
|
||||
|
||||
@ -122,7 +124,7 @@ fn get_config(
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let json = match board.get_config() {
|
||||
Ok(config) => serde_json::to_string(&config)?,
|
||||
Err(_) => serde_json::to_string(&Config::default())?,
|
||||
Err(_) => serde_json::to_string(&PlantControllerConfig::default())?,
|
||||
};
|
||||
anyhow::Ok(Some(json))
|
||||
}
|
||||
@ -134,7 +136,7 @@ fn set_config(
|
||||
let read = request.read(&mut buf)?;
|
||||
let actual_data = &buf[0..read];
|
||||
println!("Raw data {}", from_utf8(actual_data).unwrap());
|
||||
let config: Config = serde_json::from_slice(actual_data)?;
|
||||
let config: PlantControllerConfig = serde_json::from_slice(actual_data)?;
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
board.set_config(&config)?;
|
||||
anyhow::Ok(Some("saved".to_owned()))
|
||||
@ -215,6 +217,45 @@ fn ota(
|
||||
finalizer.restart();
|
||||
}
|
||||
|
||||
fn flash_bq(filename: &str, dryrun: bool) -> anyhow::Result<()> {
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
|
||||
let mut toggle = true;
|
||||
let delay = Delay::new(1);
|
||||
|
||||
let file_handle = board.get_file_handle(filename, false)?;
|
||||
|
||||
let mut reader = BufReader::with_capacity(512, file_handle).lines();
|
||||
let mut line = 0;
|
||||
loop {
|
||||
board.general_fault(toggle);
|
||||
toggle = !toggle;
|
||||
|
||||
delay.delay_us(2);
|
||||
line += 1;
|
||||
match reader.next() {
|
||||
Some(next) => {
|
||||
let input = next?;
|
||||
println!("flashing bq34z100 dryrun:{dryrun} line {line} payload: {input}");
|
||||
match board.flash_bq34_z100(&input, dryrun) {
|
||||
Ok(_) => {
|
||||
println!("ok")
|
||||
}
|
||||
Err(err) => {
|
||||
bail!(
|
||||
"Error flashing bq34z100 in dryrun: {dryrun} line: {line} error: {err}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
println!("Finished flashing file {line} lines processed");
|
||||
board.general_fault(false);
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
fn query_param(uri: &str, param_name: &str) -> Option<std::string::String> {
|
||||
println!("{uri} get {param_name}");
|
||||
let parsed = Url::parse(&format!("http://127.0.0.1/{uri}")).unwrap();
|
||||
@ -222,10 +263,8 @@ fn query_param(uri:&str, param_name:&str) -> Option<std::string::String>{
|
||||
match value {
|
||||
Some(found) => {
|
||||
return Some(found.1.into_owned());
|
||||
},
|
||||
None => {
|
||||
return None
|
||||
},
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,10 +330,12 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
server
|
||||
.fn_handler("/file", Method::Get, move |request| {
|
||||
let filename = query_param(request.uri(), "filename").unwrap();
|
||||
let file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(&filename, false);
|
||||
let file_handle = BOARD_ACCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_file_handle(&filename, false);
|
||||
match file_handle {
|
||||
Ok(mut file_handle) => {
|
||||
|
||||
let headers = [("Access-Control-Allow-Origin", "*")];
|
||||
let mut response = request.into_response(200, None, &headers)?;
|
||||
const BUFFER_SIZE: usize = 512;
|
||||
@ -303,7 +344,10 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
loop {
|
||||
let read = file_handle.read(&mut buffer)?;
|
||||
total_read += read;
|
||||
println!("sending {read} bytes of {total_read} for file {}", &filename);
|
||||
println!(
|
||||
"sending {read} bytes of {total_read} for file {}",
|
||||
&filename
|
||||
);
|
||||
let to_write = &buffer[0..read];
|
||||
response.write(to_write)?;
|
||||
println!("wrote {read} bytes of {total_read} for file {filename}");
|
||||
@ -313,8 +357,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
}
|
||||
drop(file_handle);
|
||||
response.flush()?;
|
||||
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
//todo set headers here for filename to be error
|
||||
let error_text = err.to_string();
|
||||
@ -328,7 +371,10 @@ 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 file_handle = BOARD_ACCESS.lock().unwrap().get_file_handle(&filename,true);
|
||||
let file_handle = BOARD_ACCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_file_handle(&filename, true);
|
||||
match file_handle {
|
||||
Ok(mut file_handle) => {
|
||||
const BUFFER_SIZE: usize = 512;
|
||||
@ -346,7 +392,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
}
|
||||
}
|
||||
cors_response(request, 200, &format!("saved {total_read} bytes"))?;
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
//todo set headers here for filename to be error
|
||||
let error_text = err.to_string();
|
||||
@ -367,80 +413,41 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
Ok(_) => {
|
||||
let info = format!("Deleted file {copy}");
|
||||
cors_response(request, 200, &info)?;
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
let info = format!("Could not delete file {copy} {err:?}");
|
||||
cors_response(request, 400, &info)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
server
|
||||
.fn_handler("/flashbattery", Method::Post, move |mut request| {
|
||||
|
||||
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||
let mut buffer: [u8; 128] = [0; 128];
|
||||
let mut line_buffer: VecDeque<u8> = VecDeque::new();
|
||||
|
||||
let is_dry_run = !request.uri().ends_with("?flash=true");
|
||||
let mut total_read: usize = 0;
|
||||
|
||||
let mut toggle = true;
|
||||
let delay = Delay::new(1);
|
||||
todo!("Write to storage before attempting to flash!");
|
||||
loop {
|
||||
delay.delay_us(2);
|
||||
let read = request.read(&mut buffer).unwrap();
|
||||
total_read += read;
|
||||
if read == 0 {
|
||||
if line_buffer.len() > 0 {
|
||||
println!("No further body but no endline");
|
||||
let mut line = std::string::String::new();
|
||||
line_buffer.read_to_string(&mut line).unwrap();
|
||||
let msg = format!("Finished reading, but there is still some leftover in buffer and no full line {line}<br>");
|
||||
println!("{}", msg);
|
||||
let mut response = request.into_status_response(400_u16).unwrap();
|
||||
response.write(msg.as_bytes()).unwrap();
|
||||
response.flush().unwrap();
|
||||
return anyhow::Ok(())
|
||||
.fn_handler("/flashbattery", Method::Post, move |request| {
|
||||
let filename = query_param(request.uri(),"filename").unwrap();
|
||||
let dryrun = true;
|
||||
match flash_bq(&filename, false) {
|
||||
Ok(_) => {
|
||||
if !dryrun {
|
||||
match flash_bq(&filename, true) {
|
||||
Ok(_) => {
|
||||
cors_response(request, 200, "Sucessfully flashed bq34z100")?;
|
||||
},
|
||||
Err(err) => {
|
||||
let info = format!("Could not flash bq34z100, could be bricked now! {filename} {err:?}");
|
||||
cors_response(request, 500, &info)?;
|
||||
},
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
cors_response(request, 200, "Sucessfully processed bq34z100")?;
|
||||
}
|
||||
let to_write = &buffer[0..read];
|
||||
|
||||
line_buffer.write_all(to_write).unwrap();
|
||||
board.general_fault(toggle);
|
||||
toggle = !toggle;
|
||||
loop {
|
||||
let has_line = line_buffer.contains(&b'\n');
|
||||
if !has_line {
|
||||
break;
|
||||
}
|
||||
let mut line = std::string::String::new();
|
||||
line_buffer.read_line(&mut line)?;
|
||||
let line2 = &line[0..line.len()-1];
|
||||
println!("Processing dry:{} line {}", is_dry_run, line2);
|
||||
let validate = board.flash_bq34_z100(&line2, is_dry_run);
|
||||
delay.delay_us(2);
|
||||
if validate.is_err() {
|
||||
let mut response = request.into_status_response(400_u16).unwrap();
|
||||
let err = validate.unwrap_err();
|
||||
let err_str = err.to_string();
|
||||
let err_msg = err_str.as_bytes();
|
||||
println!("Error writing {}", err_str);
|
||||
response
|
||||
.write(err_msg)
|
||||
.unwrap();
|
||||
return anyhow::Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut response = request.into_status_response(200_u16).unwrap();
|
||||
let msg = format!("Finished writing {total_read} bytes<br>");
|
||||
response.write(msg.as_bytes()).unwrap();
|
||||
board.general_fault(false);
|
||||
},
|
||||
Err(err) => {
|
||||
let info = format!("Could not process firmware file for, bq34z100, refusing to flash! {filename} {err:?}");
|
||||
cors_response(request, 500, &info)?;
|
||||
},
|
||||
};
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
@ -448,7 +455,7 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
server
|
||||
.fn_handler("/", Method::Get, move |request| {
|
||||
let mut response = request.into_ok_response()?;
|
||||
response.write(include_bytes!("index.html"))?;let mut buf = [0_u8; 3072];
|
||||
response.write(include_bytes!("index.html"))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
@ -458,7 +465,6 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
.into_ok_response()?
|
||||
.write(include_bytes!("favicon.ico"))?;
|
||||
anyhow::Ok(())
|
||||
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
@ -467,13 +473,16 @@ pub fn httpd(_reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
||||
.into_ok_response()?
|
||||
.write(include_bytes!("bundle.js"))?;
|
||||
anyhow::Ok(())
|
||||
|
||||
})
|
||||
.unwrap();
|
||||
server
|
||||
}
|
||||
|
||||
fn cors_response(request: Request<&mut EspHttpConnection>, status: u16, body: &str) -> Result<(), anyhow::Error>{
|
||||
fn cors_response(
|
||||
request: Request<&mut EspHttpConnection>,
|
||||
status: u16,
|
||||
body: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let headers = [("Access-Control-Allow-Origin", "*")];
|
||||
let mut response = request.into_response(status, None, &headers)?;
|
||||
response.write(body.as_bytes())?;
|
||||
|
Loading…
Reference in New Issue
Block a user