mqtt via mcutie
This commit is contained in:
@@ -159,7 +159,7 @@ option-lock = { version = "0.3.1", default-features = false }
|
|||||||
|
|
||||||
#stay in sync with mcutie version here!
|
#stay in sync with mcutie version here!
|
||||||
heapless = { version = "0.7.17", features = ["serde"] }
|
heapless = { version = "0.7.17", features = ["serde"] }
|
||||||
mcutie = { version = "0.3.0", default-features = false }
|
mcutie = { version = "0.3.0", default-features = false, features = ["log", "homeassistant"] }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use alloc::string::String;
|
|
||||||
use core::str::FromStr;
|
|
||||||
use crate::hal::PLANT_COUNT;
|
use crate::hal::PLANT_COUNT;
|
||||||
use crate::plant_state::PlantWateringMode;
|
use crate::plant_state::PlantWateringMode;
|
||||||
|
use alloc::string::String;
|
||||||
|
use core::str::FromStr;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
@@ -10,10 +10,10 @@ pub struct NetworkConfig {
|
|||||||
pub ap_ssid: heapless::String<32>,
|
pub ap_ssid: heapless::String<32>,
|
||||||
pub ssid: Option<heapless::String<32>>,
|
pub ssid: Option<heapless::String<32>>,
|
||||||
pub password: Option<heapless::String<64>>,
|
pub password: Option<heapless::String<64>>,
|
||||||
pub mqtt_url: Option<heapless::String<128>>,
|
pub mqtt_url: Option<String>,
|
||||||
pub base_topic: Option<heapless::String<64>>,
|
pub base_topic: Option<heapless::String<64>>,
|
||||||
pub mqtt_user: Option<heapless::String<32>>,
|
pub mqtt_user: Option<String>,
|
||||||
pub mqtt_password: Option<heapless::String<64>>,
|
pub mqtt_password: Option<String>,
|
||||||
pub max_wait: u32,
|
pub max_wait: u32,
|
||||||
}
|
}
|
||||||
impl Default for NetworkConfig {
|
impl Default for NetworkConfig {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::hal::Box;
|
|
||||||
use crate::fat_error::{FatError, FatResult};
|
use crate::fat_error::{FatError, FatResult};
|
||||||
|
use crate::hal::Box;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bq34z100::{Bq34z100g1, Bq34z100g1Driver, Flags};
|
use bq34z100::{Bq34z100g1, Bq34z100g1Driver, Flags};
|
||||||
@@ -43,14 +43,6 @@ pub enum BatteryError {
|
|||||||
CommunicationError(String),
|
CommunicationError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl From<Bq34Z100Error<esp_idf_hal::i2c::I2cError>> for BatteryError {
|
|
||||||
// fn from(err: Bq34Z100Error<esp_idf_hal::i2c::I2cError>) -> Self {
|
|
||||||
// BatteryError::CommunicationError(
|
|
||||||
// anyhow!("failed to communicate with battery monitor: {:?}", err).to_string(),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub enum BatteryState {
|
pub enum BatteryState {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|||||||
@@ -7,26 +7,23 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::fat_error::{ContextExt, FatError, FatResult};
|
use crate::fat_error::{ContextExt, FatError, FatResult};
|
||||||
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem;
|
||||||
use alloc::borrow::ToOwned;
|
|
||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use alloc::{format, string::String, vec::Vec};
|
use alloc::{format, string::String, vec::Vec};
|
||||||
use core::marker::PhantomData;
|
|
||||||
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use core::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use core::sync::atomic::Ordering;
|
use core::sync::atomic::Ordering;
|
||||||
use core::sync::atomic::Ordering::Relaxed;
|
|
||||||
use edge_dhcp::io::server::run;
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_net::udp::UdpSocket;
|
use embassy_net::udp::UdpSocket;
|
||||||
use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
|
use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::mutex::{Mutex, MutexGuard};
|
use embassy_sync::mutex::{Mutex, MutexGuard};
|
||||||
|
use embassy_sync::once_lock::OnceLock;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use embedded_storage::nor_flash::ReadNorFlash;
|
use embedded_storage::nor_flash::ReadNorFlash;
|
||||||
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
|
use esp_bootloader_esp_idf::ota::{Ota, OtaImageState};
|
||||||
use esp_bootloader_esp_idf::partitions::FlashRegion;
|
use esp_bootloader_esp_idf::partitions::FlashRegion;
|
||||||
use esp_hal::gpio::{Input, InputConfig, Pull, RtcPinWithResistors};
|
use esp_hal::gpio::{Input, RtcPinWithResistors};
|
||||||
use esp_hal::rng::Rng;
|
use esp_hal::rng::Rng;
|
||||||
use esp_hal::rtc_cntl::{
|
use esp_hal::rtc_cntl::{
|
||||||
sleep::{TimerWakeupSource, WakeupLevel},
|
sleep::{TimerWakeupSource, WakeupLevel},
|
||||||
@@ -37,11 +34,15 @@ use esp_println::println;
|
|||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
use esp_wifi::wifi::{
|
use esp_wifi::wifi::{
|
||||||
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
|
AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
|
||||||
Interfaces, ScanConfig, ScanTypeConfig, WifiController, WifiDevice, WifiState,
|
ScanConfig, ScanTypeConfig, WifiController, WifiDevice, WifiState,
|
||||||
};
|
};
|
||||||
use littlefs2::fs::Filesystem;
|
use littlefs2::fs::Filesystem;
|
||||||
use littlefs2_core::{FileType, PathBuf, SeekFrom};
|
use littlefs2_core::{FileType, PathBuf, SeekFrom};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
use mcutie::{
|
||||||
|
Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable,
|
||||||
|
QoS, Topic,
|
||||||
|
};
|
||||||
use portable_atomic::AtomicBool;
|
use portable_atomic::AtomicBool;
|
||||||
use smoltcp::socket::udp::PacketMetadata;
|
use smoltcp::socket::udp::PacketMetadata;
|
||||||
use smoltcp::wire::DnsQueryType;
|
use smoltcp::wire::DnsQueryType;
|
||||||
@@ -59,6 +60,11 @@ static mut RESTART_TO_CONF: i8 = 0;
|
|||||||
const CONFIG_FILE: &str = "config.json";
|
const CONFIG_FILE: &str = "config.json";
|
||||||
const NTP_SERVER: &str = "pool.ntp.org";
|
const NTP_SERVER: &str = "pool.ntp.org";
|
||||||
|
|
||||||
|
static MQTT_CONNECTED_EVENT_RECEIVED: AtomicBool = AtomicBool::new(false);
|
||||||
|
static MQTT_ROUND_TRIP_RECEIVED: AtomicBool = AtomicBool::new(false);
|
||||||
|
pub static MQTT_STAY_ALIVE: AtomicBool = AtomicBool::new(false);
|
||||||
|
static MQTT_BASE_TOPIC: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct FileInfo {
|
pub struct FileInfo {
|
||||||
filename: String,
|
filename: String,
|
||||||
@@ -72,18 +78,6 @@ pub struct FileList {
|
|||||||
files: Vec<FileInfo>,
|
files: Vec<FileInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileSystemSizeInfo {
|
|
||||||
pub total_size: usize,
|
|
||||||
pub used_size: usize,
|
|
||||||
pub free_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MqttClient<'a> {
|
|
||||||
dummy: PhantomData<&'a ()>,
|
|
||||||
//mqtt_client: EspMqttClient<'a>,
|
|
||||||
base_topic: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
struct Timestamp {
|
struct Timestamp {
|
||||||
stamp: DateTime<Utc>,
|
stamp: DateTime<Utc>,
|
||||||
@@ -127,9 +121,6 @@ pub struct Esp<'a> {
|
|||||||
pub interface_ap: Option<WifiDevice<'static>>,
|
pub interface_ap: Option<WifiDevice<'static>>,
|
||||||
pub controller: Arc<Mutex<CriticalSectionRawMutex, WifiController<'static>>>,
|
pub controller: Arc<Mutex<CriticalSectionRawMutex, WifiController<'static>>>,
|
||||||
|
|
||||||
//only filled, if a useable mqtt client with working roundtrip could be established
|
|
||||||
pub(crate) mqtt_client: Option<MqttClient<'a>>,
|
|
||||||
|
|
||||||
pub boot_button: Input<'a>,
|
pub boot_button: Input<'a>,
|
||||||
|
|
||||||
// RTC-capable GPIO used as external wake source (store the raw peripheral)
|
// RTC-capable GPIO used as external wake source (store the raw peripheral)
|
||||||
@@ -147,12 +138,6 @@ pub struct Esp<'a> {
|
|||||||
// CPU cores/threads, reconsider this.
|
// CPU cores/threads, reconsider this.
|
||||||
unsafe impl Send for Esp<'_> {}
|
unsafe impl Send for Esp<'_> {}
|
||||||
|
|
||||||
pub struct IpInfo {
|
|
||||||
pub(crate) ip: IpAddr,
|
|
||||||
netmask: IpAddr,
|
|
||||||
gateway: IpAddr,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! mk_static {
|
macro_rules! mk_static {
|
||||||
($t:ty,$val:expr) => {{
|
($t:ty,$val:expr) => {{
|
||||||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||||
@@ -164,7 +149,7 @@ macro_rules! mk_static {
|
|||||||
|
|
||||||
impl Esp<'_> {
|
impl Esp<'_> {
|
||||||
pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
|
pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> {
|
||||||
let file = PathBuf::try_from(filename.as_str()).unwrap();
|
let file = PathBuf::try_from(filename.as_str())?;
|
||||||
let access = self.fs.lock().await;
|
let access = self.fs.lock().await;
|
||||||
access.remove(&*file)?;
|
access.remove(&*file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -383,7 +368,7 @@ impl Esp<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn wifi_ap(&mut self, fallback: bool) -> FatResult<Stack<'static>> {
|
pub(crate) async fn wifi_ap(&mut self) -> FatResult<Stack<'static>> {
|
||||||
let ssid = match self.load_config().await {
|
let ssid = match self.load_config().await {
|
||||||
Ok(config) => config.network.ap_ssid.as_str().to_string(),
|
Ok(config) => config.network.ap_ssid.as_str().to_string(),
|
||||||
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
||||||
@@ -482,7 +467,7 @@ impl Esp<'_> {
|
|||||||
let (stack, runner) = embassy_net::new(
|
let (stack, runner) = embassy_net::new(
|
||||||
device,
|
device,
|
||||||
config,
|
config,
|
||||||
mk_static!(StackResources<4>, StackResources::<4>::new()),
|
mk_static!(StackResources<8>, StackResources::<8>::new()),
|
||||||
seed,
|
seed,
|
||||||
);
|
);
|
||||||
let stack = mk_static!(Stack, stack);
|
let stack = mk_static!(Stack, stack);
|
||||||
@@ -572,6 +557,8 @@ impl Esp<'_> {
|
|||||||
}
|
}
|
||||||
Timer::after(Duration::from_millis(100)).await
|
Timer::after(Duration::from_millis(100)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Connected WIFI, dhcp: {:?}", stack.config_v4());
|
||||||
Ok(stack.clone())
|
Ok(stack.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,9 +589,6 @@ impl Esp<'_> {
|
|||||||
let ext1 = esp_hal::rtc_cntl::sleep::Ext1WakeupSource::new(&mut wake_pins);
|
let ext1 = esp_hal::rtc_cntl::sleep::Ext1WakeupSource::new(&mut wake_pins);
|
||||||
rtc.sleep_deep(&[&timer, &ext1]);
|
rtc.sleep_deep(&[&timer, &ext1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should never reach here because sleep_deep never returns, but just in case, reset.
|
|
||||||
software_reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {
|
pub(crate) async fn load_config(&mut self) -> FatResult<PlantControllerConfig> {
|
||||||
@@ -708,7 +692,11 @@ impl Esp<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn mqtt(&mut self, network_config: &NetworkConfig) -> FatResult<()> {
|
pub(crate) async fn mqtt(
|
||||||
|
&mut self,
|
||||||
|
network_config: &'static NetworkConfig,
|
||||||
|
stack: Stack<'static>,
|
||||||
|
) -> FatResult<()> {
|
||||||
let base_topic = network_config
|
let base_topic = network_config
|
||||||
.base_topic
|
.base_topic
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -716,7 +704,12 @@ impl Esp<'_> {
|
|||||||
if base_topic.is_empty() {
|
if base_topic.is_empty() {
|
||||||
bail!("Mqtt base_topic was empty")
|
bail!("Mqtt base_topic was empty")
|
||||||
}
|
}
|
||||||
let _base_topic_copy = base_topic.clone();
|
MQTT_BASE_TOPIC
|
||||||
|
.init(base_topic.to_string())
|
||||||
|
.map_err(|_| FatError::String {
|
||||||
|
error: "Error setting basetopic".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let mqtt_url = network_config
|
let mqtt_url = network_config
|
||||||
.mqtt_url
|
.mqtt_url
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -725,202 +718,223 @@ impl Esp<'_> {
|
|||||||
bail!("Mqtt url was empty")
|
bail!("Mqtt url was empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
bail!("todo");
|
let last_will_topic = format!("{}/state", base_topic);
|
||||||
//
|
let round_trip_topic = format!("{}/internal/roundtrip", base_topic);
|
||||||
// let last_will_topic = format!("{}/state", base_topic);
|
let stay_alive_topic = format!("{}/stay_alive", base_topic);
|
||||||
// let mqtt_client_config = MqttClientConfiguration {
|
|
||||||
// lwt: Some(LwtConfiguration {
|
let mut builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 0> =
|
||||||
// topic: &last_will_topic,
|
McutieBuilder::new(stack, "plant ctrl", mqtt_url);
|
||||||
// payload: "lost".as_bytes(),
|
if network_config.mqtt_user.is_some() && network_config.mqtt_password.is_some() {
|
||||||
// qos: AtLeastOnce,
|
builder = builder.with_authentication(
|
||||||
// retain: true,
|
network_config.mqtt_user.as_ref().unwrap().as_str(),
|
||||||
// }),
|
network_config.mqtt_password.as_ref().unwrap().as_str(),
|
||||||
// client_id: Some("plantctrl"),
|
);
|
||||||
// keep_alive_interval: Some(Duration::from_secs(60 * 60 * 2)),
|
info!("With authentification");
|
||||||
// username: network_config.mqtt_user.as_ref().map(|v| &**v),
|
|
||||||
// password: network_config.mqtt_password.as_ref().map(|v| &**v),
|
|
||||||
// //room for improvement
|
|
||||||
// ..Default::default()
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// let mqtt_connected_event_received = Arc::new(AtomicBool::new(false));
|
|
||||||
// let mqtt_connected_event_ok = Arc::new(AtomicBool::new(false));
|
|
||||||
//
|
|
||||||
// let round_trip_ok = Arc::new(AtomicBool::new(false));
|
|
||||||
// let round_trip_topic = format!("{}/internal/roundtrip", base_topic);
|
|
||||||
// let stay_alive_topic = format!("{}/stay_alive", base_topic);
|
|
||||||
// log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic);
|
|
||||||
//
|
|
||||||
// let mqtt_connected_event_received_copy = mqtt_connected_event_received.clone();
|
|
||||||
// let mqtt_connected_event_ok_copy = mqtt_connected_event_ok.clone();
|
|
||||||
// let stay_alive_topic_copy = stay_alive_topic.clone();
|
|
||||||
// let round_trip_topic_copy = round_trip_topic.clone();
|
|
||||||
// let round_trip_ok_copy = round_trip_ok.clone();
|
|
||||||
// let client_id = mqtt_client_config.client_id.unwrap_or("not set");
|
|
||||||
// log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url);
|
|
||||||
// let mut client = EspMqttClient::new_cb(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");
|
|
||||||
// log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
|
|
||||||
// STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// } else {
|
|
||||||
// log(LogMessage::UnknownTopic, 0, 0, "", topic);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Connected(_) => {
|
|
||||||
// mqtt_connected_event_received_copy
|
|
||||||
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// mqtt_connected_event_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// log::info!("Mqtt connected");
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Disconnected => {
|
|
||||||
// mqtt_connected_event_received_copy
|
|
||||||
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// log::info!("Mqtt disconnected");
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => {
|
|
||||||
// log::info!("EspMqttError reported {:?}", esp_error);
|
|
||||||
// mqtt_connected_event_received_copy
|
|
||||||
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
// log::info!("Mqtt error");
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => {
|
|
||||||
// log::info!("Mqtt before connect")
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => {
|
|
||||||
// log::info!("Mqtt subscribed")
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => {
|
|
||||||
// log::info!("Mqtt unsubscribed")
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Published(_) => {
|
|
||||||
// log::info!("Mqtt published")
|
|
||||||
// }
|
|
||||||
// esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => {
|
|
||||||
// log::info!("Mqtt deleted")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })?;
|
|
||||||
//
|
|
||||||
// let mut wait_for_connections_event = 0;
|
|
||||||
// while wait_for_connections_event < 100 {
|
|
||||||
// wait_for_connections_event += 1;
|
|
||||||
// match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
|
|
||||||
// true => {
|
|
||||||
// log::info!("Mqtt connection callback received, progressing");
|
|
||||||
// match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
|
||||||
// true => {
|
|
||||||
// log::info!(
|
|
||||||
// "Mqtt did callback as connected, testing with roundtrip now"
|
|
||||||
// );
|
|
||||||
// //subscribe to roundtrip
|
|
||||||
// client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?;
|
|
||||||
// client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?;
|
|
||||||
// //publish to roundtrip
|
|
||||||
// client.publish(
|
|
||||||
// round_trip_topic.as_str(),
|
|
||||||
// ExactlyOnce,
|
|
||||||
// false,
|
|
||||||
// "online_test".as_bytes(),
|
|
||||||
// )?;
|
|
||||||
//
|
|
||||||
// let mut wait_for_roundtrip = 0;
|
|
||||||
// while wait_for_roundtrip < 100 {
|
|
||||||
// wait_for_roundtrip += 1;
|
|
||||||
// match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
|
||||||
// true => {
|
|
||||||
// log::info!("Round trip registered, proceeding");
|
|
||||||
// self.mqtt_client = Some(MqttClient {
|
|
||||||
// mqtt_client: client,
|
|
||||||
// base_topic: base_topic_copy,
|
|
||||||
// });
|
|
||||||
// return anyhow::Ok(());
|
|
||||||
// }
|
|
||||||
// false => {
|
|
||||||
// unsafe { vTaskDelay(10) };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// bail!("Mqtt did not complete roundtrip in time");
|
|
||||||
// }
|
|
||||||
// false => {
|
|
||||||
// bail!("Mqtt did respond but with failure")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// false => {
|
|
||||||
// unsafe { vTaskDelay(10) };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// bail!("Mqtt did not fire connection callback in time");
|
|
||||||
}
|
}
|
||||||
pub(crate) async fn mqtt_publish(&mut self, _subtopic: &str, _message: &[u8]) -> FatResult<()> {
|
|
||||||
bail!("todo");
|
let lwt = Topic::General(last_will_topic);
|
||||||
//
|
let lwt = mk_static!(Topic<String>, lwt);
|
||||||
// if self.mqtt_client.is_none() {
|
let lwt = lwt.with_display("lost").retain(true).qos(QoS::AtLeastOnce);
|
||||||
// return anyhow::Ok(());
|
builder = builder.with_last_will(lwt);
|
||||||
// }
|
//TODO make configurable
|
||||||
// if !subtopic.starts_with("/") {
|
builder = builder.with_device_id("plantctrl");
|
||||||
// log::info!("Subtopic without / at start {}", subtopic);
|
|
||||||
// bail!("Subtopic without / at start {}", subtopic);
|
let builder: McutieBuilder<'_, String, PublishDisplay<String, &str>, 2> = builder
|
||||||
// }
|
.with_subscriptions([
|
||||||
// if subtopic.len() > 192 {
|
Topic::General(round_trip_topic.clone()),
|
||||||
// log::info!("Subtopic exceeds 192 chars {}", subtopic);
|
Topic::General(stay_alive_topic.clone()),
|
||||||
// bail!("Subtopic exceeds 192 chars {}", subtopic);
|
]);
|
||||||
// }
|
|
||||||
// let client = self.mqtt_client.as_mut().unwrap();
|
let keep_alive = Duration::from_secs(60 * 60 * 2).as_secs() as u16;
|
||||||
// let mut full_topic: heapless::String<256> = heapless::String::new();
|
let (receiver, task) = builder.build(keep_alive);
|
||||||
// if full_topic.push_str(client.base_topic.as_str()).is_err() {
|
|
||||||
// log::info!("Some error assembling full_topic 1");
|
let spawner = Spawner::for_current_executor().await;
|
||||||
// bail!("Some error assembling full_topic 1")
|
spawner.spawn(mqtt_incoming_task(
|
||||||
// };
|
receiver,
|
||||||
// if full_topic.push_str(subtopic).is_err() {
|
round_trip_topic.clone(),
|
||||||
// log::info!("Some error assembling full_topic 2");
|
stay_alive_topic.clone(),
|
||||||
// bail!("Some error assembling full_topic 2")
|
))?;
|
||||||
// };
|
spawner.spawn(mqtt_runner(task))?;
|
||||||
// let publish = client
|
|
||||||
// .mqtt_client
|
LOG_ACCESS
|
||||||
// .publish(&full_topic, ExactlyOnce, true, message);
|
.lock()
|
||||||
// Timer::after_millis(10).await;
|
.await
|
||||||
// match publish {
|
.log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic)
|
||||||
// OkStd(message_id) => {
|
.await;
|
||||||
// log::info!(
|
|
||||||
// "Published mqtt topic {} with message {:#?} msgid is {:?}",
|
LOG_ACCESS
|
||||||
// full_topic,
|
.lock()
|
||||||
// String::from_utf8_lossy(message),
|
.await
|
||||||
// message_id
|
.log(LogMessage::MqttInfo, 0, 0, "", mqtt_url)
|
||||||
// );
|
.await;
|
||||||
// anyhow::Ok(())
|
|
||||||
// }
|
let mqtt_timeout = 15000;
|
||||||
// Err(err) => {
|
let timeout = {
|
||||||
// log::info!(
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
// "Error during mqtt send on topic {} with message {:#?} error is {:?}",
|
guard.current_time_us()
|
||||||
// full_topic,
|
} + mqtt_timeout as u64 * 1000;
|
||||||
// String::from_utf8_lossy(message),
|
while !MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed) {
|
||||||
// err
|
let cur = TIME_ACCESS.get().await.lock().await.current_time_us();
|
||||||
// );
|
if cur > timeout {
|
||||||
// Err(err)?
|
bail!("Timeout waiting MQTT connect event")
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
Timer::after(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Topic::General(round_trip_topic.clone())
|
||||||
|
.with_display("online_text")
|
||||||
|
.publish()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let timeout = {
|
||||||
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
|
guard.current_time_us()
|
||||||
|
} + mqtt_timeout as u64 * 1000;
|
||||||
|
while !MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed) {
|
||||||
|
let cur = TIME_ACCESS.get().await.lock().await.current_time_us();
|
||||||
|
if cur > timeout {
|
||||||
|
//ensure we do not further try to publish
|
||||||
|
MQTT_CONNECTED_EVENT_RECEIVED.store(false, Ordering::Relaxed);
|
||||||
|
bail!("Timeout waiting MQTT roundtrip")
|
||||||
|
}
|
||||||
|
Timer::after(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn mqtt_inner(&mut self, subtopic: &str, message: &str) -> FatResult<()> {
|
||||||
|
if !subtopic.starts_with("/") {
|
||||||
|
bail!("Subtopic without / at start {}", subtopic);
|
||||||
|
}
|
||||||
|
if subtopic.len() > 192 {
|
||||||
|
bail!("Subtopic exceeds 192 chars {}", subtopic);
|
||||||
|
}
|
||||||
|
let base_topic = MQTT_BASE_TOPIC
|
||||||
|
.try_get()
|
||||||
|
.context("missing base topic in static!")?;
|
||||||
|
|
||||||
|
let full_topic = format!("{base_topic}{subtopic}");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let result = Topic::General(full_topic.as_str())
|
||||||
|
.with_display(message)
|
||||||
|
.retain(true)
|
||||||
|
.publish()
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Ok(()) => return Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
let retry = match err {
|
||||||
|
Error::IOError => false,
|
||||||
|
Error::TimedOut => true,
|
||||||
|
Error::TooLarge => false,
|
||||||
|
Error::PacketError => false,
|
||||||
|
Error::Invalid => false,
|
||||||
|
};
|
||||||
|
if !retry {
|
||||||
|
bail!(
|
||||||
|
"Error during mqtt send on topic {} with message {:#?} error is {:?}",
|
||||||
|
&full_topic,
|
||||||
|
message,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"Retransmit for {} with message {:#?} error is {:?} retrying {}",
|
||||||
|
&full_topic, message, err, retry
|
||||||
|
);
|
||||||
|
Timer::after(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) async fn mqtt_publish(&mut self, subtopic: &str, message: &str) {
|
||||||
|
let online = MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed);
|
||||||
|
if !online {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let roundtrip_ok = MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed);
|
||||||
|
if !roundtrip_ok {
|
||||||
|
info!("MQTT roundtrip not received yet, dropping message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match self.mqtt_inner(subtopic, message).await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
info!(
|
||||||
|
"Error during mqtt send on topic {} with message {:#?} error is {:?}",
|
||||||
|
subtopic, message, err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn mqtt_runner(
|
||||||
|
task: McutieTask<'static, String, PublishDisplay<'static, String, &'static str>, 2>,
|
||||||
|
) {
|
||||||
|
task.run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn mqtt_incoming_task(
|
||||||
|
receiver: McutieReceiver,
|
||||||
|
round_trip_topic: String,
|
||||||
|
stay_alive_topic: String,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
let message = receiver.receive().await;
|
||||||
|
match message {
|
||||||
|
MqttMessage::Connected => {
|
||||||
|
info!("Mqtt connected");
|
||||||
|
MQTT_CONNECTED_EVENT_RECEIVED.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
MqttMessage::Publish(topic, payload) => match topic {
|
||||||
|
Topic::DeviceType(type_topic) => {}
|
||||||
|
Topic::Device(device_topic) => {}
|
||||||
|
Topic::General(topic) => {
|
||||||
|
let subtopic = topic.as_str();
|
||||||
|
|
||||||
|
if subtopic.eq(round_trip_topic.as_str()) {
|
||||||
|
MQTT_ROUND_TRIP_RECEIVED.store(true, Ordering::Relaxed);
|
||||||
|
} else if subtopic.eq(stay_alive_topic.as_str()) {
|
||||||
|
let value = payload.eq_ignore_ascii_case("true".as_ref())
|
||||||
|
|| payload.eq_ignore_ascii_case("1".as_ref());
|
||||||
|
let a = match value {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
};
|
||||||
|
LOG_ACCESS
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.log(LogMessage::MqttStayAliveRec, a, 0, "", "")
|
||||||
|
.await;
|
||||||
|
MQTT_STAY_ALIVE.store(value, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
LOG_ACCESS
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.log(LogMessage::UnknownTopic, 0, 0, "", &*topic)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MqttMessage::Disconnected => {
|
||||||
|
MQTT_CONNECTED_EVENT_RECEIVED.store(false, Ordering::Relaxed);
|
||||||
|
info!("Mqtt disconnected");
|
||||||
|
}
|
||||||
|
MqttMessage::HomeAssistantOnline => {
|
||||||
|
info!("Home assistant is online");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task(pool_size = 2)]
|
||||||
|
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
||||||
|
runner.run().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
@@ -943,7 +957,7 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
|
|||||||
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
|
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
|
||||||
let unbound_socket = Udp::new(stack, &buffers);
|
let unbound_socket = Udp::new(stack, &buffers);
|
||||||
let mut bound_socket = unbound_socket
|
let mut bound_socket = unbound_socket
|
||||||
.bind(core::net::SocketAddr::V4(SocketAddrV4::new(
|
.bind(SocketAddr::V4(SocketAddrV4::new(
|
||||||
Ipv4Addr::UNSPECIFIED,
|
Ipv4Addr::UNSPECIFIED,
|
||||||
DEFAULT_SERVER_PORT,
|
DEFAULT_SERVER_PORT,
|
||||||
)))
|
)))
|
||||||
@@ -962,8 +976,3 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
|
|||||||
Timer::after(Duration::from_millis(500)).await;
|
Timer::after(Duration::from_millis(500)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task(pool_size = 2)]
|
|
||||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
|
||||||
runner.run().await
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod initial_hal;
|
|||||||
mod little_fs2storage_adapter;
|
mod little_fs2storage_adapter;
|
||||||
pub(crate) mod rtc;
|
pub(crate) mod rtc;
|
||||||
mod v3_hal;
|
mod v3_hal;
|
||||||
|
mod v3_shift_register;
|
||||||
mod v4_hal;
|
mod v4_hal;
|
||||||
mod v4_sensor;
|
mod v4_sensor;
|
||||||
mod water;
|
mod water;
|
||||||
@@ -68,11 +69,8 @@ use eeprom24x::page_size::B32;
|
|||||||
use eeprom24x::unique_serial::No;
|
use eeprom24x::unique_serial::No;
|
||||||
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
|
use eeprom24x::{Eeprom24x, SlaveAddr, Storage};
|
||||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||||
use embassy_executor::Spawner;
|
|
||||||
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
|
||||||
//use battery::BQ34Z100G1;
|
|
||||||
//use bq34z100::Bq34z100g1Driver;
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
|
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
||||||
use esp_bootloader_esp_idf::partitions::{
|
use esp_bootloader_esp_idf::partitions::{
|
||||||
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
|
AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
|
||||||
};
|
};
|
||||||
@@ -218,12 +216,8 @@ macro_rules! mk_static {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP");
|
|
||||||
|
|
||||||
impl PlantHal {
|
impl PlantHal {
|
||||||
pub async fn create(
|
pub async fn create() -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>, FatError> {
|
||||||
spawner: Spawner,
|
|
||||||
) -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>, FatError> {
|
|
||||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||||
let peripherals: Peripherals = esp_hal::init(config);
|
let peripherals: Peripherals = esp_hal::init(config);
|
||||||
|
|
||||||
@@ -394,7 +388,6 @@ impl PlantHal {
|
|||||||
interface_ap: Some(ap),
|
interface_ap: Some(ap),
|
||||||
boot_button,
|
boot_button,
|
||||||
wake_gpio1,
|
wake_gpio1,
|
||||||
mqtt_client: None,
|
|
||||||
ota,
|
ota,
|
||||||
ota_next,
|
ota_next,
|
||||||
};
|
};
|
||||||
@@ -548,9 +541,6 @@ impl PlantHal {
|
|||||||
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
bail!("Unknown board version");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HAL { board_hal }
|
HAL { board_hal }
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use crate::bail;
|
|||||||
use crate::fat_error::FatError;
|
use crate::fat_error::FatError;
|
||||||
use crate::hal::esp::{hold_disable, hold_enable};
|
use crate::hal::esp::{hold_disable, hold_enable};
|
||||||
use crate::hal::rtc::RTCModuleInteraction;
|
use crate::hal::rtc::RTCModuleInteraction;
|
||||||
|
use crate::hal::v3_shift_register::ShiftRegister40;
|
||||||
use crate::hal::water::TankSensor;
|
use crate::hal::water::TankSensor;
|
||||||
use crate::hal::{BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, TIME_ACCESS};
|
use crate::hal::{BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, TIME_ACCESS};
|
||||||
use crate::log::{LogMessage, LOG_ACCESS};
|
use crate::log::{LogMessage, LOG_ACCESS};
|
||||||
use crate::sipo::ShiftRegister40;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::PlantControllerConfig,
|
config::PlantControllerConfig,
|
||||||
hal::{battery::BatteryInteraction, esp::Esp},
|
hal::{battery::BatteryInteraction, esp::Esp},
|
||||||
@@ -15,7 +15,6 @@ use alloc::format;
|
|||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::blocking_mutex::CriticalSectionMutex;
|
|
||||||
use embassy_sync::mutex::Mutex;
|
use embassy_sync::mutex::Mutex;
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use embedded_hal::digital::OutputPin as _;
|
use embedded_hal::digital::OutputPin as _;
|
||||||
@@ -267,7 +266,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
|
|||||||
//self.signal_counter.counter_clear()?;
|
//self.signal_counter.counter_clear()?;
|
||||||
//Disable all
|
//Disable all
|
||||||
{
|
{
|
||||||
let mut shift_register = self.shift_register.lock().await;
|
let shift_register = self.shift_register.lock().await;
|
||||||
shift_register.decompose()[MS_4].set_high()?;
|
shift_register.decompose()[MS_4].set_high()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +297,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
|
|||||||
|
|
||||||
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
|
let is_bit_set = |b: u8| -> bool { sensor_channel & (1 << b) != 0 };
|
||||||
{
|
{
|
||||||
let mut shift_register = self.shift_register.lock().await;
|
let shift_register = self.shift_register.lock().await;
|
||||||
let pin_0 = &mut shift_register.decompose()[MS_0];
|
let pin_0 = &mut shift_register.decompose()[MS_0];
|
||||||
let pin_1 = &mut shift_register.decompose()[MS_1];
|
let pin_1 = &mut shift_register.decompose()[MS_1];
|
||||||
let pin_2 = &mut shift_register.decompose()[MS_2];
|
let pin_2 = &mut shift_register.decompose()[MS_2];
|
||||||
@@ -336,7 +335,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> {
|
|||||||
Timer::after_millis(measurement).await;
|
Timer::after_millis(measurement).await;
|
||||||
//self.signal_counter.counter_pause()?;
|
//self.signal_counter.counter_pause()?;
|
||||||
{
|
{
|
||||||
let mut shift_register = self.shift_register.lock().await;
|
let shift_register = self.shift_register.lock().await;
|
||||||
shift_register.decompose()[MS_4].set_high()?;
|
shift_register.decompose()[MS_4].set_high()?;
|
||||||
shift_register.decompose()[SENSOR_ON].set_low()?;
|
shift_register.decompose()[SENSOR_ON].set_low()?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Serial-in parallel-out shift register
|
//! Serial-in parallel-out shift register
|
||||||
|
#![allow(warnings)]
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use core::iter::Iterator;
|
use core::iter::Iterator;
|
||||||
@@ -10,7 +10,6 @@ use async_trait::async_trait;
|
|||||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use esp_hal::analog::adc::{Adc, AdcConfig, Attenuation};
|
|
||||||
use esp_hal::{twai, Blocking};
|
use esp_hal::{twai, Blocking};
|
||||||
//use embedded_hal_bus::i2c::MutexDevice;
|
//use embedded_hal_bus::i2c::MutexDevice;
|
||||||
use crate::bail;
|
use crate::bail;
|
||||||
@@ -137,10 +136,6 @@ pub struct V4<'a> {
|
|||||||
extra2: Output<'a>,
|
extra2: Output<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InputOutput<'a> {
|
|
||||||
pin: Flex<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn create_v4(
|
pub(crate) async fn create_v4(
|
||||||
peripherals: FreePeripherals<'static>,
|
peripherals: FreePeripherals<'static>,
|
||||||
esp: Esp<'static>,
|
esp: Esp<'static>,
|
||||||
@@ -346,7 +341,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
|
|
||||||
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
|
||||||
self.awake.set_low();
|
self.awake.set_low();
|
||||||
//self.charger.power_save();
|
self.charger.power_save();
|
||||||
let rtc = TIME_ACCESS.get().await.lock().await;
|
let rtc = TIME_ACCESS.get().await.lock().await;
|
||||||
self.esp.deep_sleep(duration_in_ms, rtc);
|
self.esp.deep_sleep(duration_in_ms, rtc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::bail;
|
use crate::bail;
|
||||||
use crate::hal::{ADC1, TANK_MULTI_SAMPLE};
|
|
||||||
use crate::fat_error::FatError;
|
use crate::fat_error::FatError;
|
||||||
|
use crate::hal::{ADC1, TANK_MULTI_SAMPLE};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use esp_hal::analog::adc::{Adc, AdcConfig, AdcPin, Attenuation};
|
use esp_hal::analog::adc::{Adc, AdcConfig, AdcPin, Attenuation};
|
||||||
use esp_hal::delay::Delay;
|
use esp_hal::delay::Delay;
|
||||||
@@ -9,7 +9,6 @@ use esp_hal::pcnt::unit::Unit;
|
|||||||
use esp_hal::peripherals::GPIO5;
|
use esp_hal::peripherals::GPIO5;
|
||||||
use esp_hal::Blocking;
|
use esp_hal::Blocking;
|
||||||
use esp_println::println;
|
use esp_println::println;
|
||||||
use littlefs2::object_safe::DynStorage;
|
|
||||||
use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20};
|
use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20};
|
||||||
|
|
||||||
pub struct TankSensor<'a> {
|
pub struct TankSensor<'a> {
|
||||||
@@ -34,7 +33,7 @@ impl<'a> TankSensor<'a> {
|
|||||||
|
|
||||||
let mut adc1_config = AdcConfig::new();
|
let mut adc1_config = AdcConfig::new();
|
||||||
let tank_pin = adc1_config.enable_pin(gpio5, Attenuation::_11dB);
|
let tank_pin = adc1_config.enable_pin(gpio5, Attenuation::_11dB);
|
||||||
let mut tank_channel = Adc::new(adc1, adc1_config);
|
let tank_channel = Adc::new(adc1, adc1_config);
|
||||||
|
|
||||||
let one_wire_bus = OneWire::new(one_wire_pin, false);
|
let one_wire_bus = OneWire::new(one_wire_pin, false);
|
||||||
|
|
||||||
@@ -154,9 +153,7 @@ impl<'a> TankSensor<'a> {
|
|||||||
|
|
||||||
let mut store = [0_u16; TANK_MULTI_SAMPLE];
|
let mut store = [0_u16; TANK_MULTI_SAMPLE];
|
||||||
for multisample in 0..TANK_MULTI_SAMPLE {
|
for multisample in 0..TANK_MULTI_SAMPLE {
|
||||||
let mut asy = (&mut self.tank_channel);
|
let value = self.tank_channel.read_oneshot(&mut self.tank_pin);
|
||||||
|
|
||||||
let value = asy.read_oneshot(&mut self.tank_pin);
|
|
||||||
//force yield
|
//force yield
|
||||||
Timer::after_millis(10).await;
|
Timer::after_millis(10).await;
|
||||||
store[multisample] = value.unwrap();
|
store[multisample] = value.unwrap();
|
||||||
|
|||||||
@@ -111,7 +111,10 @@ impl LogArray {
|
|||||||
limit_length(txt_short, &mut txt_short_stack);
|
limit_length(txt_short, &mut txt_short_stack);
|
||||||
limit_length(txt_long, &mut txt_long_stack);
|
limit_length(txt_long, &mut txt_long_stack);
|
||||||
|
|
||||||
let time = { let guard = TIME_ACCESS.get().await.lock().await; guard.current_time_us() } / 1000;
|
let time = {
|
||||||
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
|
guard.current_time_us()
|
||||||
|
} / 1000;
|
||||||
|
|
||||||
let ordinal = message_key.ordinal() as u16;
|
let ordinal = message_key.ordinal() as u16;
|
||||||
let template: &str = message_key.into();
|
let template: &str = message_key.into();
|
||||||
@@ -196,7 +199,7 @@ pub enum LogMessage {
|
|||||||
StayAlive,
|
StayAlive,
|
||||||
#[strum(serialize = "Connecting mqtt ${txt_short} with id ${txt_long}")]
|
#[strum(serialize = "Connecting mqtt ${txt_short} with id ${txt_long}")]
|
||||||
MqttInfo,
|
MqttInfo,
|
||||||
#[strum(serialize = "Received stay alive with value ${txt_short}")]
|
#[strum(serialize = "Received stay alive with value ${number_a}")]
|
||||||
MqttStayAliveRec,
|
MqttStayAliveRec,
|
||||||
#[strum(serialize = "Unknown topic recieved ${txt_long}")]
|
#[strum(serialize = "Unknown topic recieved ${txt_long}")]
|
||||||
UnknownTopic,
|
UnknownTopic,
|
||||||
|
|||||||
292
rust/src/main.rs
292
rust/src/main.rs
@@ -13,17 +13,18 @@
|
|||||||
esp_bootloader_esp_idf::esp_app_desc!();
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
|
|
||||||
use crate::config::PlantConfig;
|
use crate::config::{NetworkConfig, PlantConfig};
|
||||||
use crate::fat_error::FatResult;
|
use crate::fat_error::FatResult;
|
||||||
|
use crate::hal::esp::MQTT_STAY_ALIVE;
|
||||||
use crate::hal::{esp_time, TIME_ACCESS};
|
use crate::hal::{esp_time, TIME_ACCESS};
|
||||||
use crate::log::LOG_ACCESS;
|
use crate::log::LOG_ACCESS;
|
||||||
use crate::tank::{determine_tank_state, TankError, WATER_FROZEN_THRESH};
|
use crate::tank::{determine_tank_state, TankError, TankState, WATER_FROZEN_THRESH};
|
||||||
use crate::webserver::httpd;
|
use crate::webserver::http_server;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::BoardVersion::INITIAL,
|
config::BoardVersion::INITIAL,
|
||||||
hal::{PlantHal, HAL, PLANT_COUNT},
|
hal::{PlantHal, HAL, PLANT_COUNT},
|
||||||
};
|
};
|
||||||
use ::log::{error, info, warn};
|
use ::log::{info, warn};
|
||||||
use alloc::borrow::ToOwned;
|
use alloc::borrow::ToOwned;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
@@ -65,7 +66,6 @@ mod fat_error;
|
|||||||
mod hal;
|
mod hal;
|
||||||
mod log;
|
mod log;
|
||||||
mod plant_state;
|
mod plant_state;
|
||||||
mod sipo;
|
|
||||||
mod tank;
|
mod tank;
|
||||||
mod webserver;
|
mod webserver;
|
||||||
|
|
||||||
@@ -74,8 +74,6 @@ extern crate alloc;
|
|||||||
|
|
||||||
pub static BOARD_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, HAL<'static>>> = OnceLock::new();
|
pub static BOARD_ACCESS: OnceLock<Mutex<CriticalSectionRawMutex, HAL<'static>>> = OnceLock::new();
|
||||||
|
|
||||||
pub static STAY_ALIVE: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
enum WaitType {
|
enum WaitType {
|
||||||
MissingConfig,
|
MissingConfig,
|
||||||
@@ -167,7 +165,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
let cur = match board.board_hal.get_rtc_module().get_rtc_time().await {
|
let cur = match board.board_hal.get_rtc_module().get_rtc_time().await {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
{
|
{
|
||||||
let mut guard = TIME_ACCESS.get().await.lock().await;
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
guard.set_current_time_us(value.timestamp_micros() as u64);
|
guard.set_current_time_us(value.timestamp_micros() as u64);
|
||||||
}
|
}
|
||||||
value
|
value
|
||||||
@@ -229,17 +227,17 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
info!("no mode override");
|
info!("no mode override");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (board.board_hal.get_config().hardware.board == INITIAL
|
if board.board_hal.get_config().hardware.board == INITIAL
|
||||||
&& board.board_hal.get_config().network.ssid.is_none())
|
&& board.board_hal.get_config().network.ssid.is_none()
|
||||||
{
|
{
|
||||||
info!("No wifi configured, starting initial config mode");
|
info!("No wifi configured, starting initial config mode");
|
||||||
|
|
||||||
let stack = board.board_hal.get_esp().wifi_ap(false).await?;
|
let stack = board.board_hal.get_esp().wifi_ap().await?;
|
||||||
|
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
println!("starting webserver");
|
println!("starting webserver");
|
||||||
|
|
||||||
spawner.spawn(httpd(reboot_now.clone(), stack))?;
|
spawner.spawn(http_server(reboot_now.clone(), stack))?;
|
||||||
wait_infinity(board, WaitType::MissingConfig, reboot_now.clone()).await;
|
wait_infinity(board, WaitType::MissingConfig, reboot_now.clone()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +246,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
try_connect_wifi_sntp_mqtt(&mut board, &mut stack).await
|
try_connect_wifi_sntp_mqtt(&mut board, &mut stack).await
|
||||||
} else {
|
} else {
|
||||||
info!("No wifi configured");
|
info!("No wifi configured");
|
||||||
//the current sensors require this amount to stabilize, in case of wifi this is already handled due to connect timings;
|
//the current sensors require this amount to stabilize, in the case of Wi-Fi this is already handled due to connect timings;
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(100).await;
|
||||||
NetworkMode::OFFLINE
|
NetworkMode::OFFLINE
|
||||||
};
|
};
|
||||||
@@ -258,7 +256,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
|
|
||||||
let res = {
|
let res = {
|
||||||
let esp = board.board_hal.get_esp();
|
let esp = board.board_hal.get_esp();
|
||||||
esp.wifi_ap(true).await
|
esp.wifi_ap().await
|
||||||
};
|
};
|
||||||
match res {
|
match res {
|
||||||
Ok(ap_stack) => {
|
Ok(ap_stack) => {
|
||||||
@@ -277,7 +275,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
}),
|
}),
|
||||||
None => UTC, // Fallback to UTC if no timezone is set
|
None => UTC, // Fallback to UTC if no timezone is set
|
||||||
};
|
};
|
||||||
let _timezone = Tz::UTC;
|
let _timezone = UTC;
|
||||||
|
|
||||||
let timezone_time = cur.with_timezone(&timezone);
|
let timezone_time = cur.with_timezone(&timezone);
|
||||||
info!(
|
info!(
|
||||||
@@ -318,7 +316,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
info!("executing config mode override");
|
info!("executing config mode override");
|
||||||
//config upload will trigger reboot!
|
//config upload will trigger reboot!
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
spawner.spawn(httpd(reboot_now.clone(), stack.take().unwrap()))?;
|
spawner.spawn(http_server(reboot_now.clone(), stack.take().unwrap()))?;
|
||||||
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
||||||
} else {
|
} else {
|
||||||
LOG_ACCESS
|
LOG_ACCESS
|
||||||
@@ -396,11 +394,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
_water_frozen = true;
|
_water_frozen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Water temp is {}", water_temp.unwrap_or(0.));
|
info!("Water temp is {}", water_temp.as_ref().unwrap_or(&0.));
|
||||||
|
|
||||||
//publish_tank_state(&tank_state, &water_temp).await;
|
publish_tank_state(&mut board, &tank_state, water_temp).await;
|
||||||
|
|
||||||
let _plantstate: [PlantState; PLANT_COUNT] = [
|
let plantstate: [PlantState; PLANT_COUNT] = [
|
||||||
PlantState::read_hardware_state(0, &mut board).await,
|
PlantState::read_hardware_state(0, &mut board).await,
|
||||||
PlantState::read_hardware_state(1, &mut board).await,
|
PlantState::read_hardware_state(1, &mut board).await,
|
||||||
PlantState::read_hardware_state(2, &mut board).await,
|
PlantState::read_hardware_state(2, &mut board).await,
|
||||||
@@ -411,7 +409,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
PlantState::read_hardware_state(7, &mut board).await,
|
PlantState::read_hardware_state(7, &mut board).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
//publish_plant_states(&timezone_time.clone(), &plantstate).await;
|
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate).await;
|
||||||
|
|
||||||
// let pump_required = plantstate
|
// let pump_required = plantstate
|
||||||
// .iter()
|
// .iter()
|
||||||
@@ -568,12 +566,12 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
info!("Lightstate is {:?}", light_state);
|
info!("Lightstate is {:?}", light_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
match serde_json::to_string(&light_state) {
|
match &serde_json::to_string(&light_state) {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/light", state.as_bytes())
|
.mqtt_publish("/light", state)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -587,25 +585,25 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/deepsleep", "low Volt 12h".as_bytes()).await;
|
.mqtt_publish("/deepsleep", "low Volt 12h").await;
|
||||||
12 * 60
|
12 * 60
|
||||||
} else if is_day {
|
} else if is_day {
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/deepsleep", "normal 20m".as_bytes()).await;
|
.mqtt_publish("/deepsleep", "normal 20m").await;
|
||||||
20
|
20
|
||||||
} else {
|
} else {
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/deepsleep", "night 1h".as_bytes()).await;
|
.mqtt_publish("/deepsleep", "night 1h").await;
|
||||||
60
|
60
|
||||||
};
|
};
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/state", "sleep".as_bytes())
|
.mqtt_publish("/state", "sleep")
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
info!("Go to sleep for {} minutes", deep_sleep_duration_minutes);
|
info!("Go to sleep for {} minutes", deep_sleep_duration_minutes);
|
||||||
@@ -616,18 +614,17 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
//TODO
|
//TODO
|
||||||
//mark_app_valid();
|
//mark_app_valid();
|
||||||
|
|
||||||
let stay_alive_mqtt = STAY_ALIVE.load(Ordering::Relaxed);
|
let stay_alive = MQTT_STAY_ALIVE.load(Ordering::Relaxed);
|
||||||
|
|
||||||
let stay_alive = stay_alive_mqtt;
|
|
||||||
info!("Check stay alive, current state is {}", stay_alive);
|
info!("Check stay alive, current state is {}", stay_alive);
|
||||||
|
|
||||||
if stay_alive {
|
if stay_alive {
|
||||||
info!("Go to stay alive move");
|
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
//TODO
|
let _webserver = http_server(reboot_now.clone(), stack.take().unwrap());
|
||||||
//let _webserver = httpd(reboot_now.clone());
|
|
||||||
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
|
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
|
||||||
} else {
|
} else {
|
||||||
|
//TODO wait for all mqtt publishes?
|
||||||
|
Timer::after_millis(5000).await;
|
||||||
|
|
||||||
board.board_hal.get_esp().set_restart_to_conf(false);
|
board.board_hal.get_esp().set_restart_to_conf(false);
|
||||||
board
|
board
|
||||||
.board_hal
|
.board_hal
|
||||||
@@ -654,7 +651,6 @@ pub async fn do_secure_pump(
|
|||||||
Timer::after_millis(10).await;
|
Timer::after_millis(10).await;
|
||||||
for step in 0..plant_config.pump_time_s as usize {
|
for step in 0..plant_config.pump_time_s as usize {
|
||||||
let flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value();
|
let flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value();
|
||||||
let flow_value = 1;
|
|
||||||
flow_collector[step] = flow_value;
|
flow_collector[step] = flow_value;
|
||||||
let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
|
let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
|
||||||
|
|
||||||
@@ -736,7 +732,7 @@ pub async fn do_secure_pump(
|
|||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
//eg v3 without a sensor ends here, do not spam
|
//e.g., v3 without a sensor ends here, do not spam
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,9 +740,8 @@ pub async fn do_secure_pump(
|
|||||||
pump_time_s += 1;
|
pump_time_s += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
board.board_hal.get_tank_sensor().unwrap().stop_flow_meter();
|
board.board_hal.get_tank_sensor()?.stop_flow_meter();
|
||||||
let final_flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value();
|
let final_flow_value = board.board_hal.get_tank_sensor()?.get_flow_meter_value();
|
||||||
let final_flow_value = 12;
|
|
||||||
let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
|
let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse;
|
||||||
info!(
|
info!(
|
||||||
"Final flow value is {} with {} ml",
|
"Final flow value is {} with {} ml",
|
||||||
@@ -764,42 +759,64 @@ pub async fn do_secure_pump(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>) {
|
async fn update_charge_indicator(
|
||||||
|
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
|
) {
|
||||||
//we have mppt controller, ask it for charging current
|
//we have mppt controller, ask it for charging current
|
||||||
// let tank_state = determine_tank_state(&mut board);
|
let tank_state = determine_tank_state(board).await;
|
||||||
//
|
|
||||||
// if tank_state.is_enabled() {
|
if tank_state.is_enabled() {
|
||||||
// if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) {
|
if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) {
|
||||||
// match err {
|
match err {
|
||||||
// TankError::SensorDisabled => { /* unreachable */ }
|
TankError::SensorDisabled => { /* unreachable */ }
|
||||||
// TankError::SensorMissing(raw_value_mv) => log(
|
TankError::SensorMissing(raw_value_mv) => {
|
||||||
// LogMessage::TankSensorMissing,
|
LOG_ACCESS
|
||||||
// raw_value_mv as u32,
|
.lock()
|
||||||
// 0,
|
.await
|
||||||
// "",
|
.log(
|
||||||
// "",
|
LogMessage::TankSensorMissing,
|
||||||
// ).await,
|
raw_value_mv as u32,
|
||||||
// TankError::SensorValueError { value, min, max } => log(
|
0,
|
||||||
// LogMessage::TankSensorValueRangeError,
|
"",
|
||||||
// min as u32,
|
"",
|
||||||
// max as u32,
|
)
|
||||||
// &format!("{}", value),
|
.await
|
||||||
// "",
|
}
|
||||||
// ).await,
|
TankError::SensorValueError { value, min, max } => {
|
||||||
// TankError::BoardError(err) => {
|
LOG_ACCESS
|
||||||
// log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()).await
|
.lock()
|
||||||
// }
|
.await
|
||||||
// }
|
.log(
|
||||||
// // disabled cannot trigger this because of wrapping if is_enabled
|
LogMessage::TankSensorValueRangeError,
|
||||||
// board.board_hal.general_fault(true).await;
|
min as u32,
|
||||||
// } else if tank_state
|
max as u32,
|
||||||
// .warn_level(&board.board_hal.get_config().tank)
|
&format!("{}", value),
|
||||||
// .is_ok_and(|warn| warn)
|
"",
|
||||||
// {
|
)
|
||||||
// log(LogMessage::TankWaterLevelLow, 0, 0, "", "").await;
|
.await
|
||||||
// board.board_hal.general_fault(true).await;
|
}
|
||||||
// }
|
TankError::BoardError(err) => {
|
||||||
// }
|
LOG_ACCESS
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// disabled cannot trigger this because of wrapping if is_enabled
|
||||||
|
board.board_hal.general_fault(true).await;
|
||||||
|
} else if tank_state
|
||||||
|
.warn_level(&board.board_hal.get_config().tank)
|
||||||
|
.is_ok_and(|warn| warn)
|
||||||
|
{
|
||||||
|
LOG_ACCESS
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.log(LogMessage::TankWaterLevelLow, 0, 0, "", "")
|
||||||
|
.await;
|
||||||
|
board.board_hal.general_fault(true).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Ok(current) = board.board_hal.get_mptt_current().await {
|
if let Ok(current) = board.board_hal.get_mptt_current().await {
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
@@ -818,48 +835,39 @@ async fn update_charge_indicator(board: &mut MutexGuard<'_, CriticalSectionRawMu
|
|||||||
let _ = board.board_hal.set_charge_indicator(false);
|
let _ = board.board_hal.set_charge_indicator(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// async fn publish_tank_state(tank_state: &TankState, water_temp: &anyhow::Result<f32>) {
|
|
||||||
// let board = &mut BOARD_ACCESS.get().lock().await;
|
|
||||||
// match serde_json::to_string(
|
|
||||||
// &tank_state.as_mqtt_info(&board.board_hal.get_config().tank, water_temp),
|
|
||||||
// ) {
|
|
||||||
// Ok(state) => {
|
|
||||||
// let _ = board
|
|
||||||
// .board_hal
|
|
||||||
// .get_esp()
|
|
||||||
// .mqtt_publish("/water", state.as_bytes());
|
|
||||||
// }
|
|
||||||
// Err(err) => {
|
|
||||||
// info!("Error publishing tankstate {}", err);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async fn publish_plant_states(timezone_time: &DateTime<Tz>, plantstate: &[PlantState; 8]) {
|
async fn publish_tank_state(
|
||||||
// let board = &mut BOARD_ACCESS.get().lock().await;
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
// for (plant_id, (plant_state, plant_conf)) in plantstate
|
tank_state: &TankState,
|
||||||
// .iter()
|
water_temp: FatResult<f32>,
|
||||||
// .zip(&board.board_hal.get_config().plants.clone())
|
) {
|
||||||
// .enumerate()
|
let state = serde_json::to_string(
|
||||||
// {
|
&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
|
||||||
// match serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)) {
|
)
|
||||||
// Ok(state) => {
|
.unwrap();
|
||||||
// let plant_topic = format!("/plant{}", plant_id + 1);
|
let _ = board.board_hal.get_esp().mqtt_publish("/water", &*state);
|
||||||
// let _ = board
|
}
|
||||||
// .board_hal
|
|
||||||
// .get_esp()
|
async fn publish_plant_states(
|
||||||
// .mqtt_publish(&plant_topic, state.as_bytes())
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
// .await;
|
timezone_time: &DateTime<Tz>,
|
||||||
// //TODO? reduce speed as else messages will be dropped
|
plantstate: &[PlantState; 8],
|
||||||
// Timer::after_millis(200).await
|
) {
|
||||||
// }
|
for (plant_id, (plant_state, plant_conf)) in plantstate
|
||||||
// Err(err) => {
|
.iter()
|
||||||
// error!("Error publishing plant state {}", err);
|
.zip(&board.board_hal.get_config().plants.clone())
|
||||||
// }
|
.enumerate()
|
||||||
// };
|
{
|
||||||
// }
|
let state =
|
||||||
// }
|
serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)).unwrap();
|
||||||
|
let plant_topic = format!("/plant{}", plant_id + 1);
|
||||||
|
let _ = board
|
||||||
|
.board_hal
|
||||||
|
.get_esp()
|
||||||
|
.mqtt_publish(&plant_topic, &state)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn publish_firmware_info(
|
async fn publish_firmware_info(
|
||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
@@ -868,30 +876,33 @@ async fn publish_firmware_info(
|
|||||||
timezone_time: &String,
|
timezone_time: &String,
|
||||||
) {
|
) {
|
||||||
let esp = board.board_hal.get_esp();
|
let esp = board.board_hal.get_esp();
|
||||||
|
let _ = esp.mqtt_publish("/firmware/address", ip_address).await;
|
||||||
let _ = esp
|
let _ = esp
|
||||||
.mqtt_publish("/firmware/address", ip_address.as_bytes())
|
.mqtt_publish("/firmware/githash", &version.git_hash)
|
||||||
.await;
|
.await;
|
||||||
let _ = esp
|
let _ = esp
|
||||||
.mqtt_publish("/firmware/githash", version.git_hash.as_bytes())
|
.mqtt_publish("/firmware/buildtime", &version.build_time)
|
||||||
.await;
|
.await;
|
||||||
let _ = esp
|
let _ = esp.mqtt_publish("/firmware/last_online", timezone_time);
|
||||||
.mqtt_publish("/firmware/buildtime", version.build_time.as_bytes())
|
|
||||||
.await;
|
|
||||||
let _ = esp.mqtt_publish("/firmware/last_online", timezone_time.as_bytes());
|
|
||||||
let state = esp.get_ota_state();
|
let state = esp.get_ota_state();
|
||||||
let _ = esp
|
let _ = esp.mqtt_publish("/firmware/ota_state", &state).await;
|
||||||
.mqtt_publish("/firmware/ota_state", state.as_bytes())
|
|
||||||
.await;
|
|
||||||
let slot = esp.get_ota_slot();
|
let slot = esp.get_ota_slot();
|
||||||
let _ = esp
|
let _ = esp
|
||||||
.mqtt_publish("/firmware/ota_slot", format!("slot{slot}").as_bytes())
|
.mqtt_publish("/firmware/ota_slot", &format!("slot{slot}"))
|
||||||
.await;
|
.await;
|
||||||
let _ = esp.mqtt_publish("/state", "online".as_bytes()).await;
|
let _ = esp.mqtt_publish("/state", "online").await;
|
||||||
|
}
|
||||||
|
macro_rules! mk_static {
|
||||||
|
($t:ty,$val:expr) => {{
|
||||||
|
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||||
|
#[deny(unused_attributes)]
|
||||||
|
let x = STATIC_CELL.uninit().write(($val));
|
||||||
|
x
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn try_connect_wifi_sntp_mqtt(
|
async fn try_connect_wifi_sntp_mqtt(
|
||||||
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
mut stack_store: &mut OptionLock<Stack<'static>>,
|
stack_store: &mut OptionLock<Stack<'static>>,
|
||||||
) -> NetworkMode {
|
) -> NetworkMode {
|
||||||
let nw_conf = &board.board_hal.get_config().network.clone();
|
let nw_conf = &board.board_hal.get_config().network.clone();
|
||||||
match board.board_hal.get_esp().wifi(nw_conf).await {
|
match board.board_hal.get_esp().wifi(nw_conf).await {
|
||||||
@@ -917,8 +928,9 @@ async fn try_connect_wifi_sntp_mqtt(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mqtt_connected = if board.board_hal.get_config().network.mqtt_url.is_some() {
|
let mqtt_connected = if board.board_hal.get_config().network.mqtt_url.is_some() {
|
||||||
let nw_config = &board.board_hal.get_config().network.clone();
|
let nw_config = board.board_hal.get_config().network.clone();
|
||||||
match board.board_hal.get_esp().mqtt(nw_config).await {
|
let nw_config = mk_static!(NetworkConfig, nw_config);
|
||||||
|
match board.board_hal.get_esp().mqtt(nw_config, stack).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Mqtt connection ready");
|
info!("Mqtt connection ready");
|
||||||
true
|
true
|
||||||
@@ -957,25 +969,23 @@ async fn pump_info(
|
|||||||
let pump_info = PumpInfo {
|
let pump_info = PumpInfo {
|
||||||
enabled: pump_active,
|
enabled: pump_active,
|
||||||
pump_ineffective,
|
pump_ineffective,
|
||||||
median_current_ma: median_current_ma,
|
median_current_ma,
|
||||||
max_current_ma: max_current_ma,
|
max_current_ma,
|
||||||
min_current_ma: min_current_ma,
|
min_current_ma,
|
||||||
};
|
};
|
||||||
let pump_topic = format!("/pump{}", plant_id + 1);
|
let pump_topic = format!("/pump{}", plant_id + 1);
|
||||||
|
|
||||||
match serde_json::to_string(&pump_info) {
|
match serde_json::to_string(&pump_info) {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
let _ = BOARD_ACCESS
|
BOARD_ACCESS
|
||||||
.get()
|
.get()
|
||||||
.await
|
.await
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish(&pump_topic, state.as_bytes());
|
.mqtt_publish(&pump_topic, &state)
|
||||||
//reduce speed as else messages will be dropped
|
.await;
|
||||||
//TODO maybee not required for low level hal?
|
|
||||||
Timer::after_millis(200).await;
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Error publishing pump state {}", err);
|
warn!("Error publishing pump state {}", err);
|
||||||
@@ -992,9 +1002,7 @@ async fn publish_mppt_state(
|
|||||||
current_ma: current.as_milliamperes() as u32,
|
current_ma: current.as_milliamperes() as u32,
|
||||||
voltage_ma: voltage.as_millivolts() as u32,
|
voltage_ma: voltage.as_millivolts() as u32,
|
||||||
};
|
};
|
||||||
if let Ok(serialized_solar_state_bytes) =
|
if let Ok(serialized_solar_state_bytes) = serde_json::to_string(&solar_state) {
|
||||||
serde_json::to_string(&solar_state).map(|s| s.into_bytes())
|
|
||||||
{
|
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
@@ -1014,9 +1022,9 @@ async fn publish_battery_state(
|
|||||||
let value = match state {
|
let value = match state {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
let json = serde_json::to_string(&state).unwrap().to_owned();
|
let json = serde_json::to_string(&state).unwrap().to_owned();
|
||||||
json.as_bytes().to_owned()
|
json.to_owned()
|
||||||
}
|
}
|
||||||
Err(_) => "error".as_bytes().to_owned(),
|
Err(_) => "error".to_owned(),
|
||||||
};
|
};
|
||||||
{
|
{
|
||||||
let _ = board
|
let _ = board
|
||||||
@@ -1083,7 +1091,7 @@ async fn wait_infinity(
|
|||||||
|
|
||||||
Timer::after_millis(delay).await;
|
Timer::after_millis(delay).await;
|
||||||
|
|
||||||
if wait_type == WaitType::MqttConfig && !STAY_ALIVE.load(Ordering::Relaxed) {
|
if wait_type == WaitType::MqttConfig && !MQTT_STAY_ALIVE.load(Ordering::Relaxed) {
|
||||||
reboot_now.store(true, Ordering::Relaxed);
|
reboot_now.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
if reboot_now.load(Ordering::Relaxed) {
|
if reboot_now.load(Ordering::Relaxed) {
|
||||||
@@ -1107,7 +1115,7 @@ async fn main(spawner: Spawner) -> ! {
|
|||||||
logger::init_logger_from_env();
|
logger::init_logger_from_env();
|
||||||
//force init here!
|
//force init here!
|
||||||
println!("Hal init");
|
println!("Hal init");
|
||||||
match BOARD_ACCESS.init(PlantHal::create(spawner).await.unwrap()) {
|
match BOARD_ACCESS.init(PlantHal::create().await.unwrap()) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
panic!("Could not set hal to static")
|
panic!("Could not set hal to static")
|
||||||
|
|||||||
@@ -118,7 +118,11 @@ fn map_range_moisture(
|
|||||||
impl PlantState {
|
impl PlantState {
|
||||||
pub async fn read_hardware_state(plant_id: usize, board: &mut HAL<'_>) -> Self {
|
pub async fn read_hardware_state(plant_id: usize, board: &mut HAL<'_>) -> Self {
|
||||||
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
|
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
|
||||||
match board.board_hal.measure_moisture_hz(plant_id, Sensor::A).await {
|
match board
|
||||||
|
.board_hal
|
||||||
|
.measure_moisture_hz(plant_id, Sensor::A)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(raw) => match map_range_moisture(
|
Ok(raw) => match map_range_moisture(
|
||||||
raw,
|
raw,
|
||||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
||||||
@@ -139,7 +143,11 @@ impl PlantState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
|
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
|
||||||
match board.board_hal.measure_moisture_hz(plant_id, Sensor::B).await {
|
match board
|
||||||
|
.board_hal
|
||||||
|
.measure_moisture_hz(plant_id, Sensor::B)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(raw) => match map_range_moisture(
|
Ok(raw) => match map_range_moisture(
|
||||||
raw,
|
raw,
|
||||||
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
|
||||||
@@ -264,50 +272,50 @@ impl PlantState {
|
|||||||
PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time),
|
PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// pub fn to_mqtt_info(
|
pub fn to_mqtt_info(
|
||||||
// &self,
|
&self,
|
||||||
// plant_conf: &PlantConfig,
|
plant_conf: &PlantConfig,
|
||||||
// current_time: &DateTime<Tz>,
|
current_time: &DateTime<Tz>,
|
||||||
// ) -> PlantInfo<'_> {
|
) -> PlantInfo<'_> {
|
||||||
// PlantInfo {
|
PlantInfo {
|
||||||
// sensor_a: &self.sensor_a,
|
sensor_a: &self.sensor_a,
|
||||||
// sensor_b: &self.sensor_b,
|
sensor_b: &self.sensor_b,
|
||||||
// mode: plant_conf.mode,
|
mode: plant_conf.mode,
|
||||||
// do_water: self.needs_to_be_watered(plant_conf, current_time),
|
do_water: self.needs_to_be_watered(plant_conf, current_time),
|
||||||
// dry: if let Some(moisture_percent) = self.plant_moisture().0 {
|
dry: if let Some(moisture_percent) = self.plant_moisture().0 {
|
||||||
// moisture_percent < plant_conf.target_moisture
|
moisture_percent < plant_conf.target_moisture
|
||||||
// } else {
|
} else {
|
||||||
// false
|
false
|
||||||
// },
|
},
|
||||||
// cooldown: self.pump_in_timeout(plant_conf, current_time),
|
cooldown: self.pump_in_timeout(plant_conf, current_time),
|
||||||
// out_of_work_hour: in_time_range(
|
out_of_work_hour: in_time_range(
|
||||||
// current_time,
|
current_time,
|
||||||
// plant_conf.pump_hour_start,
|
plant_conf.pump_hour_start,
|
||||||
// plant_conf.pump_hour_end,
|
plant_conf.pump_hour_end,
|
||||||
// ),
|
),
|
||||||
// consecutive_pump_count: self.pump.consecutive_pump_count,
|
consecutive_pump_count: self.pump.consecutive_pump_count,
|
||||||
// pump_error: self.pump.is_err(plant_conf),
|
pump_error: self.pump.is_err(plant_conf),
|
||||||
// last_pump: self
|
last_pump: self
|
||||||
// .pump
|
.pump
|
||||||
// .previous_pump
|
.previous_pump
|
||||||
// .map(|t| t.with_timezone(¤t_time.timezone())),
|
.map(|t| t.with_timezone(¤t_time.timezone())),
|
||||||
// next_pump: if matches!(
|
next_pump: if matches!(
|
||||||
// plant_conf.mode,
|
plant_conf.mode,
|
||||||
// PlantWateringMode::TimerOnly
|
PlantWateringMode::TimerOnly
|
||||||
// | PlantWateringMode::TargetMoisture
|
| PlantWateringMode::TargetMoisture
|
||||||
// | PlantWateringMode::MinMoisture
|
| PlantWateringMode::MinMoisture
|
||||||
// ) {
|
) {
|
||||||
// self.pump.previous_pump.and_then(|last_pump| {
|
self.pump.previous_pump.and_then(|last_pump| {
|
||||||
// last_pump
|
last_pump
|
||||||
// .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
|
.checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
|
||||||
// .map(|t| t.with_timezone(¤t_time.timezone()))
|
.map(|t| t.with_timezone(¤t_time.timezone()))
|
||||||
// })
|
})
|
||||||
// } else {
|
} else {
|
||||||
// None
|
None
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize)]
|
#[derive(Debug, PartialEq, Serialize)]
|
||||||
@@ -330,8 +338,8 @@ pub struct PlantInfo<'a> {
|
|||||||
/// how often has the pump been watered without reaching target moisture
|
/// how often has the pump been watered without reaching target moisture
|
||||||
consecutive_pump_count: u32,
|
consecutive_pump_count: u32,
|
||||||
pump_error: Option<PumpError>,
|
pump_error: Option<PumpError>,
|
||||||
// /// last time when the pump was active
|
/// last time when the pump was active
|
||||||
// last_pump: Option<DateTime<Tz>>,
|
last_pump: Option<DateTime<Tz>>,
|
||||||
// /// next time when pump should activate
|
/// next time when pump should activate
|
||||||
// next_pump: Option<DateTime<Tz>>,
|
next_pump: Option<DateTime<Tz>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ where
|
|||||||
let mut offset = 0_usize;
|
let mut offset = 0_usize;
|
||||||
let mut buf = [0_u8; 32];
|
let mut buf = [0_u8; 32];
|
||||||
|
|
||||||
let mut checksum = crate::hal::rtc::X25.digest();
|
let mut checksum = X25.digest();
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ where
|
|||||||
Some(200)
|
Some(200)
|
||||||
}
|
}
|
||||||
Method::Get => {
|
Method::Get => {
|
||||||
let disp = format!("attachment; filename=\"{filename}\"");
|
let disposition = format!("attachment; filename=\"{filename}\"");
|
||||||
let size = {
|
let size = {
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
board
|
board
|
||||||
@@ -63,7 +63,7 @@ where
|
|||||||
Some("OK"),
|
Some("OK"),
|
||||||
&[
|
&[
|
||||||
("Content-Type", "application/octet-stream"),
|
("Content-Type", "application/octet-stream"),
|
||||||
("Content-Disposition", disp.as_str()),
|
("Content-Disposition", disposition.as_str()),
|
||||||
("Content-Length", &format!("{}", size)),
|
("Content-Length", &format!("{}", size)),
|
||||||
("Access-Control-Allow-Origin", "*"),
|
("Access-Control-Allow-Origin", "*"),
|
||||||
("Access-Control-Allow-Headers", "*"),
|
("Access-Control-Allow-Headers", "*"),
|
||||||
@@ -75,7 +75,7 @@ where
|
|||||||
let mut chunk = 0;
|
let mut chunk = 0;
|
||||||
loop {
|
loop {
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
board.board_hal.progress(chunk as u32).await;
|
board.board_hal.progress(chunk).await;
|
||||||
let read_chunk = board
|
let read_chunk = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
@@ -107,7 +107,7 @@ where
|
|||||||
Method::Post => {
|
Method::Post => {
|
||||||
{
|
{
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
//ensure file is deleted, otherwise we would need to truncate the file which will not work with streaming
|
//ensure the file is deleted first; otherwise we would need to truncate the file which will not work with streaming
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::fat_error::{FatError, FatResult};
|
use crate::fat_error::{FatError, FatResult};
|
||||||
use crate::hal::{esp_time, PLANT_COUNT};
|
use crate::hal::{esp_time, PLANT_COUNT};
|
||||||
use crate::log::{LogMessage, LOG_ACCESS};
|
use crate::log::LogMessage;
|
||||||
use crate::plant_state::{MoistureSensorState, PlantState};
|
use crate::plant_state::{MoistureSensorState, PlantState};
|
||||||
use crate::tank::determine_tank_state;
|
use crate::tank::determine_tank_state;
|
||||||
use crate::{get_version, BOARD_ACCESS};
|
use crate::{get_version, BOARD_ACCESS};
|
||||||
@@ -11,7 +11,6 @@ use chrono_tz::Tz;
|
|||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use edge_http::io::server::Connection;
|
use edge_http::io::server::Connection;
|
||||||
use embedded_io_async::{Read, Write};
|
use embedded_io_async::{Read, Write};
|
||||||
use log::info;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
|
|||||||
Reference in New Issue
Block a user