cleanup main and network state handling
This commit is contained in:
parent
4f4d15e4a4
commit
3fe9aaeb6f
331
rust/src/main.rs
331
rust/src/main.rs
@ -2,9 +2,9 @@ use std::{
|
||||
fmt::Display,
|
||||
sync::{atomic::AtomicBool, Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use chrono::{DateTime, Datelike, Timelike};
|
||||
use std::sync::MutexGuard;
|
||||
use anyhow::bail;
|
||||
use chrono::{DateTime, Datelike, Timelike, Utc};
|
||||
use chrono_tz::Tz;
|
||||
use chrono_tz::Tz::UTC;
|
||||
use esp_idf_hal::delay::Delay;
|
||||
@ -20,14 +20,12 @@ use log::{log, LogMessage};
|
||||
use once_cell::sync::Lazy;
|
||||
use plant_hal::{PlantCtrlBoard, PlantHal, PLANT_COUNT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{config::PlantControllerConfig, webserver::webserver::httpd};
|
||||
mod config;
|
||||
mod log;
|
||||
pub mod plant_hal;
|
||||
mod plant_state;
|
||||
mod tank;
|
||||
pub mod util;
|
||||
|
||||
use plant_state::PlantState;
|
||||
use tank::*;
|
||||
@ -71,6 +69,13 @@ struct LightState {
|
||||
is_day: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
|
||||
///mqtt stuct to track pump activities
|
||||
struct PumpInfo{
|
||||
enabled: bool,
|
||||
pump_ineffective: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
/// humidity sensor error
|
||||
enum SensorError {
|
||||
@ -79,7 +84,21 @@ enum SensorError {
|
||||
OpenCircuit { hz: f32, min: f32 },
|
||||
}
|
||||
|
||||
fn safe_main() -> Result<()> {
|
||||
#[derive(Serialize, Debug, PartialEq)]
|
||||
enum SntpMode {
|
||||
OFFLINE,
|
||||
SYNC{
|
||||
current: DateTime<Utc>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, PartialEq)]
|
||||
enum NetworkMode{
|
||||
WIFI {sntp: SntpMode, mqtt: bool, ip_address: String},
|
||||
OFFLINE,
|
||||
}
|
||||
|
||||
fn safe_main() -> anyhow::Result<()> {
|
||||
// It is necessary to call this function once. Otherwise, some patches to the runtime
|
||||
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
||||
esp_idf_svc::sys::link_patches();
|
||||
@ -145,7 +164,7 @@ fn safe_main() -> Result<()> {
|
||||
"",
|
||||
);
|
||||
|
||||
let mut cur = board
|
||||
let cur = board
|
||||
.get_rtc_time()
|
||||
.or_else(|err| {
|
||||
println!("rtc module error: {:?}", err);
|
||||
@ -214,55 +233,15 @@ fn safe_main() -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
let mut wifi = false;
|
||||
let mut mqtt = false;
|
||||
let mut sntp = false;
|
||||
println!("attempting to connect wifi");
|
||||
let mut ip_address: Option<String> = None;
|
||||
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;
|
||||
|
||||
match board.sntp(1000 * 10) {
|
||||
Ok(new_time) => {
|
||||
println!("Using time from sntp");
|
||||
let _ = board.set_rtc_time(&new_time);
|
||||
cur = new_time;
|
||||
sntp = true;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("sntp error: {}", err);
|
||||
board.general_fault(true);
|
||||
}
|
||||
}
|
||||
if config.network.mqtt_url.is_some() {
|
||||
match board.mqtt(&config) {
|
||||
Ok(_) => {
|
||||
println!("Mqtt connection ready");
|
||||
mqtt = true;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Could not connect mqtt due to {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Offline mode");
|
||||
board.general_fault(true);
|
||||
}
|
||||
}
|
||||
let network_mode = if config.network.ssid.is_some() {
|
||||
try_connect_wifi_sntp_mqtt(&mut board, &config)
|
||||
} else {
|
||||
println!("No wifi configured");
|
||||
}
|
||||
NetworkMode::OFFLINE
|
||||
};
|
||||
|
||||
if !wifi && to_config {
|
||||
if matches!(network_mode, NetworkMode::OFFLINE) && to_config {
|
||||
println!("Could not connect to station and config mode forced, switching to ap mode!");
|
||||
match board.wifi_ap(Some(config.network.ap_ssid.clone())) {
|
||||
Ok(_) => {
|
||||
@ -288,36 +267,17 @@ fn safe_main() -> Result<()> {
|
||||
timezone_time
|
||||
);
|
||||
|
||||
if mqtt {
|
||||
let ip_string = ip_address.unwrap_or("N/A".to_owned());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/address", ip_string.as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/githash", version.git_hash.as_bytes());
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/firmware/buildtime",
|
||||
version.build_time.as_bytes(),
|
||||
);
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/firmware/last_online",
|
||||
timezone_time.to_rfc3339().as_bytes(),
|
||||
);
|
||||
let _ = board.mqtt_publish(&config, "/firmware/ota_state", ota_state_string.as_bytes());
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/firmware/partition_address",
|
||||
format!("{:#06x}", address).as_bytes(),
|
||||
);
|
||||
let _ = board.mqtt_publish(&config, "/state", "online".as_bytes());
|
||||
|
||||
if let NetworkMode::WIFI { ref ip_address, .. } = network_mode {
|
||||
publish_firmware_info(version, address, ota_state_string, &mut board, &config, &ip_address, timezone_time);
|
||||
publish_battery_state(&mut board, &config);
|
||||
}
|
||||
|
||||
|
||||
log(
|
||||
LogMessage::StartupInfo,
|
||||
wifi as u32,
|
||||
sntp as u32,
|
||||
&mqtt.to_string(),
|
||||
matches!(network_mode, NetworkMode::WIFI { .. }) as u32,
|
||||
matches!(network_mode, NetworkMode::WIFI { sntp: SntpMode::SYNC { .. }, .. }) as u32,
|
||||
matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. }).to_string().as_str(),
|
||||
"",
|
||||
);
|
||||
|
||||
@ -369,54 +329,18 @@ fn safe_main() -> Result<()> {
|
||||
|
||||
let mut water_frozen = false;
|
||||
|
||||
//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();
|
||||
match &temp {
|
||||
Ok(res) => {
|
||||
println!("Water temp is {}", res);
|
||||
break temp;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Could not get water temp {} attempt {}", err, attempt)
|
||||
}
|
||||
}
|
||||
if attempt == 5 {
|
||||
break temp;
|
||||
}
|
||||
attempt += 1;
|
||||
};
|
||||
let water_temp = obtain_tank_temperature(&mut board);
|
||||
if let Ok(res) = water_temp {
|
||||
if res < WATER_FROZEN_THRESH {
|
||||
water_frozen = true;
|
||||
}
|
||||
}
|
||||
|
||||
match serde_json::to_string(&tank_state.as_mqtt_info(&config.tank, water_temp)) {
|
||||
Ok(state) => {
|
||||
let _ = board.mqtt_publish(&config, "/water", state.as_bytes());
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing tankstate {}", err);
|
||||
}
|
||||
};
|
||||
publish_tank_state(&mut board, &config, &tank_state, &water_temp);
|
||||
|
||||
let plantstate: [PlantState; PLANT_COUNT] =
|
||||
core::array::from_fn(|i| PlantState::read_hardware_state(i, &mut board, &config.plants[i]));
|
||||
for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(&config.plants).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.mqtt_publish(&config, &plant_topic, state.as_bytes());
|
||||
//reduce speed as else messages will be dropped
|
||||
Delay::new_default().delay_ms(200);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing plant state {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
publish_plant_states(&mut board, &config, &timezone_time, &plantstate);
|
||||
|
||||
let pump_required = plantstate
|
||||
.iter()
|
||||
@ -426,24 +350,24 @@ fn safe_main() -> Result<()> {
|
||||
if pump_required {
|
||||
log(LogMessage::EnableMain, dry_run as u32, 0, "", "");
|
||||
if !dry_run {
|
||||
board.any_pump(true)?; // what does this do? Does it need to be reset?
|
||||
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(&config.plants).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);
|
||||
//TODO(judge) where to put this?
|
||||
//if state.consecutive_pump_count > plant_config.max_consecutive_pump_count as u32 {
|
||||
// log(
|
||||
// log::LogMessage::ConsecutivePumpCountLimit,
|
||||
// state.consecutive_pump_count as u32,
|
||||
// plant_config.max_consecutive_pump_count as u32,
|
||||
// &plant.to_string(),
|
||||
// "",
|
||||
// );
|
||||
// state.not_effective = true;
|
||||
// board.fault(plant, true);
|
||||
//}
|
||||
|
||||
let pump_ineffective = pump_count > plant_config.max_consecutive_pump_count as u32;
|
||||
if pump_ineffective {
|
||||
log(
|
||||
LogMessage::ConsecutivePumpCountLimit,
|
||||
pump_count as u32,
|
||||
plant_config.max_consecutive_pump_count as u32,
|
||||
&(plant_id+1).to_string(),
|
||||
"",
|
||||
);
|
||||
board.fault(plant_id, true);
|
||||
}
|
||||
log(
|
||||
LogMessage::PumpPlant,
|
||||
(plant_id + 1) as u32,
|
||||
@ -454,17 +378,25 @@ fn safe_main() -> Result<()> {
|
||||
board.store_last_pump_time(plant_id, cur);
|
||||
board.last_pump_time(plant_id);
|
||||
//state.active = true;
|
||||
|
||||
pump_info(&mut board, &config, plant_id, true, pump_ineffective);
|
||||
|
||||
if !dry_run {
|
||||
board.pump(plant_id, true)?;
|
||||
Delay::new_default().delay_ms(1000 * plant_config.pump_time_s as u32);
|
||||
board.pump(plant_id, false)?;
|
||||
}
|
||||
pump_info(&mut board, &config, 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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
@ -552,6 +484,143 @@ fn safe_main() -> Result<()> {
|
||||
board.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64);
|
||||
}
|
||||
|
||||
fn obtain_tank_temperature(board: &mut MutexGuard<PlantCtrlBoard>) -> 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();
|
||||
match &temp {
|
||||
Ok(res) => {
|
||||
println!("Water temp is {}", res);
|
||||
break temp;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Could not get water temp {} attempt {}", err, attempt)
|
||||
}
|
||||
}
|
||||
if attempt == 5 {
|
||||
break temp;
|
||||
}
|
||||
attempt += 1;
|
||||
};
|
||||
water_temp
|
||||
}
|
||||
|
||||
fn publish_tank_state(board: &mut MutexGuard<PlantCtrlBoard>, config: &PlantControllerConfig, tank_state: &TankState, water_temp: &anyhow::Result<f32>) {
|
||||
match serde_json::to_string(&tank_state.as_mqtt_info(&config.tank, water_temp)) {
|
||||
Ok(state) => {
|
||||
let _ = board.mqtt_publish(&config, "/water", state.as_bytes());
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing tankstate {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn publish_plant_states(board: &mut MutexGuard<PlantCtrlBoard>, config: &PlantControllerConfig, timezone_time: &DateTime<Tz>, plantstate: &[PlantState; 8]) {
|
||||
for (plant_id, (plant_state, plant_conf)) in plantstate.iter().zip(&config.plants).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.mqtt_publish(&config, &plant_topic, state.as_bytes());
|
||||
//reduce speed as else messages will be dropped
|
||||
Delay::new_default().delay_ms(200);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing plant state {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn publish_firmware_info(version: VersionInfo, address: u32, ota_state_string: &str, board: &mut MutexGuard<PlantCtrlBoard>, config: &PlantControllerConfig, ip_address: &String, timezone_time: DateTime<Tz>) {
|
||||
let _ = board.mqtt_publish(&config, "/firmware/address", ip_address.as_bytes());
|
||||
let _ = board.mqtt_publish(&config, "/firmware/githash", version.git_hash.as_bytes());
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/firmware/buildtime",
|
||||
version.build_time.as_bytes(),
|
||||
);
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/firmware/last_online",
|
||||
timezone_time.to_rfc3339().as_bytes(),
|
||||
);
|
||||
let _ = board.mqtt_publish(&config, "/firmware/ota_state", ota_state_string.as_bytes());
|
||||
let _ = board.mqtt_publish(
|
||||
&config,
|
||||
"/firmware/partition_address",
|
||||
format!("{:#06x}", address).as_bytes(),
|
||||
);
|
||||
let _ = board.mqtt_publish(&config, "/state", "online".as_bytes());
|
||||
}
|
||||
|
||||
fn try_connect_wifi_sntp_mqtt(board: &mut MutexGuard<PlantCtrlBoard>, config: &PlantControllerConfig) -> NetworkMode{
|
||||
match board.wifi(
|
||||
config.network.ssid.clone().unwrap(),
|
||||
config.network.password.clone(),
|
||||
10000,
|
||||
) {
|
||||
Ok(ip_info) => {
|
||||
let sntp_mode: SntpMode = match board.sntp(1000 * 10) {
|
||||
Ok(new_time) => {
|
||||
println!("Using time from sntp");
|
||||
let _ = board.set_rtc_time(&new_time);
|
||||
SntpMode::SYNC {current: new_time}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("sntp error: {}", err);
|
||||
board.general_fault(true);
|
||||
SntpMode::OFFLINE
|
||||
}
|
||||
};
|
||||
let mqtt_connected = if let Some(_) = config.network.mqtt_url {
|
||||
match board.mqtt(&config) {
|
||||
Ok(_) => {
|
||||
println!("Mqtt connection ready");
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Could not connect mqtt due to {}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
NetworkMode::WIFI {
|
||||
sntp: sntp_mode,
|
||||
mqtt: mqtt_connected,
|
||||
ip_address: ip_info.ip.to_string()
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Offline mode");
|
||||
board.general_fault(true);
|
||||
NetworkMode::OFFLINE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO clean this up? better state
|
||||
fn pump_info(board: &mut MutexGuard<PlantCtrlBoard>, config: &PlantControllerConfig, plant_id: usize, pump_active: bool, pump_ineffective: bool) {
|
||||
let pump_info = PumpInfo {
|
||||
enabled: pump_active,
|
||||
pump_ineffective
|
||||
};
|
||||
let pump_topic = format!("/pump{}", plant_id + 1);
|
||||
match serde_json::to_string(&pump_info) {
|
||||
Ok(state) => {
|
||||
let _ = board.mqtt_publish(config, &pump_topic, state.as_bytes());
|
||||
//reduce speed as else messages will be dropped
|
||||
Delay::new_default().delay_ms(200);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error publishing pump state {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn publish_battery_state(
|
||||
board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
|
||||
config: &PlantControllerConfig,
|
||||
@ -647,7 +716,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_string<T: Display>(value: Result<T>) -> String {
|
||||
fn to_string<T: Display>(value: anyhow::Result<T>) -> String {
|
||||
match value {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(err) => {
|
||||
@ -690,4 +759,4 @@ struct VersionInfo {
|
||||
git_hash: String,
|
||||
build_time: String,
|
||||
partition: String,
|
||||
}
|
||||
}
|
@ -119,7 +119,7 @@ impl TankState {
|
||||
pub fn as_mqtt_info(
|
||||
&self,
|
||||
config: &TankConfig,
|
||||
water_temp: Result<f32, anyhow::Error>,
|
||||
water_temp: &anyhow::Result<f32>,
|
||||
) -> TankInfo {
|
||||
let mut tank_err: Option<TankError> = None;
|
||||
let left_ml = match self.left_ml(config) {
|
||||
@ -151,7 +151,7 @@ impl TankState {
|
||||
.as_ref()
|
||||
.is_ok_and(|temp| *temp < WATER_FROZEN_THRESH),
|
||||
water_temp: water_temp.as_ref().copied().ok(),
|
||||
temp_sensor_error: water_temp.err().map(|err| err.to_string()),
|
||||
temp_sensor_error: water_temp.as_ref().err().map(|err| err.to_string()),
|
||||
percent,
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
pub trait LimitPrecision {
|
||||
fn to_precision(self, precision: i32) -> Self;
|
||||
}
|
||||
|
||||
impl LimitPrecision for f32 {
|
||||
fn to_precision(self, precision: i32) -> Self {
|
||||
let factor = 10_f32.powi(precision);
|
||||
(self * factor).round() / factor
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
determine_tank_state, get_version, log::LogMessage, plant_hal::PLANT_COUNT,
|
||||
plant_state::PlantState, util::LimitPrecision, BOARD_ACCESS,
|
||||
plant_state::PlantState, BOARD_ACCESS,
|
||||
};
|
||||
use anyhow::bail;
|
||||
use chrono::DateTime;
|
||||
@ -273,7 +273,7 @@ fn tank_info(
|
||||
//should be multsampled
|
||||
let water_temp = board.water_temperature_c();
|
||||
Ok(Some(serde_json::to_string(
|
||||
&tank_info.as_mqtt_info(&config.tank, water_temp),
|
||||
&tank_info.as_mqtt_info(&config.tank, &water_temp),
|
||||
)?))
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user