Improve error handling, ensure robust defaults, and eliminate unsafe unwraps/expectations across modules.
This commit is contained in:
@@ -136,6 +136,7 @@ measurements = "0.11.1"
|
|||||||
|
|
||||||
# Project-specific
|
# Project-specific
|
||||||
mcutie = { version = "0.3.0", default-features = false, features = ["log", "homeassistant"] }
|
mcutie = { version = "0.3.0", default-features = false, features = ["log", "homeassistant"] }
|
||||||
|
no-panic = "0.1.36"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
mcutie = { git = 'https://github.com/empirephoenix/mcutie.git' }
|
mcutie = { git = 'https://github.com/empirephoenix/mcutie.git' }
|
||||||
|
|||||||
2
Software/MainBoard/rust/clippy.toml
Normal file
2
Software/MainBoard/rust/clippy.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# This file is used for clippy configuration.
|
||||||
|
# It shouldn't contain the deny attributes, which belong to the crate root.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
|
use chrono::format::ParseErrorKind;
|
||||||
|
use chrono_tz::ParseError;
|
||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
@@ -149,6 +151,23 @@ impl<T> ContextExt<T> for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, E> ContextExt<T> for Result<T, E>
|
||||||
|
where
|
||||||
|
E: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn context<C>(self, context: C) -> Result<T, FatError>
|
||||||
|
where
|
||||||
|
C: AsRef<str>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Ok(value) => Ok(value),
|
||||||
|
Err(err) => Err(FatError::String {
|
||||||
|
error: format!("{}: {:?}", context.as_ref(), err),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Error<Infallible>> for FatError {
|
impl From<Error<Infallible>> for FatError {
|
||||||
fn from(error: Error<Infallible>) -> Self {
|
fn from(error: Error<Infallible>) -> Self {
|
||||||
FatError::OneWireError { error }
|
FatError::OneWireError { error }
|
||||||
@@ -283,7 +302,7 @@ impl<E: fmt::Debug> From<ShuntVoltageReadError<I2cDeviceError<E>>> for FatError
|
|||||||
|
|
||||||
impl From<Infallible> for FatError {
|
impl From<Infallible> for FatError {
|
||||||
fn from(value: Infallible) -> Self {
|
fn from(value: Infallible) -> Self {
|
||||||
panic!("Infallible error: {:?}", value)
|
match value {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,3 +355,27 @@ impl From<BmsProtocolError> for FatError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ParseError> for FatError {
|
||||||
|
fn from(value: ParseError) -> Self {
|
||||||
|
FatError::String {
|
||||||
|
error: format!("Parsing error: {value:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseErrorKind> for FatError {
|
||||||
|
fn from(value: ParseErrorKind) -> Self {
|
||||||
|
FatError::String {
|
||||||
|
error: format!("Parsing error: {value:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<chrono::format::ParseError> for FatError {
|
||||||
|
fn from(value: chrono::format::ParseError) -> Self {
|
||||||
|
FatError::String {
|
||||||
|
error: format!("Parsing error: {value:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -314,17 +314,19 @@ impl Esp<'_> {
|
|||||||
&mut tx_meta,
|
&mut tx_meta,
|
||||||
&mut tx_buffer,
|
&mut tx_buffer,
|
||||||
);
|
);
|
||||||
socket.bind(123).unwrap();
|
socket.bind(123).context("Could not bind UDP socket")?;
|
||||||
|
|
||||||
let context = NtpContext::new(Timestamp::default());
|
let context = NtpContext::new(Timestamp::default());
|
||||||
|
|
||||||
let ntp_addrs = stack
|
let ntp_addrs = stack
|
||||||
.dns_query(NTP_SERVER, DnsQueryType::A)
|
.dns_query(NTP_SERVER, DnsQueryType::A)
|
||||||
.await;
|
.await
|
||||||
if ntp_addrs.is_err() {
|
.context("Failed to resolve DNS")?;
|
||||||
bail!("Failed to resolve DNS");
|
|
||||||
|
if ntp_addrs.is_empty() {
|
||||||
|
bail!("No IP addresses found for NTP server");
|
||||||
}
|
}
|
||||||
let ntp = ntp_addrs.unwrap()[0];
|
let ntp = ntp_addrs[0];
|
||||||
info!("NTP server: {ntp:?}");
|
info!("NTP server: {ntp:?}");
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
@@ -416,9 +418,14 @@ impl Esp<'_> {
|
|||||||
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
Err(_) => "PlantCtrl Emergency Mode".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let device = self.interface_ap.take().unwrap();
|
let device = self
|
||||||
|
.interface_ap
|
||||||
|
.take()
|
||||||
|
.context("AP interface already taken")?;
|
||||||
let gw_ip_addr_str = "192.168.71.1";
|
let gw_ip_addr_str = "192.168.71.1";
|
||||||
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
|
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).map_err(|_| FatError::String {
|
||||||
|
error: "failed to parse gateway ip".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
|
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
|
||||||
address: Ipv4Cidr::new(gw_ip_addr, 24),
|
address: Ipv4Cidr::new(gw_ip_addr, 24),
|
||||||
@@ -472,18 +479,17 @@ impl Esp<'_> {
|
|||||||
spawner: Spawner,
|
spawner: Spawner,
|
||||||
) -> FatResult<Stack<'static>> {
|
) -> FatResult<Stack<'static>> {
|
||||||
esp_radio::wifi_set_log_verbose();
|
esp_radio::wifi_set_log_verbose();
|
||||||
let ssid = network_config.ssid.clone();
|
let ssid = match &network_config.ssid {
|
||||||
match &ssid {
|
|
||||||
Some(ssid) => {
|
Some(ssid) => {
|
||||||
if ssid.is_empty() {
|
if ssid.is_empty() {
|
||||||
bail!("Wifi ssid was empty")
|
bail!("Wifi ssid was empty")
|
||||||
}
|
}
|
||||||
|
ssid.to_string()
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
bail!("Wifi ssid was empty")
|
bail!("Wifi ssid was empty")
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
let ssid = ssid.unwrap().to_string();
|
|
||||||
info!("attempting to connect wifi {ssid}");
|
info!("attempting to connect wifi {ssid}");
|
||||||
let password = match network_config.password {
|
let password = match network_config.password {
|
||||||
Some(ref password) => password.to_string(),
|
Some(ref password) => password.to_string(),
|
||||||
@@ -491,7 +497,10 @@ impl Esp<'_> {
|
|||||||
};
|
};
|
||||||
let max_wait = network_config.max_wait;
|
let max_wait = network_config.max_wait;
|
||||||
|
|
||||||
let device = self.interface_sta.take().unwrap();
|
let device = self
|
||||||
|
.interface_sta
|
||||||
|
.take()
|
||||||
|
.context("STA interface already taken")?;
|
||||||
let config = embassy_net::Config::dhcpv4(DhcpConfig::default());
|
let config = embassy_net::Config::dhcpv4(DhcpConfig::default());
|
||||||
|
|
||||||
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
|
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
|
||||||
@@ -596,9 +605,9 @@ impl Esp<'_> {
|
|||||||
if let Ok(cur) = self.ota.current_ota_state() {
|
if let Ok(cur) = self.ota.current_ota_state() {
|
||||||
if cur == OtaImageState::PendingVerify {
|
if cur == OtaImageState::PendingVerify {
|
||||||
info!("Marking OTA image as valid");
|
info!("Marking OTA image as valid");
|
||||||
self.ota
|
if let Err(err) = self.ota.set_current_ota_state(Valid) {
|
||||||
.set_current_ota_state(Valid)
|
error!("Could not set image to valid: {:?}", err);
|
||||||
.expect("Could not set image to valid");
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("No OTA image to mark as valid");
|
info!("No OTA image to mark as valid");
|
||||||
@@ -753,10 +762,7 @@ impl Esp<'_> {
|
|||||||
network_config.mqtt_user.as_ref(),
|
network_config.mqtt_user.as_ref(),
|
||||||
network_config.mqtt_password.as_ref(),
|
network_config.mqtt_password.as_ref(),
|
||||||
) {
|
) {
|
||||||
builder = builder.with_authentication(
|
builder = builder.with_authentication(mqtt_user, mqtt_password);
|
||||||
mqtt_user,
|
|
||||||
mqtt_password,
|
|
||||||
);
|
|
||||||
info!("With authentification");
|
info!("With authentification");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,11 +814,10 @@ impl Esp<'_> {
|
|||||||
Timer::after(Duration::from_millis(100)).await;
|
Timer::after(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Topic::General(round_trip_topic.clone())
|
let _ = Topic::General(round_trip_topic.clone())
|
||||||
.with_display("online_text")
|
.with_display("online_text")
|
||||||
.publish()
|
.publish()
|
||||||
.await
|
.await;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let timeout = {
|
let timeout = {
|
||||||
let guard = TIME_ACCESS.get().await.lock().await;
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
@@ -974,7 +979,13 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
|
|||||||
use edge_nal::UdpBind;
|
use edge_nal::UdpBind;
|
||||||
use edge_nal_embassy::{Udp, UdpBuffers};
|
use edge_nal_embassy::{Udp, UdpBuffers};
|
||||||
|
|
||||||
let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip");
|
let ip = match Ipv4Addr::from_str(gw_ip_addr) {
|
||||||
|
Ok(ip) => ip,
|
||||||
|
Err(_) => {
|
||||||
|
error!("dhcp task failed to parse gw ip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut buf = [0u8; 1500];
|
let mut buf = [0u8; 1500];
|
||||||
|
|
||||||
@@ -982,13 +993,19 @@ 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 = match unbound_socket
|
||||||
.bind(SocketAddr::V4(SocketAddrV4::new(
|
.bind(SocketAddr::V4(SocketAddrV4::new(
|
||||||
Ipv4Addr::UNSPECIFIED,
|
Ipv4Addr::UNSPECIFIED,
|
||||||
DEFAULT_SERVER_PORT,
|
DEFAULT_SERVER_PORT,
|
||||||
)))
|
)))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
{
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
error!("dhcp task failed to bind socket: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
_ = io::server::run(
|
_ = io::server::run(
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ pub trait BoardInteraction<'a> {
|
|||||||
async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>;
|
async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>;
|
||||||
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
|
async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
|
||||||
|
|
||||||
|
|
||||||
fn is_day(&self) -> bool;
|
fn is_day(&self) -> bool;
|
||||||
//should be multsampled
|
//should be multsampled
|
||||||
async fn light(&mut self, enable: bool) -> FatResult<()>;
|
async fn light(&mut self, enable: bool) -> FatResult<()>;
|
||||||
@@ -165,7 +164,7 @@ pub trait BoardInteraction<'a> {
|
|||||||
async fn get_mptt_current(&mut self) -> FatResult<Current>;
|
async fn get_mptt_current(&mut self) -> FatResult<Current>;
|
||||||
async fn can_power(&mut self, state: bool) -> FatResult<()>;
|
async fn can_power(&mut self, state: bool) -> FatResult<()>;
|
||||||
|
|
||||||
async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>;
|
async fn backup_config(&mut self, config: &PlantControllerConfig) -> FatResult<()>;
|
||||||
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig>;
|
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig>;
|
||||||
async fn backup_info(&mut self) -> FatResult<BackupHeader>;
|
async fn backup_info(&mut self) -> FatResult<BackupHeader>;
|
||||||
|
|
||||||
@@ -271,12 +270,17 @@ impl PlantHal {
|
|||||||
let rng = Rng::new();
|
let rng = Rng::new();
|
||||||
let esp_wifi_ctrl = &*mk_static!(
|
let esp_wifi_ctrl = &*mk_static!(
|
||||||
Controller<'static>,
|
Controller<'static>,
|
||||||
init().expect("Could not init wifi controller")
|
init().map_err(|e| FatError::String {
|
||||||
|
error: format!("Could not init wifi controller: {:?}", e)
|
||||||
|
})?
|
||||||
);
|
);
|
||||||
|
|
||||||
let (controller, interfaces) =
|
let (controller, interfaces) =
|
||||||
esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default())
|
esp_radio::wifi::new(esp_wifi_ctrl, peripherals.WIFI, Default::default()).map_err(
|
||||||
.expect("Could not init wifi");
|
|e| FatError::String {
|
||||||
|
error: format!("Could not init wifi: {:?}", e),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
let pcnt_module = Pcnt::new(peripherals.PCNT);
|
let pcnt_module = Pcnt::new(peripherals.PCNT);
|
||||||
|
|
||||||
@@ -330,7 +334,7 @@ impl PlantHal {
|
|||||||
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||||
DataPartitionSubType::Ota,
|
DataPartitionSubType::Ota,
|
||||||
))?
|
))?
|
||||||
.expect("No OTA data partition found")
|
.context("No OTA data partition found")?
|
||||||
);
|
);
|
||||||
|
|
||||||
let ota_data = mk_static!(
|
let ota_data = mk_static!(
|
||||||
@@ -375,7 +379,7 @@ impl PlantHal {
|
|||||||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||||
DataPartitionSubType::LittleFs,
|
DataPartitionSubType::LittleFs,
|
||||||
))?
|
))?
|
||||||
.expect("Data partition with littlefs not found");
|
.context("Data partition with littlefs not found")?;
|
||||||
let data_partition = mk_static!(PartitionEntry, data_partition);
|
let data_partition = mk_static!(PartitionEntry, data_partition);
|
||||||
|
|
||||||
let data = mk_static!(
|
let data = mk_static!(
|
||||||
@@ -399,7 +403,8 @@ impl PlantHal {
|
|||||||
|
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
let fs = Arc::new(Mutex::new(
|
let fs = Arc::new(Mutex::new(
|
||||||
lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"),
|
lfs2Filesystem::mount(alloc, lfs2filesystem)
|
||||||
|
.context("Could not mount lfs2 filesystem")?,
|
||||||
));
|
));
|
||||||
|
|
||||||
let uart0 =
|
let uart0 =
|
||||||
@@ -493,7 +498,9 @@ impl PlantHal {
|
|||||||
RefCell<I2c<Blocking>>,
|
RefCell<I2c<Blocking>>,
|
||||||
> = CriticalSectionMutex::new(RefCell::new(i2c));
|
> = CriticalSectionMutex::new(RefCell::new(i2c));
|
||||||
|
|
||||||
I2C_DRIVER.init(i2c_bus).expect("Could not init i2c driver");
|
I2C_DRIVER.init(i2c_bus).map_err(|_| FatError::String {
|
||||||
|
error: "Could not init i2c driver".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
let i2c_bus = I2C_DRIVER.get().await;
|
let i2c_bus = I2C_DRIVER.get().await;
|
||||||
let rtc_device = I2cDevice::new(i2c_bus);
|
let rtc_device = I2cDevice::new(i2c_bus);
|
||||||
@@ -655,7 +662,7 @@ pub fn next_partition(current: AppPartitionSubType) -> FatResult<AppPartitionSub
|
|||||||
|
|
||||||
pub async fn esp_time() -> DateTime<Utc> {
|
pub async fn esp_time() -> DateTime<Utc> {
|
||||||
let guard = TIME_ACCESS.get().await.lock().await;
|
let guard = TIME_ACCESS.get().await.lock().await;
|
||||||
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap()
|
DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap_or(DateTime::UNIX_EPOCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
|
pub async fn esp_set_time(time: DateTime<FixedOffset>) -> FatResult<()> {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use ina219::SyncIna219;
|
|||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use measurements::Resistance;
|
use measurements::Resistance;
|
||||||
use measurements::{Current, Voltage};
|
use measurements::{Current, Voltage};
|
||||||
|
// use no_panic::no_panic;
|
||||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
||||||
|
|
||||||
pub const BACKUP_HEADER_MAX_SIZE: usize = 64;
|
pub const BACKUP_HEADER_MAX_SIZE: usize = 64;
|
||||||
@@ -368,7 +369,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
||||||
self.can_power.set_high();
|
self.can_power.set_high();
|
||||||
Timer::after_millis(500).await;
|
Timer::after_millis(500).await;
|
||||||
let config = self.twai_config.take().expect("twai config not set");
|
let config = self.twai_config.take().context("twai config not set")?;
|
||||||
let mut twai = config.into_async().start();
|
let mut twai = config.into_async().start();
|
||||||
|
|
||||||
if twai.is_bus_off() {
|
if twai.is_bus_off() {
|
||||||
@@ -398,7 +399,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
async fn detect_sensors(&mut self, request: Detection) -> FatResult<Detection> {
|
||||||
self.can_power.set_high();
|
self.can_power.set_high();
|
||||||
Timer::after_millis(500).await;
|
Timer::after_millis(500).await;
|
||||||
let config = self.twai_config.take().expect("twai config not set");
|
let config = self.twai_config.take().context("twai config not set")?;
|
||||||
let mut twai = config.into_async().start();
|
let mut twai = config.into_async().start();
|
||||||
|
|
||||||
if twai.is_bus_off() {
|
if twai.is_bus_off() {
|
||||||
@@ -542,8 +543,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn backup_config(&mut self, controller_config: &PlantControllerConfig) -> FatResult<()>{
|
async fn backup_config(&mut self, controller_config: &PlantControllerConfig) -> FatResult<()> {
|
||||||
let mut buffer: [u8; 4096-BACKUP_HEADER_MAX_SIZE] = [0; 4096-BACKUP_HEADER_MAX_SIZE];
|
let mut buffer: [u8; 4096 - BACKUP_HEADER_MAX_SIZE] = [0; 4096 - BACKUP_HEADER_MAX_SIZE];
|
||||||
let length = bincode::encode_into_slice(controller_config, &mut buffer, CONFIG)?;
|
let length = bincode::encode_into_slice(controller_config, &mut buffer, CONFIG)?;
|
||||||
let mut checksum = X25.digest();
|
let mut checksum = X25.digest();
|
||||||
checksum.update(&buffer[..length]);
|
checksum.update(&buffer[..length]);
|
||||||
@@ -562,7 +563,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
|
|
||||||
while to_write > 0 {
|
while to_write > 0 {
|
||||||
self.progress(chunk as u32).await;
|
self.progress(chunk as u32).await;
|
||||||
let start = BACKUP_HEADER_MAX_SIZE + chunk* EEPROM_PAGE;
|
let start = BACKUP_HEADER_MAX_SIZE + chunk * EEPROM_PAGE;
|
||||||
let end = start + crate::hal::rtc::EEPROM_PAGE;
|
let end = start + crate::hal::rtc::EEPROM_PAGE;
|
||||||
let part = &buffer[start..end];
|
let part = &buffer[start..end];
|
||||||
to_write -= part.len();
|
to_write -= part.len();
|
||||||
@@ -576,7 +577,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig> {
|
async fn read_backup(&mut self) -> FatResult<PlantControllerConfig> {
|
||||||
let info = self.backup_info().await?;
|
let info = self.backup_info().await?;
|
||||||
let mut store = alloc::vec![0_u8; info.size as usize];
|
let mut store = alloc::vec![0_u8; info.size as usize];
|
||||||
self.rtc_module.read(BACKUP_HEADER_MAX_SIZE as u32, store.as_mut_slice())?;
|
self.rtc_module
|
||||||
|
.read(BACKUP_HEADER_MAX_SIZE as u32, store.as_mut_slice())?;
|
||||||
let mut checksum = X25.digest();
|
let mut checksum = X25.digest();
|
||||||
checksum.update(&store[..]);
|
checksum.update(&store[..]);
|
||||||
let crc = checksum.finalize();
|
let crc = checksum.finalize();
|
||||||
@@ -593,7 +595,10 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
|
|
||||||
let info: Result<(BackupHeader, usize), bincode::error::DecodeError> =
|
let info: Result<(BackupHeader, usize), bincode::error::DecodeError> =
|
||||||
bincode::decode_from_slice(&header_page_buffer[..], CONFIG);
|
bincode::decode_from_slice(&header_page_buffer[..], CONFIG);
|
||||||
info.map(|(header, _)| header).map_err(|e| FatError::String {error:"Could not read backup header: ".to_string() + &e.to_string()})
|
info.map(|(header, _)| header)
|
||||||
|
.map_err(|e| FatError::String {
|
||||||
|
error: "Could not read backup header: ".to_string() + &e.to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,8 +659,6 @@ async fn wait_for_can_measurements(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Moistures> for Detection {
|
impl From<Moistures> for Detection {
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ impl<'a> TankSensor<'a> {
|
|||||||
one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None));
|
one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None));
|
||||||
|
|
||||||
let mut adc1_config = AdcConfig::new();
|
let mut adc1_config = AdcConfig::new();
|
||||||
let tank_pin = adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB);
|
let tank_pin =
|
||||||
|
adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB);
|
||||||
let 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);
|
||||||
@@ -141,12 +142,17 @@ impl<'a> TankSensor<'a> {
|
|||||||
let value = self.tank_channel.read_oneshot(&mut self.tank_pin);
|
let value = self.tank_channel.read_oneshot(&mut self.tank_pin);
|
||||||
//force yield
|
//force yield
|
||||||
Timer::after_millis(10).await;
|
Timer::after_millis(10).await;
|
||||||
*sample = value.unwrap();
|
match value {
|
||||||
|
Ok(v) => *sample = v,
|
||||||
|
Err(e) => {
|
||||||
|
bail!("ADC Hardware error: {:?}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
self.tank_power.set_low();
|
self.tank_power.set_low();
|
||||||
|
|
||||||
store.sort();
|
store.sort();
|
||||||
let median_mv = store[TANK_MULTI_SAMPLE / 2] as f32;
|
let median_mv = store[TANK_MULTI_SAMPLE / 2] as f32;
|
||||||
Ok(median_mv/1000.0)
|
Ok(median_mv / 1000.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ pub async fn log(
|
|||||||
impl LogArray {
|
impl LogArray {
|
||||||
pub fn get(&mut self) -> Vec<LogEntry> {
|
pub fn get(&mut self) -> Vec<LogEntry> {
|
||||||
let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
||||||
RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap());
|
RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0));
|
||||||
|
|
||||||
let mut rv: Vec<LogEntry> = Vec::new();
|
let mut rv: Vec<LogEntry> = Vec::new();
|
||||||
let mut index = head.wrapping_sub(1);
|
let mut index = head.wrapping_sub(1);
|
||||||
@@ -120,7 +120,7 @@ impl LogArray {
|
|||||||
txt_long: &str,
|
txt_long: &str,
|
||||||
) {
|
) {
|
||||||
let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> =
|
||||||
RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap());
|
RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0));
|
||||||
|
|
||||||
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
|
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
|
||||||
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
|
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
|
||||||
@@ -281,6 +281,8 @@ pub enum LogMessage {
|
|||||||
PumpMissingSensorCurrent,
|
PumpMissingSensorCurrent,
|
||||||
#[strum(serialize = "MPPT Current sensor could not be reached")]
|
#[strum(serialize = "MPPT Current sensor could not be reached")]
|
||||||
MPPTError,
|
MPPTError,
|
||||||
|
#[strum(serialize = "Parsing error reading message")]
|
||||||
|
UnknownMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -301,7 +303,7 @@ impl From<&LogMessage> for MessageTranslation {
|
|||||||
impl LogMessage {
|
impl LogMessage {
|
||||||
pub fn log_localisation_config() -> Vec<MessageTranslation> {
|
pub fn log_localisation_config() -> Vec<MessageTranslation> {
|
||||||
Vec::from_iter((0..LogMessage::len()).map(|i| {
|
Vec::from_iter((0..LogMessage::len()).map(|i| {
|
||||||
let msg_type = LogMessage::from_ordinal(i).unwrap();
|
let msg_type = LogMessage::from_ordinal(i).unwrap_or(LogMessage::UnknownMessage);
|
||||||
(&msg_type).into()
|
(&msg_type).into()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
|
||||||
holding buffers for the duration of a data transfer."
|
holding buffers for the duration of a data transfer."
|
||||||
)]
|
)]
|
||||||
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||||
|
|
||||||
//TODO insert version here and read it in other parts, also read this for the ota webview
|
//TODO insert version here and read it in other parts, also read this for the ota webview
|
||||||
esp_bootloader_esp_idf::esp_app_desc!();
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
@@ -39,7 +40,7 @@ use embassy_net::Stack;
|
|||||||
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_sync::once_lock::OnceLock;
|
||||||
use embassy_time::{Duration, Instant, Timer, WithTimeout};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
use esp_hal::rom::ets_delay_us;
|
use esp_hal::rom::ets_delay_us;
|
||||||
use esp_hal::system::software_reset;
|
use esp_hal::system::software_reset;
|
||||||
use esp_println::{logger, println};
|
use esp_println::{logger, println};
|
||||||
@@ -297,7 +298,9 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
|
|
||||||
if let NetworkMode::Wifi { ref ip_address, .. } = network_mode {
|
if let NetworkMode::Wifi { ref ip_address, .. } = network_mode {
|
||||||
publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await;
|
publish_firmware_info(&mut board, version, ip_address, &timezone_time.to_rfc3339()).await;
|
||||||
publish_battery_state(&mut board).await;
|
publish_battery_state(&mut board).await.unwrap_or_else(|e| {
|
||||||
|
error!("Error publishing battery state {e}");
|
||||||
|
});
|
||||||
let _ = publish_mppt_state(&mut board).await;
|
let _ = publish_mppt_state(&mut board).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +329,12 @@ 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(http_server(reboot_now.clone(), stack.take().unwrap()))?;
|
let stack_val = stack.take();
|
||||||
|
if let Some(s) = stack_val {
|
||||||
|
spawner.spawn(http_server(reboot_now.clone(), s))?;
|
||||||
|
} else {
|
||||||
|
bail!("Network stack missing, hard abort")
|
||||||
|
}
|
||||||
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await;
|
||||||
} else {
|
} else {
|
||||||
LOG_ACCESS
|
LOG_ACCESS
|
||||||
@@ -406,7 +414,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
}
|
}
|
||||||
info!("Water temp is {}", water_temp.as_ref().unwrap_or(&0.));
|
info!("Water temp is {}", water_temp.as_ref().unwrap_or(&0.));
|
||||||
|
|
||||||
publish_tank_state(&mut board, &tank_state, water_temp).await;
|
publish_tank_state(&mut board, &tank_state, water_temp)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!("Error publishing tank state {e}");
|
||||||
|
});
|
||||||
|
|
||||||
let moisture = board.board_hal.measure_moisture_hz().await?;
|
let moisture = board.board_hal.measure_moisture_hz().await?;
|
||||||
|
|
||||||
@@ -421,7 +433,11 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
PlantState::read_hardware_state(moisture, 7, &mut board).await,
|
PlantState::read_hardware_state(moisture, 7, &mut board).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate).await;
|
publish_plant_states(&mut board, &timezone_time.clone(), &plantstate)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!("Error publishing plant states {e}");
|
||||||
|
});
|
||||||
|
|
||||||
let pump_required = plantstate
|
let pump_required = plantstate
|
||||||
.iter()
|
.iter()
|
||||||
@@ -625,14 +641,18 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> {
|
|||||||
|
|
||||||
if stay_alive {
|
if stay_alive {
|
||||||
let reboot_now = Arc::new(AtomicBool::new(false));
|
let reboot_now = Arc::new(AtomicBool::new(false));
|
||||||
let _webserver = http_server(reboot_now.clone(), stack.take().unwrap());
|
if let Some(s) = stack.take() {
|
||||||
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
|
let _webserver = http_server(reboot_now.clone(), s);
|
||||||
|
wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await;
|
||||||
|
} else {
|
||||||
|
bail!("Network Stack missing, hard abort");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO wait for all mqtt publishes?
|
//TODO wait for all mqtt publishes?
|
||||||
Timer::after_millis(5000).await;
|
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
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64)
|
.deep_sleep(1000 * 1000 * 60 * deep_sleep_duration_minutes as u64)
|
||||||
.await;
|
.await;
|
||||||
@@ -801,30 +821,29 @@ async fn publish_tank_state(
|
|||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
tank_state: &TankState,
|
tank_state: &TankState,
|
||||||
water_temp: FatResult<f32>,
|
water_temp: FatResult<f32>,
|
||||||
) {
|
) -> FatResult<()> {
|
||||||
let state = serde_json::to_string(
|
let state = serde_json::to_string(
|
||||||
&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
|
&tank_state.as_mqtt_info(&board.board_hal.get_config().tank, &water_temp),
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
board
|
board
|
||||||
.board_hal
|
.board_hal
|
||||||
.get_esp()
|
.get_esp()
|
||||||
.mqtt_publish("/water", &state)
|
.mqtt_publish("/water", &state)
|
||||||
.await;
|
.await;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn publish_plant_states(
|
async fn publish_plant_states(
|
||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
timezone_time: &DateTime<Tz>,
|
timezone_time: &DateTime<Tz>,
|
||||||
plantstate: &[PlantState; 8],
|
plantstate: &[PlantState; 8],
|
||||||
) {
|
) -> FatResult<()> {
|
||||||
for (plant_id, (plant_state, plant_conf)) in plantstate
|
for (plant_id, (plant_state, plant_conf)) in plantstate
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&board.board_hal.get_config().plants.clone())
|
.zip(&board.board_hal.get_config().plants.clone())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let state =
|
let state = serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time))?;
|
||||||
serde_json::to_string(&plant_state.to_mqtt_info(plant_conf, timezone_time)).unwrap();
|
|
||||||
let plant_topic = format!("/plant{}", plant_id + 1);
|
let plant_topic = format!("/plant{}", plant_id + 1);
|
||||||
let _ = board
|
let _ = board
|
||||||
.board_hal
|
.board_hal
|
||||||
@@ -832,6 +851,7 @@ async fn publish_plant_states(
|
|||||||
.mqtt_publish(&plant_topic, &state)
|
.mqtt_publish(&plant_topic, &state)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn publish_firmware_info(
|
async fn publish_firmware_info(
|
||||||
@@ -907,13 +927,9 @@ async fn try_connect_wifi_sntp_mqtt(
|
|||||||
|
|
||||||
let ip = match stack.config_v4() {
|
let ip = match stack.config_v4() {
|
||||||
Some(config) => config.address.address().to_string(),
|
Some(config) => config.address.address().to_string(),
|
||||||
None => {
|
None => match stack.config_v6() {
|
||||||
match stack.config_v6() {
|
Some(config) => config.address.address().to_string(),
|
||||||
Some(config) => config.address.address().to_string(),
|
None => String::from("No IP"),
|
||||||
None => {
|
|
||||||
String::from("No IP")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
NetworkMode::Wifi {
|
NetworkMode::Wifi {
|
||||||
@@ -987,11 +1003,11 @@ async fn publish_mppt_state(
|
|||||||
|
|
||||||
async fn publish_battery_state(
|
async fn publish_battery_state(
|
||||||
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
) -> () {
|
) -> FatResult<()> {
|
||||||
let state = board.board_hal.get_battery_monitor().get_state().await;
|
let state = board.board_hal.get_battery_monitor().get_state().await;
|
||||||
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)?.to_owned();
|
||||||
json.to_owned()
|
json.to_owned()
|
||||||
}
|
}
|
||||||
Err(_) => "error".to_owned(),
|
Err(_) => "error".to_owned(),
|
||||||
@@ -1003,6 +1019,7 @@ async fn publish_battery_state(
|
|||||||
.mqtt_publish("/battery", &value)
|
.mqtt_publish("/battery", &value)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_infinity(
|
async fn wait_infinity(
|
||||||
@@ -1049,8 +1066,7 @@ async fn wait_infinity(
|
|||||||
exit_hold_blink = !exit_hold_blink;
|
exit_hold_blink = !exit_hold_blink;
|
||||||
|
|
||||||
let progress = core::cmp::min(elapsed, exit_hold_duration);
|
let progress = core::cmp::min(elapsed, exit_hold_duration);
|
||||||
let lit = ((progress.as_millis() * 8)
|
let lit = ((progress.as_millis() * 8) / exit_hold_duration.as_millis())
|
||||||
/ exit_hold_duration.as_millis())
|
|
||||||
.saturating_add(1)
|
.saturating_add(1)
|
||||||
.min(8) as usize;
|
.min(8) as usize;
|
||||||
|
|
||||||
@@ -1200,6 +1216,8 @@ async fn handle_serial_config(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use embassy_time::WithTimeout;
|
||||||
|
#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
|
||||||
#[esp_rtos::main]
|
#[esp_rtos::main]
|
||||||
async fn main(spawner: Spawner) -> ! {
|
async fn main(spawner: Spawner) -> ! {
|
||||||
// intialize embassy
|
// intialize embassy
|
||||||
|
|||||||
@@ -158,12 +158,11 @@ pub async fn determine_tank_state(
|
|||||||
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>,
|
||||||
) -> TankState {
|
) -> TankState {
|
||||||
if board.board_hal.get_config().tank.tank_sensor_enabled {
|
if board.board_hal.get_config().tank.tank_sensor_enabled {
|
||||||
match board
|
match board.board_hal.get_tank_sensor() {
|
||||||
.board_hal
|
Ok(sensor) => match sensor.tank_sensor_voltage().await {
|
||||||
.get_tank_sensor()
|
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
|
||||||
.map(|f| f.tank_sensor_voltage())
|
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||||
{
|
},
|
||||||
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv.await.unwrap()),
|
|
||||||
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ where
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
conn.write_all(serde_json::to_string(&backup)?.as_bytes()).await?;
|
conn.write_all(serde_json::to_string(&backup)?.as_bytes())
|
||||||
|
.await?;
|
||||||
Ok(Some(200))
|
Ok(Some(200))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ pub(crate) async fn backup_config<T, const N: usize>(
|
|||||||
where
|
where
|
||||||
T: Read + Write,
|
T: Read + Write,
|
||||||
{
|
{
|
||||||
let input = read_up_to_bytes_from_request(conn, Option::None).await?;
|
let input = read_up_to_bytes_from_request(conn, Some(4096)).await?;
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
let config_to_backup = serde_json::from_slice(&input)?;
|
let config_to_backup = serde_json::from_slice(&input)?;
|
||||||
board.board_hal.backup_config(&config_to_backup).await?;
|
board.board_hal.backup_config(&config_to_backup).await?;
|
||||||
@@ -70,10 +71,9 @@ where
|
|||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
let info = board.board_hal.backup_info().await;
|
let info = board.board_hal.backup_info().await;
|
||||||
|
|
||||||
|
|
||||||
let json = match info {
|
let json = match info {
|
||||||
Ok(h) => {
|
Ok(h) => {
|
||||||
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap();
|
let timestamp = DateTime::from_timestamp_millis(h.timestamp).unwrap_or_default();
|
||||||
let wbh = WebBackupHeader {
|
let wbh = WebBackupHeader {
|
||||||
timestamp: timestamp.to_rfc3339(),
|
timestamp: timestamp.to_rfc3339(),
|
||||||
size: h.size,
|
size: h.size,
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
|
#[allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
|
||||||
pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
pub async fn http_server(reboot_now: Arc<AtomicBool>, stack: Stack<'static>) {
|
||||||
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
|
let buffer: TcpBuffers<2, 1024, 1024> = TcpBuffers::new();
|
||||||
let tcp = Tcp::new(stack, &buffer);
|
let tcp = Tcp::new(stack, &buffer);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ where
|
|||||||
{
|
{
|
||||||
let actual_data = read_up_to_bytes_from_request(request, None).await?;
|
let actual_data = read_up_to_bytes_from_request(request, None).await?;
|
||||||
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
let time: SetTime = serde_json::from_slice(&actual_data)?;
|
||||||
let parsed = DateTime::parse_from_rfc3339(time.time).unwrap();
|
let parsed = DateTime::parse_from_rfc3339(time.time)?;
|
||||||
esp_set_time(parsed).await?;
|
esp_set_time(parsed).await?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user