fix rtc storage
This commit is contained in:
		
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "board": {
 | 
					  "board": {
 | 
				
			||||||
    "active_layer": 31,
 | 
					    "active_layer": 2,
 | 
				
			||||||
    "active_layer_preset": "",
 | 
					    "active_layer_preset": "",
 | 
				
			||||||
    "auto_track_width": false,
 | 
					    "auto_track_width": false,
 | 
				
			||||||
    "hidden_netclasses": [],
 | 
					    "hidden_netclasses": [],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,29 +55,7 @@
 | 
				
			|||||||
          "width": 0.0
 | 
					          "width": 0.0
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "drc_exclusions": [
 | 
					      "drc_exclusions": [],
 | 
				
			||||||
        "courtyards_overlap|189080001|128865001|67ac55df-4a92-42f8-90ae-0f317d1fcee5|c62213e3-5f38-460c-825e-5567e84c0a13",
 | 
					 | 
				
			||||||
        "courtyards_overlap|199080001|128865001|67ac55df-4a92-42f8-90ae-0f317d1fcee5|cfde0667-9234-45fc-8eda-14a18003cf8d",
 | 
					 | 
				
			||||||
        "courtyards_overlap|209080001|128865001|5ed14a34-184f-41cd-94a0-c9f4df9ff6fd|cfde0667-9234-45fc-8eda-14a18003cf8d",
 | 
					 | 
				
			||||||
        "courtyards_overlap|219080001|128865001|4dcef43e-4bc3-40ed-bbe5-22243d3d487f|5ed14a34-184f-41cd-94a0-c9f4df9ff6fd",
 | 
					 | 
				
			||||||
        "courtyards_overlap|229080001|128865001|1c5c2a00-3616-406b-aedf-bf3a81986791|4dcef43e-4bc3-40ed-bbe5-22243d3d487f",
 | 
					 | 
				
			||||||
        "courtyards_overlap|239080001|128865001|1c5c2a00-3616-406b-aedf-bf3a81986791|1ecf7cd6-6488-4305-b20b-72dbab800538",
 | 
					 | 
				
			||||||
        "courtyards_overlap|249080001|128865001|1ecf7cd6-6488-4305-b20b-72dbab800538|6ccc9c73-6edf-479a-97a6-a01eda77987f",
 | 
					 | 
				
			||||||
        "courtyards_overlap|268679999|80770001|22510631-57e1-46b7-916a-63be4d1f1249|ab413b79-2f68-48c4-8ed3-cedb5f870570",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|170180000|117670000|87e2c53a-11a1-4d9d-89a9-2a5ca6e6f269|058105c1-5307-4874-9401-d1e0612824b0",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|170180000|49705000|87e2c53a-11a1-4d9d-89a9-2a5ca6e6f269|2af3bc4a-e24d-48a4-898d-739774f84bd6",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|170180000|67945000|87e2c53a-11a1-4d9d-89a9-2a5ca6e6f269|d1ed732d-93f0-4bca-ae40-dde82301c749",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|170242295|123498571|87e2c53a-11a1-4d9d-89a9-2a5ca6e6f269|98242ad4-5a51-4508-b392-eeabff738172",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|170242295|127998571|87e2c53a-11a1-4d9d-89a9-2a5ca6e6f269|9dfe510c-725d-437b-9270-62f0fb83346b",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|110840000|6d070ea4-5732-4974-b15a-6adc25e87507|ab851b96-316a-460d-b1e8-9d3fef419e7b",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|113440000|6d070ea4-5732-4974-b15a-6adc25e87507|f1ae4a26-c73e-4b69-af96-801c2a8e61d3",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|115980000|6d070ea4-5732-4974-b15a-6adc25e87507|685b88f7-a226-4ce5-b9a2-70f5eea54d45",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|118520000|6d070ea4-5732-4974-b15a-6adc25e87507|dfd6db18-258f-4809-8096-700f3184e482",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|121060000|6d070ea4-5732-4974-b15a-6adc25e87507|df12db4f-f2d6-4bbb-962f-c504c70a12d7",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|123600000|6d070ea4-5732-4974-b15a-6adc25e87507|0f1bb1cf-595c-42a5-b79f-53ba7db0e565",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|126140000|6d070ea4-5732-4974-b15a-6adc25e87507|85c77ea0-2469-4305-9635-0c4ccb268a57",
 | 
					 | 
				
			||||||
        "silk_edge_clearance|269240000|128740000|6d070ea4-5732-4974-b15a-6adc25e87507|90543f7d-a988-4bce-a183-20300704c3c1"
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      "meta": {
 | 
					      "meta": {
 | 
				
			||||||
        "filename": "board_design_settings.json",
 | 
					        "filename": "board_design_settings.json",
 | 
				
			||||||
        "version": 2
 | 
					        "version": 2
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -55,32 +55,28 @@ embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
log = { version = "0.4", default-features = false }
 | 
					log = { version = "0.4", default-features = false }
 | 
				
			||||||
esp-idf-svc = { version = "0.47.3", default-features = false }
 | 
					esp-idf-svc = { version = "0.48.0", default-features = false }
 | 
				
			||||||
serde = { version = "1.0.192",  features = ["derive"] }
 | 
					serde = { version = "1.0.192",  features = ["derive"] }
 | 
				
			||||||
average = { version = "0.14.1" , features = ["std"] }
 | 
					average = { version = "0.14.1" , features = ["std"] }
 | 
				
			||||||
#esp32 = "0.28.0"
 | 
					#esp32 = "0.28.0"
 | 
				
			||||||
bit_field = "0.10.2"
 | 
					bit_field = "0.10.2"
 | 
				
			||||||
ds18b20 = "0.1.1"
 | 
					ds18b20 = "0.1.1"
 | 
				
			||||||
embedded-svc = { version = "0.26.4", features = ["experimental"] }
 | 
					embedded-svc = { version = "0.27.0", features = ["experimental"] }
 | 
				
			||||||
esp-idf-hal = "0.42.5"
 | 
					esp-idf-hal = "0.43.0"
 | 
				
			||||||
esp-idf-sys = { version = "0.33.7", features = ["binstart", "native"] }
 | 
					esp-idf-sys = { version = "0.34.0", features = ["binstart", "native"] }
 | 
				
			||||||
esp-ota = "0.2.0"
 | 
					 | 
				
			||||||
esp_idf_build = "0.1.3"
 | 
					esp_idf_build = "0.1.3"
 | 
				
			||||||
chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone"] }
 | 
					chrono = { version = "0.4.23", default-features = false , features = ["iana-time-zone"] }
 | 
				
			||||||
chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]}
 | 
					chrono-tz = {version="0.8.0", default-features = false , features = [ "filter-by-regex" ]}
 | 
				
			||||||
embedded-hal = "0.2.7"
 | 
					embedded-hal = "1.0.0"
 | 
				
			||||||
#shift-register-driver = "0.1.1"
 | 
					 | 
				
			||||||
one-wire-bus = "0.1.1"
 | 
					one-wire-bus = "0.1.1"
 | 
				
			||||||
anyhow = { version = "1.0.75", features = ["std", "backtrace"] }
 | 
					anyhow = { version = "1.0.75", features = ["std", "backtrace"] }
 | 
				
			||||||
schemars = "0.8.16"
 | 
					schemars = "0.8.16"
 | 
				
			||||||
heapless = { version = "0.7", features = ["serde"] }
 | 
					heapless = { version = "0.8", features = ["serde"] }
 | 
				
			||||||
serde_json = "1.0.108"
 | 
					serde_json = "1.0.108"
 | 
				
			||||||
strum = { version = "0.25.0", features = ["derive"] }
 | 
					strum = { version = "0.26.1", features = ["derive"] }
 | 
				
			||||||
once_cell = "1.19.0"
 | 
					once_cell = "1.19.0"
 | 
				
			||||||
measurements = "0.11.0"
 | 
					measurements = "0.11.0"
 | 
				
			||||||
medians = "3.0.6"
 | 
					bq34z100 = "0.1.0"
 | 
				
			||||||
median-accumulator = "0.2.0"
 | 
					 | 
				
			||||||
#?bq34z100 required
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[build-dependencies]
 | 
					[build-dependencies]
 | 
				
			||||||
embuild = "0.31.3"
 | 
					embuild = "0.31.3"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1168
									
								
								rust/src/bq34z100.rs
									
									
									
									
									
								
							
							
						
						
									
										1168
									
								
								rust/src/bq34z100.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
use std::fmt;
 | 
					use std::{fmt, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,8 +14,8 @@ pub struct Config {
 | 
				
			|||||||
    pub tank_sensor_enabled: bool,
 | 
					    pub tank_sensor_enabled: bool,
 | 
				
			||||||
    pub tank_useable_ml: u32,
 | 
					    pub tank_useable_ml: u32,
 | 
				
			||||||
    pub tank_warn_percent: u8,
 | 
					    pub tank_warn_percent: u8,
 | 
				
			||||||
    pub tank_empty_percent: u16,
 | 
					    pub tank_empty_percent: u8,
 | 
				
			||||||
    pub tank_full_percent: u16,
 | 
					    pub tank_full_percent: u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub night_lamp_hour_start: u8,
 | 
					    pub night_lamp_hour_start: u8,
 | 
				
			||||||
    pub night_lamp_hour_end: u8,
 | 
					    pub night_lamp_hour_end: u8,
 | 
				
			||||||
@@ -27,8 +27,8 @@ pub struct Config {
 | 
				
			|||||||
impl Default for Config {
 | 
					impl Default for Config {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            base_topic: "plant/one".into(),
 | 
					            base_topic: heapless::String::from_str("plant/one").unwrap(),
 | 
				
			||||||
            mqtt_url: "mqtt://192.168.1.1:1883".into(),
 | 
					            mqtt_url: heapless::String::from_str("mqtt://192.168.1.1:1883").unwrap(),
 | 
				
			||||||
            tank_allow_pumping_if_sensor_error: true,
 | 
					            tank_allow_pumping_if_sensor_error: true,
 | 
				
			||||||
            tank_sensor_enabled: true,
 | 
					            tank_sensor_enabled: true,
 | 
				
			||||||
            tank_warn_percent: 50,
 | 
					            tank_warn_percent: 50,
 | 
				
			||||||
@@ -38,8 +38,8 @@ impl Default for Config {
 | 
				
			|||||||
            plants: [Plant::default(); PLANT_COUNT],
 | 
					            plants: [Plant::default(); PLANT_COUNT],
 | 
				
			||||||
            max_consecutive_pump_count: 15,
 | 
					            max_consecutive_pump_count: 15,
 | 
				
			||||||
            tank_useable_ml: 5000,
 | 
					            tank_useable_ml: 5000,
 | 
				
			||||||
            tank_empty_percent: 0_u16,
 | 
					            tank_empty_percent: 0_u8,
 | 
				
			||||||
            tank_full_percent: 100_u16,
 | 
					            tank_full_percent: 100_u8,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										266
									
								
								rust/src/espota.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								rust/src/espota.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,266 @@
 | 
				
			|||||||
 | 
					use core::fmt;
 | 
				
			||||||
 | 
					use core::mem;
 | 
				
			||||||
 | 
					use core::ptr;
 | 
				
			||||||
 | 
					use esp_idf_sys::{
 | 
				
			||||||
 | 
					    esp_ota_abort, esp_ota_begin, esp_ota_end, esp_ota_get_next_update_partition, esp_ota_handle_t,
 | 
				
			||||||
 | 
					    esp_ota_mark_app_invalid_rollback_and_reboot, esp_ota_mark_app_valid_cancel_rollback,
 | 
				
			||||||
 | 
					    esp_ota_set_boot_partition, esp_ota_write, esp_partition_t, esp_restart, ESP_ERR_FLASH_OP_FAIL,
 | 
				
			||||||
 | 
					    ESP_ERR_FLASH_OP_TIMEOUT, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_SIZE, ESP_ERR_INVALID_STATE,
 | 
				
			||||||
 | 
					    ESP_ERR_NOT_FOUND, ESP_ERR_NO_MEM, ESP_ERR_OTA_PARTITION_CONFLICT, ESP_ERR_OTA_ROLLBACK_FAILED,
 | 
				
			||||||
 | 
					    ESP_ERR_OTA_ROLLBACK_INVALID_STATE, ESP_ERR_OTA_SELECT_INFO_INVALID,
 | 
				
			||||||
 | 
					    ESP_ERR_OTA_VALIDATE_FAILED, ESP_FAIL, ESP_OK, OTA_SIZE_UNKNOWN,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type Result<T> = core::result::Result<T, Error>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// An error that can happen during ESP OTA operations.
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct Error {
 | 
				
			||||||
 | 
					    kind: ErrorKind,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Error {
 | 
				
			||||||
 | 
					    pub(crate) fn from_kind(kind: ErrorKind) -> Self {
 | 
				
			||||||
 | 
					        Self { kind }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns the kind of error as an enum, that can be matched on.
 | 
				
			||||||
 | 
					    pub fn kind(&self) -> ErrorKind {
 | 
				
			||||||
 | 
					        self.kind
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for Error {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        self.kind.fmt(f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::error::Error for Error {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 | 
				
			||||||
 | 
					#[non_exhaustive]
 | 
				
			||||||
 | 
					pub enum ErrorKind {
 | 
				
			||||||
 | 
					    /// No suitable partition for writing OTA update to found.
 | 
				
			||||||
 | 
					    NoOtaPartition,
 | 
				
			||||||
 | 
					    /// Cannot allocate memory for OTA operation.
 | 
				
			||||||
 | 
					    AllocFailed,
 | 
				
			||||||
 | 
					    /// Rollback enabled, but the currently running application is still pending. The currently
 | 
				
			||||||
 | 
					    /// running application must confirm itself before downloading and flashing a new app.
 | 
				
			||||||
 | 
					    InvalidRollbackState,
 | 
				
			||||||
 | 
					    /// First byte of image contains invalid app image magic byte.
 | 
				
			||||||
 | 
					    InvalidMagicByte,
 | 
				
			||||||
 | 
					    /// Flash write operation timed out.
 | 
				
			||||||
 | 
					    FlashTimeout,
 | 
				
			||||||
 | 
					    /// Flash write operation failed.
 | 
				
			||||||
 | 
					    FlashFailed,
 | 
				
			||||||
 | 
					    /// OTA data partition has invalid contents.
 | 
				
			||||||
 | 
					    InvalidOtaPartitionData,
 | 
				
			||||||
 | 
					    /// The [`OtaUpdate`] handle was finalized before any app image was written to it.
 | 
				
			||||||
 | 
					    NothingWritten,
 | 
				
			||||||
 | 
					    /// OTA image is invalid (either not a valid app image, or - if secure boot is enabled - signature failed to verify.)
 | 
				
			||||||
 | 
					    InvalidImage,
 | 
				
			||||||
 | 
					    /// If flash encryption is enabled, this result indicates an internal error writing the final encrypted bytes to flash.
 | 
				
			||||||
 | 
					    WritingEncryptedFailed,
 | 
				
			||||||
 | 
					    /// The rollback failed.
 | 
				
			||||||
 | 
					    RollbackFailed,
 | 
				
			||||||
 | 
					    /// The rollback is not possible due to flash does not have any apps.
 | 
				
			||||||
 | 
					    RollbackFailedNoApps,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl fmt::Display for ErrorKind {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
				
			||||||
 | 
					        use ErrorKind::*;
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            NoOtaPartition => "No suitable partition for writing OTA update to found",
 | 
				
			||||||
 | 
					            AllocFailed => "Cannot allocate memory for OTA operation",
 | 
				
			||||||
 | 
					            InvalidRollbackState => {
 | 
				
			||||||
 | 
					                "Rollback enabled, but the currently running application is still pending"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            InvalidMagicByte => "First byte of image contains invalid app image magic byte",
 | 
				
			||||||
 | 
					            FlashTimeout => "Flash write operation timed out",
 | 
				
			||||||
 | 
					            FlashFailed => "Flash write operation failed",
 | 
				
			||||||
 | 
					            InvalidOtaPartitionData => "OTA data partition has invalid contents",
 | 
				
			||||||
 | 
					            NothingWritten => "OtaUpdate was never written to",
 | 
				
			||||||
 | 
					            InvalidImage => "OTA image is invalid",
 | 
				
			||||||
 | 
					            WritingEncryptedFailed => "Internal error writing the final encrypted bytes to flash",
 | 
				
			||||||
 | 
					            RollbackFailed => "The rollback failed",
 | 
				
			||||||
 | 
					            RollbackFailedNoApps => {
 | 
				
			||||||
 | 
					                "The rollback is not possible due to flash does not have any apps"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .fmt(f)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Represents an ongoing OTA update.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Dropping this object before calling [`finalize`](OtaUpdate::finalize) will abort the update.
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct OtaUpdate {
 | 
				
			||||||
 | 
					    partition: *const esp_partition_t,
 | 
				
			||||||
 | 
					    ota_handle: esp_ota_handle_t,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl OtaUpdate {
 | 
				
			||||||
 | 
					    /// Starts an OTA update to the next OTA compatible partition.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Finds next partition round-robin, starting from the current running partition.
 | 
				
			||||||
 | 
					    /// The entire partition is erased.
 | 
				
			||||||
 | 
					    pub fn begin() -> Result<Self> {
 | 
				
			||||||
 | 
					        let partition = unsafe { esp_ota_get_next_update_partition(ptr::null()) };
 | 
				
			||||||
 | 
					        if partition.is_null() {
 | 
				
			||||||
 | 
					            return Err(Error::from_kind(ErrorKind::NoOtaPartition));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut ota_handle = 0;
 | 
				
			||||||
 | 
					        match unsafe { esp_ota_begin(partition, OTA_SIZE_UNKNOWN as usize, &mut ota_handle) } {
 | 
				
			||||||
 | 
					            ESP_OK => Ok(()),
 | 
				
			||||||
 | 
					            ESP_ERR_INVALID_ARG => panic!("Invalid partition or out_handle"),
 | 
				
			||||||
 | 
					            ESP_ERR_NO_MEM => Err(Error::from_kind(ErrorKind::AllocFailed)),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_PARTITION_CONFLICT => Err(Error::from_kind(ErrorKind::NoOtaPartition)),
 | 
				
			||||||
 | 
					            ESP_ERR_NOT_FOUND => panic!("Partition argument not found in partition table"),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_SELECT_INFO_INVALID => {
 | 
				
			||||||
 | 
					                Err(Error::from_kind(ErrorKind::InvalidOtaPartitionData))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ESP_ERR_INVALID_SIZE => panic!("Partition doesn’t fit in configured flash size"),
 | 
				
			||||||
 | 
					            ESP_ERR_FLASH_OP_TIMEOUT => Err(Error::from_kind(ErrorKind::FlashTimeout)),
 | 
				
			||||||
 | 
					            ESP_ERR_FLASH_OP_FAIL => Err(Error::from_kind(ErrorKind::FlashFailed)),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_ROLLBACK_INVALID_STATE => {
 | 
				
			||||||
 | 
					                Err(Error::from_kind(ErrorKind::InvalidRollbackState))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            code => panic!("Unexpected esp_ota_begin return code: {}", code),
 | 
				
			||||||
 | 
					        }?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            partition,
 | 
				
			||||||
 | 
					            ota_handle,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Write app image data to partition.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// This method can be called multiple times as data is received during the OTA operation.
 | 
				
			||||||
 | 
					    /// Data is written sequentially to the partition.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// The format of the app image can be read about in the main README and crate documentation.
 | 
				
			||||||
 | 
					    pub fn write(&mut self, app_image_chunk: &[u8]) -> Result<()> {
 | 
				
			||||||
 | 
					        let chunk_ptr = app_image_chunk.as_ptr() as *const _;
 | 
				
			||||||
 | 
					        let chunk_len = app_image_chunk.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match unsafe { esp_ota_write(self.ota_handle, chunk_ptr, chunk_len) } {
 | 
				
			||||||
 | 
					            ESP_OK => Ok(()),
 | 
				
			||||||
 | 
					            ESP_ERR_INVALID_ARG => panic!("Invalid OTA handle"),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_VALIDATE_FAILED => Err(Error::from_kind(ErrorKind::InvalidMagicByte)),
 | 
				
			||||||
 | 
					            ESP_ERR_FLASH_OP_TIMEOUT => Err(Error::from_kind(ErrorKind::FlashTimeout)),
 | 
				
			||||||
 | 
					            ESP_ERR_FLASH_OP_FAIL => Err(Error::from_kind(ErrorKind::FlashFailed)),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_SELECT_INFO_INVALID => {
 | 
				
			||||||
 | 
					                Err(Error::from_kind(ErrorKind::InvalidOtaPartitionData))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            code => panic!("Unexpected esp_ota_write return code: {code}"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Finish OTA update and validate newly written app image.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Unless you also call [`set_as_boot_partition`](CompletedOtaUpdate::set_as_boot_partition) the new app will not
 | 
				
			||||||
 | 
					    /// start.
 | 
				
			||||||
 | 
					    pub fn finalize(self) -> Result<CompletedOtaUpdate> {
 | 
				
			||||||
 | 
					        match unsafe { esp_ota_end(self.ota_handle) } {
 | 
				
			||||||
 | 
					            ESP_OK => Ok(()),
 | 
				
			||||||
 | 
					            ESP_ERR_NOT_FOUND => panic!("Invalid OTA handle"),
 | 
				
			||||||
 | 
					            ESP_ERR_INVALID_ARG => Err(Error::from_kind(ErrorKind::NothingWritten)),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_VALIDATE_FAILED => Err(Error::from_kind(ErrorKind::InvalidImage)),
 | 
				
			||||||
 | 
					            ESP_ERR_INVALID_STATE => Err(Error::from_kind(ErrorKind::WritingEncryptedFailed)),
 | 
				
			||||||
 | 
					            code => panic!("Unexpected esp_ota_end return code: {code}"),
 | 
				
			||||||
 | 
					        }?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let partition = self.partition;
 | 
				
			||||||
 | 
					        mem::forget(self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(CompletedOtaUpdate { partition })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a raw pointer to the partition that the new app is/will be written to.
 | 
				
			||||||
 | 
					    pub fn raw_partition(&self) -> *const esp_partition_t {
 | 
				
			||||||
 | 
					        self.partition
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Drop for OtaUpdate {
 | 
				
			||||||
 | 
					    fn drop(&mut self) {
 | 
				
			||||||
 | 
					        #[cfg(feature = "log")]
 | 
				
			||||||
 | 
					        log::debug!("Aborting OTA update");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let abort_result_code = unsafe { esp_ota_abort(self.ota_handle) };
 | 
				
			||||||
 | 
					        if abort_result_code != ESP_OK {
 | 
				
			||||||
 | 
					            #[cfg(feature = "log")]
 | 
				
			||||||
 | 
					            log::error!(
 | 
				
			||||||
 | 
					                "Aborting the OTA update returned an unexpected code: {}",
 | 
				
			||||||
 | 
					                abort_result_code
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct CompletedOtaUpdate {
 | 
				
			||||||
 | 
					    partition: *const esp_partition_t,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl CompletedOtaUpdate {
 | 
				
			||||||
 | 
					    /// Sets the boot partition to the newly flashed OTA partition.
 | 
				
			||||||
 | 
					    pub fn set_as_boot_partition(&mut self) -> Result<()> {
 | 
				
			||||||
 | 
					        match unsafe { esp_ota_set_boot_partition(self.partition) } {
 | 
				
			||||||
 | 
					            ESP_OK => Ok(()),
 | 
				
			||||||
 | 
					            ESP_ERR_INVALID_ARG => panic!("Invalid partition sent to esp_ota_set_boot_partition"),
 | 
				
			||||||
 | 
					            ESP_ERR_OTA_VALIDATE_FAILED => Err(Error::from_kind(ErrorKind::InvalidImage)),
 | 
				
			||||||
 | 
					            ESP_ERR_NOT_FOUND => panic!("OTA data partition not found"),
 | 
				
			||||||
 | 
					            ESP_ERR_FLASH_OP_TIMEOUT => Err(Error::from_kind(ErrorKind::FlashTimeout)),
 | 
				
			||||||
 | 
					            ESP_ERR_FLASH_OP_FAIL => Err(Error::from_kind(ErrorKind::FlashFailed)),
 | 
				
			||||||
 | 
					            code => panic!("Unexpected esp_ota_set_boot_partition code: {}", code),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Restarts the CPU. If [`set_as_boot_partition`](CompletedOtaUpdate::set_as_boot_partition) was
 | 
				
			||||||
 | 
					    /// called and completed successfully, the CPU will boot into the newly written app.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// After successful restart, CPU reset reason will be SW_CPU_RESET. Peripherals
 | 
				
			||||||
 | 
					    /// (except for WiFi, BT, UART0, SPI1, and legacy timers) are not reset.
 | 
				
			||||||
 | 
					    pub fn restart(self) -> ! {
 | 
				
			||||||
 | 
					        unsafe { esp_restart() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a raw pointer to the partition that the new app was written to.
 | 
				
			||||||
 | 
					    pub fn raw_partition(&self) -> *const esp_partition_t {
 | 
				
			||||||
 | 
					        self.partition
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Call this function to indicate that the running app is working well.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Should be called (at least) the first time a new app starts up after
 | 
				
			||||||
 | 
					/// being flashed.
 | 
				
			||||||
 | 
					pub fn mark_app_valid() {
 | 
				
			||||||
 | 
					    match unsafe { esp_ota_mark_app_valid_cancel_rollback() } {
 | 
				
			||||||
 | 
					        ESP_OK => (),
 | 
				
			||||||
 | 
					        code => panic!(
 | 
				
			||||||
 | 
					            "Unexpected esp_ota_mark_app_valid_cancel_rollback code: {}",
 | 
				
			||||||
 | 
					            code
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Call this function to roll back to the previously workable app with reboot.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// If rolling back failed, it returns an error, otherwise this function never returns,
 | 
				
			||||||
 | 
					/// as the CPU is rebooting.
 | 
				
			||||||
 | 
					pub fn rollback_and_reboot() -> Result<core::convert::Infallible> {
 | 
				
			||||||
 | 
					    match unsafe { esp_ota_mark_app_invalid_rollback_and_reboot() } {
 | 
				
			||||||
 | 
					        ESP_FAIL => Err(Error::from_kind(ErrorKind::RollbackFailed)),
 | 
				
			||||||
 | 
					        ESP_ERR_OTA_ROLLBACK_FAILED => Err(Error::from_kind(ErrorKind::RollbackFailedNoApps)),
 | 
				
			||||||
 | 
					        code => panic!(
 | 
				
			||||||
 | 
					            "Unexpected esp_ota_mark_app_invalid_rollback_and_reboot code: {}",
 | 
				
			||||||
 | 
					            code
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										874
									
								
								rust/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										874
									
								
								rust/src/main.rs
									
									
									
									
									
								
							@@ -3,14 +3,14 @@ use std::{
 | 
				
			|||||||
    sync::{atomic::AtomicBool, Arc, Mutex},
 | 
					    sync::{atomic::AtomicBool, Arc, Mutex},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{bail, Result};
 | 
					 | 
				
			||||||
use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike};
 | 
					use chrono::{DateTime, Datelike, Duration, NaiveDateTime, Timelike};
 | 
				
			||||||
use chrono_tz::{Europe::Berlin, Tz};
 | 
					use chrono_tz::{Europe::Berlin, Tz};
 | 
				
			||||||
 | 
					use config::Plant;
 | 
				
			||||||
use esp_idf_hal::delay::Delay;
 | 
					use esp_idf_hal::delay::Delay;
 | 
				
			||||||
use esp_idf_sys::{
 | 
					use esp_idf_sys::{
 | 
				
			||||||
    esp_deep_sleep, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay, CONFIG_FREERTOS_HZ
 | 
					    esp_deep_sleep, esp_restart, gpio_deep_sleep_hold_dis, gpio_deep_sleep_hold_en, vTaskDelay,
 | 
				
			||||||
 | 
					    CONFIG_FREERTOS_HZ,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use esp_ota::rollback_and_reboot;
 | 
					 | 
				
			||||||
use log::error;
 | 
					use log::error;
 | 
				
			||||||
use once_cell::sync::Lazy;
 | 
					use once_cell::sync::Lazy;
 | 
				
			||||||
use plant_hal::{CreatePlantHal, PlantCtrlBoard, PlantCtrlBoardInteraction, PlantHal, PLANT_COUNT};
 | 
					use plant_hal::{CreatePlantHal, PlantCtrlBoard, PlantCtrlBoardInteraction, PlantHal, PLANT_COUNT};
 | 
				
			||||||
@@ -18,10 +18,11 @@ use serde::{Deserialize, Serialize};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    config::{Config, WifiConfig},
 | 
					    config::{Config, WifiConfig},
 | 
				
			||||||
 | 
					    espota::rollback_and_reboot,
 | 
				
			||||||
    webserver::webserver::{httpd, httpd_initial},
 | 
					    webserver::webserver::{httpd, httpd_initial},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
pub mod bq34z100;
 | 
					 | 
				
			||||||
mod config;
 | 
					mod config;
 | 
				
			||||||
 | 
					pub mod espota;
 | 
				
			||||||
pub mod plant_hal;
 | 
					pub mod plant_hal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MOIST_SENSOR_MAX_FREQUENCY: u32 = 5200; // 60kHz (500Hz margin)
 | 
					const MOIST_SENSOR_MAX_FREQUENCY: u32 = 5200; // 60kHz (500Hz margin)
 | 
				
			||||||
@@ -33,6 +34,9 @@ const FROM: (f32, f32) = (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
const TO: (f32, f32) = (0_f32, 100_f32);
 | 
					const TO: (f32, f32) = (0_f32, 100_f32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub static BOARD_ACCESS: Lazy<Mutex<PlantCtrlBoard>> = Lazy::new(|| PlantHal::create().unwrap());
 | 
				
			||||||
 | 
					pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod webserver {
 | 
					mod webserver {
 | 
				
			||||||
    pub mod webserver;
 | 
					    pub mod webserver;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -42,6 +46,7 @@ enum OnlineMode {
 | 
				
			|||||||
    Offline,
 | 
					    Offline,
 | 
				
			||||||
    Wifi,
 | 
					    Wifi,
 | 
				
			||||||
    SnTp,
 | 
					    SnTp,
 | 
				
			||||||
 | 
					    Online,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
 | 
					#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
 | 
				
			||||||
@@ -74,235 +79,28 @@ struct PlantState {
 | 
				
			|||||||
    not_effective: bool,
 | 
					    not_effective: bool,
 | 
				
			||||||
    cooldown: bool,
 | 
					    cooldown: bool,
 | 
				
			||||||
    no_water: bool,
 | 
					    no_water: bool,
 | 
				
			||||||
    sensor_error_a: bool,
 | 
					    sensor_error_a: Option<SensorError>,
 | 
				
			||||||
    sensor_error_b: bool,
 | 
					    sensor_error_b: Option<SensorError>,
 | 
				
			||||||
    sensor_error_p: bool,
 | 
					    sensor_error_p: Option<SensorError>,
 | 
				
			||||||
    out_of_work_hour: bool,
 | 
					    out_of_work_hour: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
 | 
					#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
 | 
				
			||||||
    let delay = match wait_type {
 | 
					enum SensorError {
 | 
				
			||||||
        WaitType::InitialConfig => 250_u32,
 | 
					    Unknown,
 | 
				
			||||||
        WaitType::FlashError => 100_u32,
 | 
					    ShortCircuit { hz: f32, max: f32 },
 | 
				
			||||||
        WaitType::NormalConfig => 500_u32,
 | 
					    OpenCircuit { hz: f32, min: f32 },
 | 
				
			||||||
        WaitType::StayAlive => 1000_u32,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    let led_count = match wait_type {
 | 
					 | 
				
			||||||
        WaitType::InitialConfig => 8,
 | 
					 | 
				
			||||||
        WaitType::FlashError => 8,
 | 
					 | 
				
			||||||
        WaitType::NormalConfig => 4,
 | 
					 | 
				
			||||||
        WaitType::StayAlive => 2,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    loop {
 | 
					 | 
				
			||||||
        unsafe {
 | 
					 | 
				
			||||||
            //do not trigger watchdog
 | 
					 | 
				
			||||||
            for i in 0..8 {
 | 
					 | 
				
			||||||
                BOARD_ACCESS.lock().unwrap().fault(i, i < led_count);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            BOARD_ACCESS.lock().unwrap().general_fault(true);
 | 
					 | 
				
			||||||
            vTaskDelay(delay);
 | 
					 | 
				
			||||||
            BOARD_ACCESS.lock().unwrap().general_fault(false);
 | 
					 | 
				
			||||||
            for i in 0..8 {
 | 
					 | 
				
			||||||
                BOARD_ACCESS.lock().unwrap().fault(i, false);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            vTaskDelay(delay);
 | 
					 | 
				
			||||||
            if wait_type == WaitType::StayAlive
 | 
					 | 
				
			||||||
                && !STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
 | 
					 | 
				
			||||||
                println!("Rebooting");
 | 
					 | 
				
			||||||
                esp_restart();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub static BOARD_ACCESS: Lazy<Mutex<PlantCtrlBoard>> = Lazy::new(|| PlantHal::create().unwrap());
 | 
					#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Default)]
 | 
				
			||||||
pub static STAY_ALIVE: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(false));
 | 
					struct TankState {
 | 
				
			||||||
 | 
					 | 
				
			||||||
fn map_range(from_range: (f32, f32), s: f32) -> Result<f32> {
 | 
					 | 
				
			||||||
    if s < from_range.0 {
 | 
					 | 
				
			||||||
        bail!(
 | 
					 | 
				
			||||||
            "Value out of range, min {} but current is {}",
 | 
					 | 
				
			||||||
            from_range.0,
 | 
					 | 
				
			||||||
            s
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if s > from_range.1 {
 | 
					 | 
				
			||||||
        bail!(
 | 
					 | 
				
			||||||
            "Value out of range, max {} but current is {}",
 | 
					 | 
				
			||||||
            from_range.1,
 | 
					 | 
				
			||||||
            s
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn map_range_moisture(s: f32) -> Result<u8> {
 | 
					 | 
				
			||||||
    if s < FROM.0 {
 | 
					 | 
				
			||||||
        bail!("Value out of range, min {} but current is {}", FROM.0, s);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if s > FROM.1 {
 | 
					 | 
				
			||||||
        bail!("Value out of range, max {} but current is {}", FROM.1, s);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    let tmp = TO.0 + (s - FROM.0) * (TO.1 - TO.0) / (FROM.1 - FROM.0);
 | 
					 | 
				
			||||||
    return Ok(tmp as u8);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn in_time_range(cur: DateTime<Tz>, start: u8, end: u8) -> bool {
 | 
					 | 
				
			||||||
    let curhour = cur.hour() as u8;
 | 
					 | 
				
			||||||
    //eg 10-14
 | 
					 | 
				
			||||||
    if start < end {
 | 
					 | 
				
			||||||
        return curhour > start && curhour < end;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        //eg 20-05
 | 
					 | 
				
			||||||
        return curhour > start || curhour < end;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn determine_next_plant(
 | 
					 | 
				
			||||||
    plantstate: &mut [PlantState; PLANT_COUNT],
 | 
					 | 
				
			||||||
    cur: DateTime<Tz>,
 | 
					 | 
				
			||||||
    enough_water: bool,
 | 
					    enough_water: bool,
 | 
				
			||||||
    water_frozen: bool,
 | 
					    left_ml: u32,
 | 
				
			||||||
    tank_sensor_error: bool,
 | 
					    sensor_error: bool,
 | 
				
			||||||
    config: &Config,
 | 
					    raw: u16,
 | 
				
			||||||
    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
 | 
					 | 
				
			||||||
) -> Option<usize> {
 | 
					 | 
				
			||||||
    for plant in 0..PLANT_COUNT {
 | 
					 | 
				
			||||||
        let state = &mut plantstate[plant];
 | 
					 | 
				
			||||||
        let plant_config = config.plants[plant];
 | 
					 | 
				
			||||||
        match plant_config.mode {
 | 
					 | 
				
			||||||
            config::Mode::OFF => {}
 | 
					 | 
				
			||||||
            config::Mode::TargetMoisture => {
 | 
					 | 
				
			||||||
                match board
 | 
					 | 
				
			||||||
                    .measure_moisture_hz(plant, plant_hal::Sensor::A)
 | 
					 | 
				
			||||||
                    .and_then(|moist| map_range_moisture(moist as f32))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Ok(a) => state.a = Some(a),
 | 
					 | 
				
			||||||
                    Err(err) => {
 | 
					 | 
				
			||||||
                        board.fault(plant, true);
 | 
					 | 
				
			||||||
                        println!(
 | 
					 | 
				
			||||||
                            "Could not determine Moisture A for plant {} due to {}",
 | 
					 | 
				
			||||||
                            plant, err
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        state.a = None;
 | 
					 | 
				
			||||||
                        state.sensor_error_a = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                match board
 | 
					 | 
				
			||||||
                    .measure_moisture_hz(plant, plant_hal::Sensor::B)
 | 
					 | 
				
			||||||
                    .and_then(|moist| map_range_moisture(moist as f32))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Ok(b) => state.b = Some(b),
 | 
					 | 
				
			||||||
                    Err(err) => {
 | 
					 | 
				
			||||||
                        board.fault(plant, true);
 | 
					 | 
				
			||||||
                        println!(
 | 
					 | 
				
			||||||
                            "Could not determine Moisture B for plant {} due to {}",
 | 
					 | 
				
			||||||
                            plant, err
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        state.b = None;
 | 
					 | 
				
			||||||
                        state.sensor_error_b = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                //FIXME how to average analyze whatever?
 | 
					 | 
				
			||||||
                let a_low = state.a.is_some() && state.a.unwrap() < plant_config.target_moisture;
 | 
					 | 
				
			||||||
                let b_low = state.b.is_some() && state.b.unwrap() < plant_config.target_moisture;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if a_low || b_low {
 | 
					 | 
				
			||||||
                    state.dry = true;
 | 
					 | 
				
			||||||
                    if tank_sensor_error && !config.tank_allow_pumping_if_sensor_error
 | 
					 | 
				
			||||||
                        || !enough_water
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        state.no_water = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
 | 
					 | 
				
			||||||
                let next_pump = board.last_pump_time(plant) + duration;
 | 
					 | 
				
			||||||
                if next_pump > cur {
 | 
					 | 
				
			||||||
                    state.cooldown = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if !in_time_range(
 | 
					 | 
				
			||||||
                    cur,
 | 
					 | 
				
			||||||
                    plant_config.pump_hour_start,
 | 
					 | 
				
			||||||
                    plant_config.pump_hour_end,
 | 
					 | 
				
			||||||
                ) {
 | 
					 | 
				
			||||||
                    state.out_of_work_hour = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if water_frozen {
 | 
					 | 
				
			||||||
                    state.frozen = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if state.dry && !state.no_water && !state.cooldown && !state.out_of_work_hour {
 | 
					 | 
				
			||||||
                    if water_frozen {
 | 
					 | 
				
			||||||
                        state.frozen = true;
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        state.do_water = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            config::Mode::TimerOnly => {
 | 
					 | 
				
			||||||
                let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
 | 
					 | 
				
			||||||
                let next_pump = board.last_pump_time(plant) + duration;
 | 
					 | 
				
			||||||
                if next_pump > cur {
 | 
					 | 
				
			||||||
                    state.cooldown = true;
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    if water_frozen {
 | 
					 | 
				
			||||||
                        state.frozen = true;
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        state.do_water = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            config::Mode::TimerAndDeadzone => {
 | 
					 | 
				
			||||||
                let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
 | 
					 | 
				
			||||||
                let next_pump = board.last_pump_time(plant) + duration;
 | 
					 | 
				
			||||||
                if next_pump > cur {
 | 
					 | 
				
			||||||
                    state.cooldown = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if !in_time_range(
 | 
					 | 
				
			||||||
                    cur,
 | 
					 | 
				
			||||||
                    plant_config.pump_hour_start,
 | 
					 | 
				
			||||||
                    plant_config.pump_hour_end,
 | 
					 | 
				
			||||||
                ) {
 | 
					 | 
				
			||||||
                    state.out_of_work_hour = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if !state.cooldown && !state.out_of_work_hour {
 | 
					 | 
				
			||||||
                    if water_frozen {
 | 
					 | 
				
			||||||
                        state.frozen = true;
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        state.do_water = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        //FIXME publish state here!
 | 
					 | 
				
			||||||
        if state.do_water {
 | 
					 | 
				
			||||||
            if board.consecutive_pump_count(plant) > config.max_consecutive_pump_count.into() {
 | 
					 | 
				
			||||||
                state.not_effective = true;
 | 
					 | 
				
			||||||
                board.fault(plant, true);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            board.store_consecutive_pump_count(plant, 0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        println!("Plant {} state is {:?}", plant, state);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    for plant in 0..PLANT_COUNT {
 | 
					 | 
				
			||||||
        let state = &plantstate[plant];
 | 
					 | 
				
			||||||
        println!(
 | 
					 | 
				
			||||||
            "Checking for water plant {} with state {}",
 | 
					 | 
				
			||||||
            plant, state.do_water
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        if state.do_water {
 | 
					 | 
				
			||||||
            return Some(plant);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    println!("No plant needs water");
 | 
					 | 
				
			||||||
    return None;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn safe_main() -> Result<()> {
 | 
					fn safe_main() -> anyhow::Result<()> {
 | 
				
			||||||
    // It is necessary to call this function once. Otherwise some patches to the runtime
 | 
					    // It is necessary to call this function once. Otherwise some patches to the runtime
 | 
				
			||||||
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
 | 
					    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
 | 
				
			||||||
    esp_idf_svc::sys::link_patches();
 | 
					    esp_idf_svc::sys::link_patches();
 | 
				
			||||||
@@ -432,7 +230,7 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    println!("attempting to connect wifi");
 | 
					    println!("attempting to connect wifi");
 | 
				
			||||||
    match board.wifi(&wifi.ssid, wifi.password.as_deref(), 10000) {
 | 
					    match board.wifi(wifi.ssid, wifi.password, 5000) {
 | 
				
			||||||
        Ok(_) => {
 | 
					        Ok(_) => {
 | 
				
			||||||
            online_mode = OnlineMode::Wifi;
 | 
					            online_mode = OnlineMode::Wifi;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -479,6 +277,7 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
        match board.mqtt(&config) {
 | 
					        match board.mqtt(&config) {
 | 
				
			||||||
            Ok(_) => {
 | 
					            Ok(_) => {
 | 
				
			||||||
                println!("Mqtt connection ready");
 | 
					                println!("Mqtt connection ready");
 | 
				
			||||||
 | 
					                online_mode = OnlineMode::Online;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
                println!("Could not connect mqtt due to {}", err);
 | 
					                println!("Could not connect mqtt due to {}", err);
 | 
				
			||||||
@@ -486,64 +285,27 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match board.battery_state() {
 | 
					    if online_mode == OnlineMode::Online {
 | 
				
			||||||
        Ok(_state) => {}
 | 
					        let _ = board.mqtt_publish(&config, "/firmware/githash", git_hash.as_bytes());
 | 
				
			||||||
        Err(err) => {
 | 
					        let _ = board.mqtt_publish(&config, "/state", "online".as_bytes());
 | 
				
			||||||
            board.general_fault(true);
 | 
					
 | 
				
			||||||
            println!("Could not read battery state, assuming low power {}", err);
 | 
					        publish_battery_state(&mut board, &config);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut enough_water = true;
 | 
					    let tank_state = determine_tank_state(&mut board, &config);
 | 
				
			||||||
    let mut tank_sensor_error = false;
 | 
					 | 
				
			||||||
    if config.tank_sensor_enabled {
 | 
					 | 
				
			||||||
        let mut tank_value_r = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let success = board
 | 
					 | 
				
			||||||
            .tank_sensor_percent()
 | 
					 | 
				
			||||||
            .and_then(|raw| {
 | 
					 | 
				
			||||||
                tank_value_r = raw;
 | 
					 | 
				
			||||||
                return map_range(
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        config.tank_empty_percent as f32,
 | 
					 | 
				
			||||||
                        config.tank_full_percent as f32,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    raw as f32,
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .and_then(|percent| {
 | 
					 | 
				
			||||||
                let left_ml = (percent * config.tank_useable_ml as f32) as u32;
 | 
					 | 
				
			||||||
                println!(
 | 
					 | 
				
			||||||
                    "Tank sensor returned mv {} as {}% leaving {} ml useable",
 | 
					 | 
				
			||||||
                    tank_value_r, percent as u8, left_ml
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                if config.tank_warn_percent > percent as u8 {
 | 
					 | 
				
			||||||
                    board.general_fault(true);
 | 
					 | 
				
			||||||
                    println!(
 | 
					 | 
				
			||||||
                        "Low water, current percent is {}, minimum warn level is {}",
 | 
					 | 
				
			||||||
                        percent as u8, config.tank_warn_percent
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if config.tank_warn_percent <= 0 {
 | 
					 | 
				
			||||||
                    enough_water = false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return Ok(());
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        match success {
 | 
					 | 
				
			||||||
            Err(err) => {
 | 
					 | 
				
			||||||
                println!("Could not determine tank value due to {}", err);
 | 
					 | 
				
			||||||
                board.general_fault(true);
 | 
					 | 
				
			||||||
                tank_sensor_error = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Ok(_) => {}
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut water_frozen = false;
 | 
					    let mut water_frozen = false;
 | 
				
			||||||
    for _attempt in 0..5 {
 | 
					    for _attempt in 0..5 {
 | 
				
			||||||
        let water_temperature = board.water_temperature_c();
 | 
					        let water_temperature = board.water_temperature_c();
 | 
				
			||||||
        match water_temperature {
 | 
					        match water_temperature {
 | 
				
			||||||
            Ok(temp) => {
 | 
					            Ok(temp) => {
 | 
				
			||||||
 | 
					                if online_mode == OnlineMode::Online {
 | 
				
			||||||
 | 
					                    let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                        &config,
 | 
				
			||||||
 | 
					                        "/water/temperature",
 | 
				
			||||||
 | 
					                        temp.to_string().as_bytes(),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                //FIXME mqtt here
 | 
					                //FIXME mqtt here
 | 
				
			||||||
                println!("Water temp is {}", temp);
 | 
					                println!("Water temp is {}", temp);
 | 
				
			||||||
                if temp < 4_f32 {
 | 
					                if temp < 4_f32 {
 | 
				
			||||||
@@ -552,6 +314,9 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
 | 
					                if online_mode == OnlineMode::Online {
 | 
				
			||||||
 | 
					                    let _ = board.mqtt_publish(&config, "/water/temperature", "Error".as_bytes());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                println!("Could not get water temp {}", err)
 | 
					                println!("Could not get water temp {}", err)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -561,11 +326,11 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
        ..Default::default()
 | 
					        ..Default::default()
 | 
				
			||||||
    }; PLANT_COUNT];
 | 
					    }; PLANT_COUNT];
 | 
				
			||||||
    let plant_to_pump = determine_next_plant(
 | 
					    let plant_to_pump = determine_next_plant(
 | 
				
			||||||
 | 
					        online_mode,
 | 
				
			||||||
        &mut plantstate,
 | 
					        &mut plantstate,
 | 
				
			||||||
        europe_time,
 | 
					        europe_time,
 | 
				
			||||||
        enough_water,
 | 
					        &tank_state,
 | 
				
			||||||
        water_frozen,
 | 
					        water_frozen,
 | 
				
			||||||
        tank_sensor_error,
 | 
					 | 
				
			||||||
        &config,
 | 
					        &config,
 | 
				
			||||||
        &mut board,
 | 
					        &mut board,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -602,12 +367,7 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
                Ok(p) => state.after_p = Some(p),
 | 
					                Ok(p) => state.after_p = Some(p),
 | 
				
			||||||
                Err(err) => {
 | 
					                Err(err) => {
 | 
				
			||||||
                    board.fault(plant, true);
 | 
					                    board.fault(plant, true);
 | 
				
			||||||
                    println!(
 | 
					                    state.sensor_error_p = Some(err);
 | 
				
			||||||
                        "Could not determine Moisture P after for plant {} due to {}",
 | 
					 | 
				
			||||||
                        plant, err
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                    state.after_p = None;
 | 
					 | 
				
			||||||
                    state.sensor_error_p = true;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if state.after_p.is_none()
 | 
					            if state.after_p.is_none()
 | 
				
			||||||
@@ -616,6 +376,7 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                state.pump_error = true;
 | 
					                state.pump_error = true;
 | 
				
			||||||
                board.fault(plant, true);
 | 
					                board.fault(plant, true);
 | 
				
			||||||
 | 
					                //mqtt sync pump error value
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        None => {
 | 
					        None => {
 | 
				
			||||||
@@ -632,37 +393,558 @@ fn safe_main() -> Result<()> {
 | 
				
			|||||||
        config.night_lamp_hour_start,
 | 
					        config.night_lamp_hour_start,
 | 
				
			||||||
        config.night_lamp_hour_end,
 | 
					        config.night_lamp_hour_end,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let state_of_charge = board.state_charge_percent().unwrap_or(0);
 | 
				
			||||||
 | 
					    if state_of_charge < 30 {
 | 
				
			||||||
 | 
					        board.set_low_voltage_in_cycle();
 | 
				
			||||||
 | 
					    } else if state_of_charge > 50 {
 | 
				
			||||||
 | 
					        board.clear_low_voltage_in_cycle();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    light_state.battery_low = board.low_voltage_in_cycle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if !light_state.out_of_work_hour {
 | 
					    if !light_state.out_of_work_hour {
 | 
				
			||||||
        if config.night_lamp_only_when_dark {
 | 
					        if config.night_lamp_only_when_dark {
 | 
				
			||||||
            if !light_state.is_day {
 | 
					            if !light_state.is_day {
 | 
				
			||||||
 | 
					                if light_state.battery_low {
 | 
				
			||||||
 | 
					                    board.light(false).unwrap();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    light_state.active = true;
 | 
				
			||||||
 | 
					                    board.light(true).unwrap();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if light_state.battery_low {
 | 
				
			||||||
 | 
					                board.light(false).unwrap();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
                light_state.active = true;
 | 
					                light_state.active = true;
 | 
				
			||||||
                board.light(true).unwrap();
 | 
					                board.light(true).unwrap();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            light_state.active = true;
 | 
					 | 
				
			||||||
            board.light(true).unwrap();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        light_state.active = false;
 | 
					        light_state.active = false;
 | 
				
			||||||
        board.light(false).unwrap();
 | 
					        board.light(false).unwrap();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    println!("Lightstate is {:?}", light_state);
 | 
					    println!("Lightstate is {:?}", light_state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //check if during light time
 | 
					    if online_mode == OnlineMode::Online {
 | 
				
			||||||
    //lightstate += out of worktime
 | 
					        match serde_json::to_string(&light_state) {
 | 
				
			||||||
    //check battery level
 | 
					            Ok(state) => {
 | 
				
			||||||
    //lightstate += battery empty
 | 
					                let _ = board.mqtt_publish(&config, "/light/active", state.as_bytes());
 | 
				
			||||||
    //check solar level if config requires
 | 
					            }
 | 
				
			||||||
    //lightstate += stillday
 | 
					            Err(err) => {
 | 
				
			||||||
    //if no preventing lightstate, enable light
 | 
					                println!("Error publishing lightstate {}", err);
 | 
				
			||||||
    //lightstate = active
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //relatch
 | 
					    //relatch
 | 
				
			||||||
    unsafe{gpio_deep_sleep_hold_dis()};
 | 
					    unsafe { gpio_deep_sleep_hold_dis() };
 | 
				
			||||||
    unsafe { gpio_deep_sleep_hold_en() };
 | 
					    unsafe { gpio_deep_sleep_hold_en() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //determine next event
 | 
				
			||||||
 | 
					    //is light out of work trigger soon?
 | 
				
			||||||
 | 
					    //is battery low ??
 | 
				
			||||||
 | 
					    //is deep sleep
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    unsafe { esp_deep_sleep(1000 * 1000 * 20) };
 | 
					    unsafe { esp_deep_sleep(1000 * 1000 * 20) };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn publish_battery_state(
 | 
				
			||||||
 | 
					    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
 | 
				
			||||||
 | 
					    config: &Config,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    match board.voltage_milli_volt() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/voltage_milli_volt",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/battery/voltage_milli_volt", "-1".as_bytes());
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.average_current_milli_ampere() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/average_current_milli_ampere",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/average_current_milli_ampere",
 | 
				
			||||||
 | 
					                "-1".as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.cycle_count() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/battery/cycle_count", v.to_string().as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/battery/cycle_count", "-1".as_bytes());
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.design_milli_ampere_hour() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/design_milli_ampere_hour",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/design_milli_ampere_hour",
 | 
				
			||||||
 | 
					                "-1".as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.max_milli_ampere_hour() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/max_milli_ampere_hour",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/battery/max_milli_ampere_hour", "-1".as_bytes());
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.remaining_milli_ampere_hour() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/remaining_milli_ampere_hour",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/remaining_milli_ampere_hour",
 | 
				
			||||||
 | 
					                "-1".as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.state_charge_percent() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/state_charge_percent",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/battery/state_charge_percent", "-1".as_bytes());
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    match board.state_health_percent() {
 | 
				
			||||||
 | 
					        Ok(v) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                "/battery/state_health_percent",
 | 
				
			||||||
 | 
					                v.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(err) => {
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/battery/state_health_percent", "-1".as_bytes());
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(&config, "/errorlog", format!("{:?}", err).as_bytes());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn determine_tank_state(
 | 
				
			||||||
 | 
					    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
 | 
				
			||||||
 | 
					    config: &Config,
 | 
				
			||||||
 | 
					) -> TankState {
 | 
				
			||||||
 | 
					    if config.tank_sensor_enabled {
 | 
				
			||||||
 | 
					        let mut rv: TankState = TankState {
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let success = board
 | 
				
			||||||
 | 
					            .tank_sensor_percent()
 | 
				
			||||||
 | 
					            .and_then(|raw| {
 | 
				
			||||||
 | 
					                rv.raw = raw;
 | 
				
			||||||
 | 
					                return map_range(
 | 
				
			||||||
 | 
					                    (
 | 
				
			||||||
 | 
					                        config.tank_empty_percent as f32,
 | 
				
			||||||
 | 
					                        config.tank_full_percent as f32,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    raw as f32,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .and_then(|percent| {
 | 
				
			||||||
 | 
					                rv.left_ml = (percent * config.tank_useable_ml as f32) as u32;
 | 
				
			||||||
 | 
					                println!(
 | 
				
			||||||
 | 
					                    "Tank sensor returned mv {} as {}% leaving {} ml useable",
 | 
				
			||||||
 | 
					                    rv.raw, percent as u8, rv.left_ml
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if config.tank_warn_percent > percent as u8 {
 | 
				
			||||||
 | 
					                    board.general_fault(true);
 | 
				
			||||||
 | 
					                    println!(
 | 
				
			||||||
 | 
					                        "Low water, current percent is {}, minimum warn level is {}",
 | 
				
			||||||
 | 
					                        percent as u8, config.tank_warn_percent
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if config.tank_empty_percent > percent as u8 {
 | 
				
			||||||
 | 
					                    println!(
 | 
				
			||||||
 | 
					                        "Empty water, current percent is {}, minimum empty level is {}",
 | 
				
			||||||
 | 
					                        percent as u8, config.tank_empty_percent
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    rv.enough_water = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return Ok(());
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        match success {
 | 
				
			||||||
 | 
					            Err(err) => {
 | 
				
			||||||
 | 
					                println!("Could not determine tank value due to {}", err);
 | 
				
			||||||
 | 
					                board.general_fault(true);
 | 
				
			||||||
 | 
					                rv.sensor_error = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Ok(_) => {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return rv;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return TankState {
 | 
				
			||||||
 | 
					        enough_water: true,
 | 
				
			||||||
 | 
					        left_ml: 1337,
 | 
				
			||||||
 | 
					        sensor_error: false,
 | 
				
			||||||
 | 
					        raw: 0,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn map_range(from_range: (f32, f32), s: f32) -> anyhow::Result<f32> {
 | 
				
			||||||
 | 
					    if s < from_range.0 {
 | 
				
			||||||
 | 
					        anyhow::bail!(
 | 
				
			||||||
 | 
					            "Value out of range, min {} but current is {}",
 | 
				
			||||||
 | 
					            from_range.0,
 | 
				
			||||||
 | 
					            s
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if s > from_range.1 {
 | 
				
			||||||
 | 
					        anyhow::bail!(
 | 
				
			||||||
 | 
					            "Value out of range, max {} but current is {}",
 | 
				
			||||||
 | 
					            from_range.1,
 | 
				
			||||||
 | 
					            s
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return Ok(TO.0 + (s - from_range.0) * (TO.1 - TO.0) / (from_range.1 - from_range.0));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn map_range_moisture(s: f32) -> Result<u8, SensorError> {
 | 
				
			||||||
 | 
					    if s < FROM.0 {
 | 
				
			||||||
 | 
					        return Err(SensorError::OpenCircuit { hz: s, min: FROM.0 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if s > FROM.1 {
 | 
				
			||||||
 | 
					        return Err(SensorError::ShortCircuit { hz: s, max: FROM.1 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let tmp = TO.0 + (s - FROM.0) * (TO.1 - TO.0) / (FROM.1 - FROM.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Ok(tmp as u8);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn in_time_range(cur: DateTime<Tz>, start: u8, end: u8) -> bool {
 | 
				
			||||||
 | 
					    let curhour = cur.hour() as u8;
 | 
				
			||||||
 | 
					    //eg 10-14
 | 
				
			||||||
 | 
					    if start < end {
 | 
				
			||||||
 | 
					        return curhour > start && curhour < end;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        //eg 20-05
 | 
				
			||||||
 | 
					        return curhour > start || curhour < end;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn option_to_string(value: Option<u8>) -> String {
 | 
				
			||||||
 | 
					    match value {
 | 
				
			||||||
 | 
					        Some(v) => v.to_string(),
 | 
				
			||||||
 | 
					        None => "Error".to_owned(),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn determine_state_target_moisture_for_plant(
 | 
				
			||||||
 | 
					    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
 | 
				
			||||||
 | 
					    plant: usize,
 | 
				
			||||||
 | 
					    state: &mut PlantState,
 | 
				
			||||||
 | 
					    config: &Config,
 | 
				
			||||||
 | 
					    tank_state: &TankState,
 | 
				
			||||||
 | 
					    water_frozen: bool,
 | 
				
			||||||
 | 
					    cur: DateTime<Tz>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    let plant_config = &config.plants[plant];
 | 
				
			||||||
 | 
					    match board.measure_moisture_hz(plant, plant_hal::Sensor::A) {
 | 
				
			||||||
 | 
					        Ok(a) => {
 | 
				
			||||||
 | 
					            let mapped = map_range_moisture(a as f32);
 | 
				
			||||||
 | 
					            match mapped {
 | 
				
			||||||
 | 
					                Ok(result) => state.a = Some(result),
 | 
				
			||||||
 | 
					                Err(err) => {
 | 
				
			||||||
 | 
					                    state.sensor_error_a = Some(err);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(_) => {
 | 
				
			||||||
 | 
					            state.sensor_error_a = Some(SensorError::Unknown);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    match board.measure_moisture_hz(plant, plant_hal::Sensor::B) {
 | 
				
			||||||
 | 
					        Ok(b) => {
 | 
				
			||||||
 | 
					            let mapped = map_range_moisture(b as f32);
 | 
				
			||||||
 | 
					            match mapped {
 | 
				
			||||||
 | 
					                Ok(result) => state.b = Some(result),
 | 
				
			||||||
 | 
					                Err(err) => {
 | 
				
			||||||
 | 
					                    state.sensor_error_b = Some(err);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Err(_) => {
 | 
				
			||||||
 | 
					            state.sensor_error_b = Some(SensorError::Unknown);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    //FIXME how to average analyze whatever?
 | 
				
			||||||
 | 
					    let a_low = state.a.is_some() && state.a.unwrap() < plant_config.target_moisture;
 | 
				
			||||||
 | 
					    let b_low = state.b.is_some() && state.b.unwrap() < plant_config.target_moisture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if a_low || b_low {
 | 
				
			||||||
 | 
					        state.dry = true;
 | 
				
			||||||
 | 
					        if tank_state.sensor_error && !config.tank_allow_pumping_if_sensor_error
 | 
				
			||||||
 | 
					            || !tank_state.enough_water
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            state.no_water = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let duration = Duration::minutes((plant_config.pump_cooldown_min).into());
 | 
				
			||||||
 | 
					    let next_pump = board.last_pump_time(plant) + duration;
 | 
				
			||||||
 | 
					    if next_pump > cur {
 | 
				
			||||||
 | 
					        state.cooldown = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if !in_time_range(
 | 
				
			||||||
 | 
					        cur,
 | 
				
			||||||
 | 
					        plant_config.pump_hour_start,
 | 
				
			||||||
 | 
					        plant_config.pump_hour_end,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        state.out_of_work_hour = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if water_frozen {
 | 
				
			||||||
 | 
					        state.frozen = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if state.dry && !state.no_water && !state.cooldown && !state.out_of_work_hour {
 | 
				
			||||||
 | 
					        if water_frozen {
 | 
				
			||||||
 | 
					            state.frozen = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            state.do_water = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn determine_next_plant(
 | 
				
			||||||
 | 
					    online_mode: OnlineMode,
 | 
				
			||||||
 | 
					    plantstate: &mut [PlantState; PLANT_COUNT],
 | 
				
			||||||
 | 
					    cur: DateTime<Tz>,
 | 
				
			||||||
 | 
					    tank_state: &TankState,
 | 
				
			||||||
 | 
					    water_frozen: bool,
 | 
				
			||||||
 | 
					    config: &Config,
 | 
				
			||||||
 | 
					    board: &mut std::sync::MutexGuard<'_, PlantCtrlBoard<'_>>,
 | 
				
			||||||
 | 
					) -> Option<usize> {
 | 
				
			||||||
 | 
					    for plant in 0..PLANT_COUNT {
 | 
				
			||||||
 | 
					        let state = &mut plantstate[plant];
 | 
				
			||||||
 | 
					        let plant_config = &config.plants[plant];
 | 
				
			||||||
 | 
					        match plant_config.mode {
 | 
				
			||||||
 | 
					            config::Mode::OFF => {}
 | 
				
			||||||
 | 
					            config::Mode::TargetMoisture => {
 | 
				
			||||||
 | 
					                determine_state_target_moisture_for_plant(
 | 
				
			||||||
 | 
					                    board,
 | 
				
			||||||
 | 
					                    plant,
 | 
				
			||||||
 | 
					                    state,
 | 
				
			||||||
 | 
					                    config,
 | 
				
			||||||
 | 
					                    tank_state,
 | 
				
			||||||
 | 
					                    water_frozen,
 | 
				
			||||||
 | 
					                    cur,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            config::Mode::TimerOnly => {
 | 
				
			||||||
 | 
					                let duration = Duration::minutes((plant_config.pump_cooldown_min).into());
 | 
				
			||||||
 | 
					                let next_pump = board.last_pump_time(plant) + duration;
 | 
				
			||||||
 | 
					                if next_pump > cur {
 | 
				
			||||||
 | 
					                    state.cooldown = true;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if water_frozen {
 | 
				
			||||||
 | 
					                        state.frozen = true;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        state.do_water = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            config::Mode::TimerAndDeadzone => {
 | 
				
			||||||
 | 
					                let duration = Duration::minutes((60 * plant_config.pump_cooldown_min).into());
 | 
				
			||||||
 | 
					                let next_pump = board.last_pump_time(plant) + duration;
 | 
				
			||||||
 | 
					                if next_pump > cur {
 | 
				
			||||||
 | 
					                    state.cooldown = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if !in_time_range(
 | 
				
			||||||
 | 
					                    cur,
 | 
				
			||||||
 | 
					                    plant_config.pump_hour_start,
 | 
				
			||||||
 | 
					                    plant_config.pump_hour_end,
 | 
				
			||||||
 | 
					                ) {
 | 
				
			||||||
 | 
					                    state.out_of_work_hour = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if !state.cooldown && !state.out_of_work_hour {
 | 
				
			||||||
 | 
					                    if water_frozen {
 | 
				
			||||||
 | 
					                        state.frozen = true;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        state.do_water = true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if state.sensor_error_a.is_some()
 | 
				
			||||||
 | 
					            || state.sensor_error_b.is_some()
 | 
				
			||||||
 | 
					            || state.sensor_error_p.is_some()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            board.fault(plant, true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if state.do_water {
 | 
				
			||||||
 | 
					            if board.consecutive_pump_count(plant) > config.max_consecutive_pump_count.into() {
 | 
				
			||||||
 | 
					                state.not_effective = true;
 | 
				
			||||||
 | 
					                board.fault(plant, true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            board.store_consecutive_pump_count(plant, 0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        println!("Plant {} state is {:?}", plant, state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if online_mode == OnlineMode::Online {
 | 
				
			||||||
 | 
					        for plant in 0..PLANT_COUNT {
 | 
				
			||||||
 | 
					            let state = &plantstate[plant];
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Sensor A", plant).as_str(),
 | 
				
			||||||
 | 
					                option_to_string(state.a).as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Sensor B", plant).as_str(),
 | 
				
			||||||
 | 
					                option_to_string(state.b).as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Sensor P before", plant).as_str(),
 | 
				
			||||||
 | 
					                option_to_string(state.p).as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Sensor P after", plant).as_str(),
 | 
				
			||||||
 | 
					                option_to_string(state.after_p).as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Should water", plant).as_str(),
 | 
				
			||||||
 | 
					                state.do_water.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Is frozen", plant).as_str(),
 | 
				
			||||||
 | 
					                state.frozen.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Is dry", plant).as_str(),
 | 
				
			||||||
 | 
					                state.dry.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Pump Error", plant).as_str(),
 | 
				
			||||||
 | 
					                state.pump_error.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Pump Ineffective", plant).as_str(),
 | 
				
			||||||
 | 
					                state.not_effective.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Is in Cooldown", plant).as_str(),
 | 
				
			||||||
 | 
					                state.cooldown.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/No Water", plant).as_str(),
 | 
				
			||||||
 | 
					                state.no_water.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            let _ = board.mqtt_publish(
 | 
				
			||||||
 | 
					                &config,
 | 
				
			||||||
 | 
					                format!("/plant{}/Out of Work Hour", plant).as_str(),
 | 
				
			||||||
 | 
					                state.out_of_work_hour.to_string().as_bytes(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for plant in 0..PLANT_COUNT {
 | 
				
			||||||
 | 
					        let state = &plantstate[plant];
 | 
				
			||||||
 | 
					        println!(
 | 
				
			||||||
 | 
					            "Checking for water plant {} with state {}",
 | 
				
			||||||
 | 
					            plant, state.do_water
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if state.do_water {
 | 
				
			||||||
 | 
					            return Some(plant);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    println!("No plant needs water");
 | 
				
			||||||
 | 
					    return None;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
 | 
				
			||||||
 | 
					    let delay = match wait_type {
 | 
				
			||||||
 | 
					        WaitType::InitialConfig => 250_u32,
 | 
				
			||||||
 | 
					        WaitType::FlashError => 100_u32,
 | 
				
			||||||
 | 
					        WaitType::NormalConfig => 500_u32,
 | 
				
			||||||
 | 
					        WaitType::StayAlive => 1000_u32,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    let led_count = match wait_type {
 | 
				
			||||||
 | 
					        WaitType::InitialConfig => 8,
 | 
				
			||||||
 | 
					        WaitType::FlashError => 8,
 | 
				
			||||||
 | 
					        WaitType::NormalConfig => 4,
 | 
				
			||||||
 | 
					        WaitType::StayAlive => 2,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            //do not trigger watchdog
 | 
				
			||||||
 | 
					            for i in 0..8 {
 | 
				
			||||||
 | 
					                BOARD_ACCESS.lock().unwrap().fault(i, i < led_count);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            BOARD_ACCESS.lock().unwrap().general_fault(true);
 | 
				
			||||||
 | 
					            vTaskDelay(delay);
 | 
				
			||||||
 | 
					            BOARD_ACCESS.lock().unwrap().general_fault(false);
 | 
				
			||||||
 | 
					            for i in 0..8 {
 | 
				
			||||||
 | 
					                BOARD_ACCESS.lock().unwrap().fault(i, false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            vTaskDelay(delay);
 | 
				
			||||||
 | 
					            if wait_type == WaitType::StayAlive
 | 
				
			||||||
 | 
					                && !STAY_ALIVE.load(std::sync::atomic::Ordering::Relaxed)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
 | 
				
			||||||
 | 
					                println!("Rebooting");
 | 
				
			||||||
 | 
					                esp_restart();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    let result = safe_main();
 | 
					    let result = safe_main();
 | 
				
			||||||
    result.unwrap();
 | 
					    result.unwrap();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,15 @@
 | 
				
			|||||||
 | 
					use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
 | 
				
			||||||
//mod config;
 | 
					//mod config;
 | 
				
			||||||
 | 
					use chrono_tz::Europe::Berlin;
 | 
				
			||||||
use embedded_svc::wifi::{
 | 
					use embedded_svc::wifi::{
 | 
				
			||||||
    AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
 | 
					    AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
use esp_idf_hal::i2c::{I2cConfig, I2cDriver, I2cError};
 | 
					use esp_idf_hal::i2c::{I2cConfig, I2cDriver, I2cError};
 | 
				
			||||||
use esp_idf_hal::units::FromValueType;
 | 
					use esp_idf_hal::units::FromValueType;
 | 
				
			||||||
use esp_idf_svc::eventloop::EspSystemEventLoop;
 | 
					use esp_idf_svc::eventloop::EspSystemEventLoop;
 | 
				
			||||||
 | 
					use esp_idf_svc::mqtt::client::QoS::AtLeastOnce;
 | 
				
			||||||
use esp_idf_svc::mqtt::client::QoS::ExactlyOnce;
 | 
					use esp_idf_svc::mqtt::client::QoS::ExactlyOnce;
 | 
				
			||||||
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
 | 
					use esp_idf_svc::mqtt::client::{EspMqttClient, LwtConfiguration, MqttClientConfiguration};
 | 
				
			||||||
use esp_idf_svc::nvs::EspDefaultNvsPartition;
 | 
					use esp_idf_svc::nvs::EspDefaultNvsPartition;
 | 
				
			||||||
use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
 | 
					use esp_idf_svc::wifi::config::{ScanConfig, ScanType};
 | 
				
			||||||
use esp_idf_svc::wifi::EspWifi;
 | 
					use esp_idf_svc::wifi::EspWifi;
 | 
				
			||||||
@@ -16,19 +18,19 @@ use plant_ctrl2::sipo::ShiftRegister40;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use anyhow::anyhow;
 | 
					use anyhow::anyhow;
 | 
				
			||||||
use anyhow::{bail, Ok, Result};
 | 
					use anyhow::{bail, Ok, Result};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					 | 
				
			||||||
use std::ffi::CString;
 | 
					use std::ffi::CString;
 | 
				
			||||||
use std::fs::File;
 | 
					use std::fs::File;
 | 
				
			||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use chrono::{DateTime, NaiveDateTime, Utc};
 | 
				
			||||||
 | 
					use ds18b20::Ds18b20;
 | 
				
			||||||
 | 
					use std::result::Result::Ok as OkStd;
 | 
				
			||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
use std::sync::atomic::AtomicBool;
 | 
					use std::sync::atomic::AtomicBool;
 | 
				
			||||||
use std::sync::{Arc, Mutex};
 | 
					use std::sync::{Arc, Mutex};
 | 
				
			||||||
use std::time::Duration;
 | 
					use std::time::Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono::{DateTime, NaiveDateTime, Utc};
 | 
					use embedded_hal::digital::OutputPin;
 | 
				
			||||||
use ds18b20::Ds18b20;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use embedded_hal::digital::v2::OutputPin;
 | 
					 | 
				
			||||||
use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
 | 
					use esp_idf_hal::adc::{attenuation, AdcChannelDriver, AdcDriver};
 | 
				
			||||||
use esp_idf_hal::delay::Delay;
 | 
					use esp_idf_hal::delay::Delay;
 | 
				
			||||||
use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, InputOutput, Level, PinDriver, Pull};
 | 
					use esp_idf_hal::gpio::{AnyInputPin, Gpio39, Gpio4, InputOutput, Level, PinDriver, Pull};
 | 
				
			||||||
@@ -39,10 +41,9 @@ use esp_idf_hal::prelude::Peripherals;
 | 
				
			|||||||
use esp_idf_hal::reset::ResetReason;
 | 
					use esp_idf_hal::reset::ResetReason;
 | 
				
			||||||
use esp_idf_svc::sntp::{self, SyncStatus};
 | 
					use esp_idf_svc::sntp::{self, SyncStatus};
 | 
				
			||||||
use esp_idf_svc::systime::EspSystemTime;
 | 
					use esp_idf_svc::systime::EspSystemTime;
 | 
				
			||||||
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
 | 
					use esp_idf_sys::{esp, gpio_hold_dis, gpio_hold_en, vTaskDelay, EspError};
 | 
				
			||||||
use one_wire_bus::OneWire;
 | 
					use one_wire_bus::OneWire;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver};
 | 
					 | 
				
			||||||
use crate::config::{self, Config, WifiConfig};
 | 
					use crate::config::{self, Config, WifiConfig};
 | 
				
			||||||
use crate::STAY_ALIVE;
 | 
					use crate::STAY_ALIVE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,8 +52,8 @@ const PINS_PER_PLANT: usize = 5;
 | 
				
			|||||||
const PLANT_PUMP_OFFSET: usize = 0;
 | 
					const PLANT_PUMP_OFFSET: usize = 0;
 | 
				
			||||||
const PLANT_FAULT_OFFSET: usize = 1;
 | 
					const PLANT_FAULT_OFFSET: usize = 1;
 | 
				
			||||||
const PLANT_MOIST_PUMP_OFFSET: usize = 2;
 | 
					const PLANT_MOIST_PUMP_OFFSET: usize = 2;
 | 
				
			||||||
const PLANT_MOIST_B_OFFSET: usize = 3;
 | 
					const PLANT_MOIST_A_OFFSET: usize = 3;
 | 
				
			||||||
const PLANT_MOIST_A_OFFSET: usize = 4;
 | 
					const PLANT_MOIST_B_OFFSET: usize = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SPIFFS_PARTITION_NAME: &str = "storage";
 | 
					const SPIFFS_PARTITION_NAME: &str = "storage";
 | 
				
			||||||
const WIFI_CONFIG_FILE: &str = "/spiffs/wifi.cfg";
 | 
					const WIFI_CONFIG_FILE: &str = "/spiffs/wifi.cfg";
 | 
				
			||||||
@@ -67,43 +68,6 @@ static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
 | 
				
			|||||||
#[link_section = ".rtc.data"]
 | 
					#[link_section = ".rtc.data"]
 | 
				
			||||||
static mut LOW_VOLTAGE_DETECTED: bool = false;
 | 
					static mut LOW_VOLTAGE_DETECTED: bool = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize, Debug)]
 | 
					 | 
				
			||||||
pub struct BatteryState {
 | 
					 | 
				
			||||||
    pub state_charge_percent: u8,
 | 
					 | 
				
			||||||
    max_error_percent: u8,
 | 
					 | 
				
			||||||
    remaining_milli_ampere_hour: u32,
 | 
					 | 
				
			||||||
    max_milli_ampere_hour: u32,
 | 
					 | 
				
			||||||
    design_milli_ampere_hour: u32,
 | 
					 | 
				
			||||||
    voltage_milli_volt: u16,
 | 
					 | 
				
			||||||
    average_current_milli_ampere: u16,
 | 
					 | 
				
			||||||
    temperature_tenth_kelvin: u32,
 | 
					 | 
				
			||||||
    average_time_to_empty_minute: u16,
 | 
					 | 
				
			||||||
    average_time_to_full_minute: u16,
 | 
					 | 
				
			||||||
    average_discharge_power_cycle_milli_watt: u16,
 | 
					 | 
				
			||||||
    cycle_count: u16,
 | 
					 | 
				
			||||||
    state_health_percent: u8,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for BatteryState {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        BatteryState {
 | 
					 | 
				
			||||||
            state_charge_percent: 50,
 | 
					 | 
				
			||||||
            max_error_percent: 100,
 | 
					 | 
				
			||||||
            remaining_milli_ampere_hour: 100,
 | 
					 | 
				
			||||||
            max_milli_ampere_hour: 200,
 | 
					 | 
				
			||||||
            design_milli_ampere_hour: 200,
 | 
					 | 
				
			||||||
            voltage_milli_volt: 12,
 | 
					 | 
				
			||||||
            average_current_milli_ampere: 50,
 | 
					 | 
				
			||||||
            temperature_tenth_kelvin: 1337,
 | 
					 | 
				
			||||||
            average_time_to_empty_minute: 123,
 | 
					 | 
				
			||||||
            average_time_to_full_minute: 123,
 | 
					 | 
				
			||||||
            average_discharge_power_cycle_milli_watt: 123,
 | 
					 | 
				
			||||||
            cycle_count: 123,
 | 
					 | 
				
			||||||
            state_health_percent: 90,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct FileSystemSizeInfo {
 | 
					pub struct FileSystemSizeInfo {
 | 
				
			||||||
    pub total_size: usize,
 | 
					    pub total_size: usize,
 | 
				
			||||||
    pub used_size: usize,
 | 
					    pub used_size: usize,
 | 
				
			||||||
@@ -125,12 +89,24 @@ pub enum Sensor {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
pub trait PlantCtrlBoardInteraction {
 | 
					pub trait PlantCtrlBoardInteraction {
 | 
				
			||||||
    fn time(&mut self) -> Result<chrono::DateTime<Utc>>;
 | 
					    fn time(&mut self) -> Result<chrono::DateTime<Utc>>;
 | 
				
			||||||
    fn wifi(&mut self, ssid: &str, password: Option<&str>, max_wait: u32) -> Result<()>;
 | 
					    fn wifi(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        ssid: heapless::String<32>,
 | 
				
			||||||
 | 
					        password: Option<heapless::String<64>>,
 | 
				
			||||||
 | 
					        max_wait: u32,
 | 
				
			||||||
 | 
					    ) -> Result<()>;
 | 
				
			||||||
    fn sntp(&mut self, max_wait: u32) -> Result<chrono::DateTime<Utc>>;
 | 
					    fn sntp(&mut self, max_wait: u32) -> Result<chrono::DateTime<Utc>>;
 | 
				
			||||||
    fn mount_file_system(&mut self) -> Result<()>;
 | 
					    fn mount_file_system(&mut self) -> Result<()>;
 | 
				
			||||||
    fn file_system_size(&mut self) -> Result<FileSystemSizeInfo>;
 | 
					    fn file_system_size(&mut self) -> Result<FileSystemSizeInfo>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn battery_state(&mut self) -> Result<BatteryState>;
 | 
					    fn state_charge_percent(&mut self) -> Result<u8>;
 | 
				
			||||||
 | 
					    fn remaining_milli_ampere_hour(&mut self) -> Result<u16>;
 | 
				
			||||||
 | 
					    fn max_milli_ampere_hour(&mut self) -> Result<u16>;
 | 
				
			||||||
 | 
					    fn design_milli_ampere_hour(&mut self) -> Result<u16>;
 | 
				
			||||||
 | 
					    fn voltage_milli_volt(&mut self) -> Result<u16>;
 | 
				
			||||||
 | 
					    fn average_current_milli_ampere(&mut self) -> Result<i16>;
 | 
				
			||||||
 | 
					    fn cycle_count(&mut self) -> Result<u16>;
 | 
				
			||||||
 | 
					    fn state_health_percent(&mut self) -> Result<u8>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn general_fault(&mut self, enable: bool);
 | 
					    fn general_fault(&mut self, enable: bool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,6 +144,7 @@ pub trait PlantCtrlBoardInteraction {
 | 
				
			|||||||
    fn test(&mut self) -> Result<()>;
 | 
					    fn test(&mut self) -> Result<()>;
 | 
				
			||||||
    fn is_wifi_config_file_existant(&mut self) -> bool;
 | 
					    fn is_wifi_config_file_existant(&mut self) -> bool;
 | 
				
			||||||
    fn mqtt(&mut self, config: &Config) -> Result<()>;
 | 
					    fn mqtt(&mut self, config: &Config) -> Result<()>;
 | 
				
			||||||
 | 
					    fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait CreatePlantHal<'a> {
 | 
					pub trait CreatePlantHal<'a> {
 | 
				
			||||||
@@ -182,8 +159,6 @@ pub struct PlantCtrlBoard<'a> {
 | 
				
			|||||||
        PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
 | 
					        PinDriver<'a, esp_idf_hal::gpio::Gpio22, InputOutput>,
 | 
				
			||||||
        PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
 | 
					        PinDriver<'a, esp_idf_hal::gpio::Gpio19, InputOutput>,
 | 
				
			||||||
    >,
 | 
					    >,
 | 
				
			||||||
    consecutive_watering_plant: Mutex<[u32; PLANT_COUNT]>,
 | 
					 | 
				
			||||||
    last_watering_timestamp: Mutex<[i64; PLANT_COUNT]>,
 | 
					 | 
				
			||||||
    low_voltage_detected: Mutex<bool>,
 | 
					    low_voltage_detected: Mutex<bool>,
 | 
				
			||||||
    tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
 | 
					    tank_driver: AdcDriver<'a, esp_idf_hal::adc::ADC1>,
 | 
				
			||||||
    tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
 | 
					    tank_channel: esp_idf_hal::adc::AdcChannelDriver<'a, { attenuation::DB_11 }, Gpio39>,
 | 
				
			||||||
@@ -197,17 +172,10 @@ pub struct PlantCtrlBoard<'a> {
 | 
				
			|||||||
    pub wifi_driver: EspWifi<'a>,
 | 
					    pub wifi_driver: EspWifi<'a>,
 | 
				
			||||||
    one_wire_bus: OneWire<PinDriver<'a, Gpio4, esp_idf_hal::gpio::InputOutput>>,
 | 
					    one_wire_bus: OneWire<PinDriver<'a, Gpio4, esp_idf_hal::gpio::InputOutput>>,
 | 
				
			||||||
    mqtt_client: Option<EspMqttClient<'a>>,
 | 
					    mqtt_client: Option<EspMqttClient<'a>>,
 | 
				
			||||||
 | 
					    battery_driver: Bq34z100g1Driver<I2cDriver<'a>, Delay>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
					impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			||||||
    fn battery_state(&mut self) -> Result<BatteryState> {
 | 
					 | 
				
			||||||
        let state = BatteryState {
 | 
					 | 
				
			||||||
            ..Default::default()
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(state)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn is_day(&self) -> bool {
 | 
					    fn is_day(&self) -> bool {
 | 
				
			||||||
        self.solar_is_day.get_level().into()
 | 
					        self.solar_is_day.get_level().into()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -281,11 +249,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_low_voltage_in_cycle(&mut self) {
 | 
					    fn set_low_voltage_in_cycle(&mut self) {
 | 
				
			||||||
        *self.low_voltage_detected.get_mut().unwrap() = true;
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            LOW_VOLTAGE_DETECTED = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn clear_low_voltage_in_cycle(&mut self) {
 | 
					    fn clear_low_voltage_in_cycle(&mut self) {
 | 
				
			||||||
        *self.low_voltage_detected.get_mut().unwrap() = false;
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            LOW_VOLTAGE_DETECTED = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn light(&mut self, enable: bool) -> Result<()> {
 | 
					    fn light(&mut self, enable: bool) -> Result<()> {
 | 
				
			||||||
@@ -311,15 +283,21 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>) {
 | 
					    fn store_last_pump_time(&mut self, plant: usize, time: chrono::DateTime<Utc>) {
 | 
				
			||||||
        self.last_watering_timestamp.get_mut().unwrap()[plant] = time.timestamp_millis();
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
 | 
					    fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
 | 
				
			||||||
        self.consecutive_watering_plant.get_mut().unwrap()[plant] = count;
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            CONSECUTIVE_WATERING_PLANT[plant] = count;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
 | 
					    fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
 | 
				
			||||||
        return self.consecutive_watering_plant.get_mut().unwrap()[plant];
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            return CONSECUTIVE_WATERING_PLANT[plant];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn fault(&self, plant: usize, enable: bool) {
 | 
					    fn fault(&self, plant: usize, enable: bool) {
 | 
				
			||||||
@@ -330,7 +308,9 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn low_voltage_in_cycle(&mut self) -> bool {
 | 
					    fn low_voltage_in_cycle(&mut self) -> bool {
 | 
				
			||||||
        return *self.low_voltage_detected.get_mut().unwrap();
 | 
					        unsafe {
 | 
				
			||||||
 | 
					            return LOW_VOLTAGE_DETECTED;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn any_pump(&mut self, enable: bool) -> Result<()> {
 | 
					    fn any_pump(&mut self, enable: bool) -> Result<()> {
 | 
				
			||||||
@@ -376,7 +356,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let delay = Delay::new_default();
 | 
					        let delay = Delay::new_default();
 | 
				
			||||||
        let measurement = 100;
 | 
					        let measurement = 100;
 | 
				
			||||||
        let factor = 1000 / 100;
 | 
					        let factor = 1000 as f32 / measurement as f32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.shift_register.decompose()[index].set_high().unwrap();
 | 
					        self.shift_register.decompose()[index].set_high().unwrap();
 | 
				
			||||||
        //give some time to stabilize
 | 
					        //give some time to stabilize
 | 
				
			||||||
@@ -386,7 +366,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
        self.signal_counter.counter_pause()?;
 | 
					        self.signal_counter.counter_pause()?;
 | 
				
			||||||
        self.shift_register.decompose()[index].set_low().unwrap();
 | 
					        self.shift_register.decompose()[index].set_low().unwrap();
 | 
				
			||||||
        let unscaled = self.signal_counter.get_counter_value()? as i32;
 | 
					        let unscaled = self.signal_counter.get_counter_value()? as i32;
 | 
				
			||||||
        let hz = unscaled * factor;
 | 
					        let hz = (unscaled as f32 * factor) as i32;
 | 
				
			||||||
        println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
 | 
					        println!("Measuring {:?} @ {} with {}", sensor, plant, hz);
 | 
				
			||||||
        Ok(hz)
 | 
					        Ok(hz)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -397,7 +377,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn wifi_ap(&mut self) -> Result<()> {
 | 
					    fn wifi_ap(&mut self) -> Result<()> {
 | 
				
			||||||
        let apconfig = AccessPointConfiguration {
 | 
					        let apconfig = AccessPointConfiguration {
 | 
				
			||||||
            ssid: "PlantCtrl".into(),
 | 
					            ssid: heapless::String::from_str("PlantCtrl").unwrap(),
 | 
				
			||||||
            auth_method: AuthMethod::None,
 | 
					            auth_method: AuthMethod::None,
 | 
				
			||||||
            ssid_hidden: false,
 | 
					            ssid_hidden: false,
 | 
				
			||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
@@ -409,14 +389,19 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn wifi(&mut self, ssid: &str, password: Option<&str>, max_wait: u32) -> Result<()> {
 | 
					    fn wifi(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        ssid: heapless::String<32>,
 | 
				
			||||||
 | 
					        password: Option<heapless::String<64>>,
 | 
				
			||||||
 | 
					        max_wait: u32,
 | 
				
			||||||
 | 
					    ) -> Result<()> {
 | 
				
			||||||
        match password {
 | 
					        match password {
 | 
				
			||||||
            Some(pw) => {
 | 
					            Some(pw) => {
 | 
				
			||||||
                //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
 | 
					                //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
 | 
				
			||||||
                self.wifi_driver.set_configuration(&Configuration::Client(
 | 
					                self.wifi_driver.set_configuration(&Configuration::Client(
 | 
				
			||||||
                    ClientConfiguration {
 | 
					                    ClientConfiguration {
 | 
				
			||||||
                        ssid: ssid.into(),
 | 
					                        ssid: ssid,
 | 
				
			||||||
                        password: pw.into(),
 | 
					                        password: pw,
 | 
				
			||||||
                        ..Default::default()
 | 
					                        ..Default::default()
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                ))?;
 | 
					                ))?;
 | 
				
			||||||
@@ -424,7 +409,7 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
            None => {
 | 
					            None => {
 | 
				
			||||||
                self.wifi_driver
 | 
					                self.wifi_driver
 | 
				
			||||||
                    .set_configuration(&Configuration::Client(ClientConfiguration {
 | 
					                    .set_configuration(&Configuration::Client(ClientConfiguration {
 | 
				
			||||||
                        ssid: ssid.into(),
 | 
					                        ssid: ssid,
 | 
				
			||||||
                        auth_method: AuthMethod::None,
 | 
					                        auth_method: AuthMethod::None,
 | 
				
			||||||
                        ..Default::default()
 | 
					                        ..Default::default()
 | 
				
			||||||
                    }))
 | 
					                    }))
 | 
				
			||||||
@@ -439,7 +424,6 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
        let mut counter = 0_u32;
 | 
					        let mut counter = 0_u32;
 | 
				
			||||||
        while !self.wifi_driver.is_connected()? {
 | 
					        while !self.wifi_driver.is_connected()? {
 | 
				
			||||||
            println!("Waiting for station connection");
 | 
					            println!("Waiting for station connection");
 | 
				
			||||||
            //TODO blink status?
 | 
					 | 
				
			||||||
            delay.delay_ms(250);
 | 
					            delay.delay_ms(250);
 | 
				
			||||||
            counter += 250;
 | 
					            counter += 250;
 | 
				
			||||||
            if counter > max_wait {
 | 
					            if counter > max_wait {
 | 
				
			||||||
@@ -539,7 +523,11 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn get_config(&mut self) -> Result<config::Config> {
 | 
					    fn get_config(&mut self) -> Result<config::Config> {
 | 
				
			||||||
        let cfg = File::open(CONFIG_FILE)?;
 | 
					        let cfg = File::open(CONFIG_FILE)?;
 | 
				
			||||||
        let config: Config = serde_json::from_reader(cfg)?;
 | 
					        let mut config: Config = serde_json::from_reader(cfg)?;
 | 
				
			||||||
 | 
					        //remove duplicate end of topic
 | 
				
			||||||
 | 
					        if config.base_topic.ends_with("/") {
 | 
				
			||||||
 | 
					            config.base_topic.pop();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        Ok(config)
 | 
					        Ok(config)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -627,8 +615,15 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn mqtt(&mut self, config: &Config) -> Result<()> {
 | 
					    fn mqtt(&mut self, config: &Config) -> Result<()> {
 | 
				
			||||||
        //FIXME testament
 | 
					        let last_will_topic = format!("{}/state", config.base_topic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mqtt_client_config = MqttClientConfiguration {
 | 
					        let mqtt_client_config = MqttClientConfiguration {
 | 
				
			||||||
 | 
					            lwt: Some(LwtConfiguration {
 | 
				
			||||||
 | 
					                topic: &last_will_topic,
 | 
				
			||||||
 | 
					                payload: "lost".as_bytes(),
 | 
				
			||||||
 | 
					                qos: AtLeastOnce,
 | 
				
			||||||
 | 
					                retain: true,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
            //room for improvement
 | 
					            //room for improvement
 | 
				
			||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
@@ -643,32 +638,32 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
        let round_trip_topic_copy = round_trip_topic.clone();
 | 
					        let round_trip_topic_copy = round_trip_topic.clone();
 | 
				
			||||||
        let round_trip_ok_copy = round_trip_ok.clone();
 | 
					        let round_trip_ok_copy = round_trip_ok.clone();
 | 
				
			||||||
        let mut client =
 | 
					        let mut client =
 | 
				
			||||||
            EspMqttClient::new(&config.mqtt_url, &mqtt_client_config, move |handler| {
 | 
					            EspMqttClient::new_cb(&config.mqtt_url, &mqtt_client_config, move |event| {
 | 
				
			||||||
                match handler {
 | 
					                let payload = event.payload();
 | 
				
			||||||
                    Err(err) => println!("Ignoring damaged message {}", err),
 | 
					                match payload {
 | 
				
			||||||
                    core::result::Result::Ok(event) => {
 | 
					                    embedded_svc::mqtt::client::EventPayload::Received {
 | 
				
			||||||
                        match event {
 | 
					                        id: _,
 | 
				
			||||||
                            embedded_svc::mqtt::client::Event::Received(msg) => {
 | 
					                        topic,
 | 
				
			||||||
                                let data = String::from_utf8_lossy(msg.data());
 | 
					                        data,
 | 
				
			||||||
                                if let Some(topic) = msg.topic() {
 | 
					                        details: _,
 | 
				
			||||||
                                    //todo use enums
 | 
					                    } => {
 | 
				
			||||||
                                    if topic.eq(round_trip_topic_copy.as_str()) {
 | 
					                        let data = String::from_utf8_lossy(data);
 | 
				
			||||||
                                        round_trip_ok_copy
 | 
					                        if let Some(topic) = topic {
 | 
				
			||||||
                                            .store(true, std::sync::atomic::Ordering::Relaxed);
 | 
					                            //todo use enums
 | 
				
			||||||
                                    } else if topic.eq(stay_alive_topic_copy.as_str()) {
 | 
					                            if topic.eq(round_trip_topic_copy.as_str()) {
 | 
				
			||||||
                                        let value = data.eq_ignore_ascii_case("true")
 | 
					                                round_trip_ok_copy
 | 
				
			||||||
                                            || data.eq_ignore_ascii_case("1");
 | 
					                                    .store(true, std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
                                        println!("Received stay alive with value {}", value);
 | 
					                            } else if topic.eq(stay_alive_topic_copy.as_str()) {
 | 
				
			||||||
                                        STAY_ALIVE
 | 
					                                let value = data.eq_ignore_ascii_case("true")
 | 
				
			||||||
                                            .store(value, std::sync::atomic::Ordering::Relaxed);
 | 
					                                    || data.eq_ignore_ascii_case("1");
 | 
				
			||||||
                                    } else {
 | 
					                                println!("Received stay alive with value {}", value);
 | 
				
			||||||
                                        println!("Unknown topic recieved {}", topic);
 | 
					                                STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
                                    }
 | 
					                            } else {
 | 
				
			||||||
                                }
 | 
					                                println!("Unknown topic recieved {}", topic);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            _ => {}
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    _ => {}
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            })?;
 | 
					            })?;
 | 
				
			||||||
        //subscribe to roundtrip
 | 
					        //subscribe to roundtrip
 | 
				
			||||||
@@ -698,15 +693,111 @@ impl PlantCtrlBoardInteraction for PlantCtrlBoard<'_> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        bail!("Mqtt did not complete roundtrip in time");
 | 
					        bail!("Mqtt did not complete roundtrip in time");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn mqtt_publish(&mut self, config: &Config, subtopic: &str, message: &[u8]) -> Result<()> {
 | 
				
			||||||
 | 
					        if !subtopic.starts_with("/") {
 | 
				
			||||||
 | 
					            println!("Subtopic without / at start {}", subtopic);
 | 
				
			||||||
 | 
					            bail!("Subtopic without / at start {}", subtopic);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if subtopic.len() > 192 {
 | 
				
			||||||
 | 
					            println!("Subtopic exceeds 192 chars {}", subtopic);
 | 
				
			||||||
 | 
					            bail!("Subtopic exceeds 192 chars {}", subtopic);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if self.mqtt_client.is_none() {
 | 
				
			||||||
 | 
					            println!("Not connected to mqtt");
 | 
				
			||||||
 | 
					            bail!("Not connected to mqtt");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let client = self.mqtt_client.as_mut().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut full_topic: heapless::String<256> = heapless::String::new();
 | 
				
			||||||
 | 
					        if full_topic.push_str(&config.base_topic).is_err() {
 | 
				
			||||||
 | 
					            println!("Some error assembling full_topic 1");
 | 
				
			||||||
 | 
					            bail!("Some error assembling full_topic 1")
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        if full_topic.push_str(subtopic).is_err() {
 | 
				
			||||||
 | 
					            println!("Some error assembling full_topic 2");
 | 
				
			||||||
 | 
					            bail!("Some error assembling full_topic 2")
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        client.publish(
 | 
				
			||||||
 | 
					            &full_topic,
 | 
				
			||||||
 | 
					            embedded_svc::mqtt::client::QoS::ExactlyOnce,
 | 
				
			||||||
 | 
					            true,
 | 
				
			||||||
 | 
					            message,
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn state_charge_percent(&mut self) -> Result<u8> {
 | 
				
			||||||
 | 
					        match self.battery_driver.state_of_charge() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading SoC {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn remaining_milli_ampere_hour(&mut self) -> Result<u16> {
 | 
				
			||||||
 | 
					        match self.battery_driver.remaining_capacity() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading Remaining Capacity {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn max_milli_ampere_hour(&mut self) -> Result<u16> {
 | 
				
			||||||
 | 
					        match self.battery_driver.full_charge_capacity() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading Full Charge Capacity {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn design_milli_ampere_hour(&mut self) -> Result<u16> {
 | 
				
			||||||
 | 
					        match self.battery_driver.design_capacity() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading Design Capacity {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn voltage_milli_volt(&mut self) -> Result<u16> {
 | 
				
			||||||
 | 
					        return match self.battery_driver.voltage() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading voltage {:?}", err),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn average_current_milli_ampere(&mut self) -> Result<i16> {
 | 
				
			||||||
 | 
					        match self.battery_driver.average_current() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading Average Current {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn cycle_count(&mut self) -> Result<u16> {
 | 
				
			||||||
 | 
					        match self.battery_driver.cycle_count() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading Cycle Count {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn state_health_percent(&mut self) -> Result<u8> {
 | 
				
			||||||
 | 
					        match self.battery_driver.state_of_health() {
 | 
				
			||||||
 | 
					            OkStd(r) => Ok(r as u8),
 | 
				
			||||||
 | 
					            Err(err) => bail!("Error reading State of Health {:?}", err),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn print_battery(
 | 
					fn print_battery(
 | 
				
			||||||
    battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>,
 | 
					    battery_driver: &mut Bq34z100g1Driver<I2cDriver, Delay>,
 | 
				
			||||||
) -> Result<(), Bq34Z100Error<I2cError>> {
 | 
					) -> Result<(), Bq34Z100Error<I2cError>> {
 | 
				
			||||||
    let fwversion = battery_driver.fw_version()?;
 | 
					    let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Firmeware {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    println!("fw version is {}", fwversion);
 | 
					    println!("fw version is {}", fwversion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let design_capacity = battery_driver.design_capacity()?;
 | 
					    let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Design capacity {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    println!("Design Capacity {}", design_capacity);
 | 
					    println!("Design Capacity {}", design_capacity);
 | 
				
			||||||
    if design_capacity == 1000 {
 | 
					    if design_capacity == 1000 {
 | 
				
			||||||
        println!("Still stock configuring battery, readouts are likely to be wrong!");
 | 
					        println!("Still stock configuring battery, readouts are likely to be wrong!");
 | 
				
			||||||
@@ -715,15 +806,39 @@ fn print_battery(
 | 
				
			|||||||
    let flags = battery_driver.get_flags_decoded()?;
 | 
					    let flags = battery_driver.get_flags_decoded()?;
 | 
				
			||||||
    println!("Flags {:?}", flags);
 | 
					    println!("Flags {:?}", flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let chem_id = battery_driver.chem_id()?;
 | 
					    let chem_id = battery_driver.chem_id().unwrap_or_else(|e| {
 | 
				
			||||||
    let bat_temp = battery_driver.internal_temperature()?;
 | 
					        println!("Chemid {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Bat Temp {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius();
 | 
					    let temp_c = Temperature::from_kelvin(bat_temp as f64 / 10_f64).as_celsius();
 | 
				
			||||||
    let voltage = battery_driver.voltage()?;
 | 
					    let voltage = battery_driver.voltage().unwrap_or_else(|e| {
 | 
				
			||||||
    let current = battery_driver.current()?;
 | 
					        println!("Bat volt {:?}", e);
 | 
				
			||||||
    let state = battery_driver.state_of_charge()?;
 | 
					        0
 | 
				
			||||||
    let charge_voltage = battery_driver.charge_voltage()?;
 | 
					    });
 | 
				
			||||||
    let charge_current = battery_driver.charge_current()?;
 | 
					    let current = battery_driver.current().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Bat current {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    let state = battery_driver.state_of_charge().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Bat Soc {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Bat Charge Volt {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    let charge_current = battery_driver.charge_current().unwrap_or_else(|e| {
 | 
				
			||||||
 | 
					        println!("Bat Charge Current {:?}", e);
 | 
				
			||||||
 | 
					        0
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
 | 
					    println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current);
 | 
				
			||||||
 | 
					    let _ = battery_driver.unsealed();
 | 
				
			||||||
 | 
					    let _ = battery_driver.it_enable();
 | 
				
			||||||
    return Result::Ok(());
 | 
					    return Result::Ok(());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -741,8 +856,8 @@ impl CreatePlantHal<'_> for PlantHal {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap();
 | 
					        let driver = I2cDriver::new(i2c, sda, scl, &config).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //let i2c_port = driver.port();
 | 
					        let i2c_port = driver.port();
 | 
				
			||||||
        //esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
 | 
					        esp!(unsafe { esp_idf_sys::i2c_set_timeout(i2c_port, 1048000) }).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut battery_driver: Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver {
 | 
					        let mut battery_driver: Bq34z100g1Driver<I2cDriver, Delay> = Bq34z100g1Driver {
 | 
				
			||||||
            i2c: driver,
 | 
					            i2c: driver,
 | 
				
			||||||
@@ -789,6 +904,30 @@ impl CreatePlantHal<'_> for PlantHal {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            println!("Keeping RTC store");
 | 
					            println!("Keeping RTC store");
 | 
				
			||||||
 | 
					            unsafe {
 | 
				
			||||||
 | 
					                println!(
 | 
				
			||||||
 | 
					                    "Current low voltage detection is {:?}",
 | 
				
			||||||
 | 
					                    LOW_VOLTAGE_DETECTED
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                for i in 0..PLANT_COUNT {
 | 
				
			||||||
 | 
					                    let smaller_time = LAST_WATERING_TIMESTAMP[i];
 | 
				
			||||||
 | 
					                    let local_time = NaiveDateTime::from_timestamp_millis(smaller_time)
 | 
				
			||||||
 | 
					                        .ok_or(anyhow!("could not convert timestamp"))?;
 | 
				
			||||||
 | 
					                    let utc_time = local_time.and_utc();
 | 
				
			||||||
 | 
					                    let europe_time = utc_time.with_timezone(&Berlin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    println!(
 | 
				
			||||||
 | 
					                        "LAST_WATERING_TIMESTAMP[{}] = {} as europe {}",
 | 
				
			||||||
 | 
					                        i, LAST_WATERING_TIMESTAMP[i], europe_time
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                for i in 0..PLANT_COUNT {
 | 
				
			||||||
 | 
					                    println!(
 | 
				
			||||||
 | 
					                        "CONSECUTIVE_WATERING_PLANT[{}] = {}",
 | 
				
			||||||
 | 
					                        i, CONSECUTIVE_WATERING_PLANT[i]
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut counter_unit1 = PcntDriver::new(
 | 
					        let mut counter_unit1 = PcntDriver::new(
 | 
				
			||||||
@@ -806,10 +945,10 @@ impl CreatePlantHal<'_> for PlantHal {
 | 
				
			|||||||
            PinIndex::Pin0,
 | 
					            PinIndex::Pin0,
 | 
				
			||||||
            PinIndex::Pin1,
 | 
					            PinIndex::Pin1,
 | 
				
			||||||
            &PcntChannelConfig {
 | 
					            &PcntChannelConfig {
 | 
				
			||||||
                lctrl_mode: PcntControlMode::Reverse,
 | 
					                lctrl_mode: PcntControlMode::Keep,
 | 
				
			||||||
                hctrl_mode: PcntControlMode::Keep,
 | 
					                hctrl_mode: PcntControlMode::Keep,
 | 
				
			||||||
                pos_mode: PcntCountMode::Decrement,
 | 
					                pos_mode: PcntCountMode::Increment,
 | 
				
			||||||
                neg_mode: PcntCountMode::Increment,
 | 
					                neg_mode: PcntCountMode::Hold,
 | 
				
			||||||
                counter_h_lim: i16::MAX,
 | 
					                counter_h_lim: i16::MAX,
 | 
				
			||||||
                counter_l_lim: 0,
 | 
					                counter_l_lim: 0,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@@ -827,8 +966,6 @@ impl CreatePlantHal<'_> for PlantHal {
 | 
				
			|||||||
        let nvs = EspDefaultNvsPartition::take()?;
 | 
					        let nvs = EspDefaultNvsPartition::take()?;
 | 
				
			||||||
        let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
 | 
					        let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let last_watering_timestamp = Mutex::new(unsafe { LAST_WATERING_TIMESTAMP });
 | 
					 | 
				
			||||||
        let consecutive_watering_plant = Mutex::new(unsafe { CONSECUTIVE_WATERING_PLANT });
 | 
					 | 
				
			||||||
        let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED });
 | 
					        let low_voltage_detected = Mutex::new(unsafe { LOW_VOLTAGE_DETECTED });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let adc_config = esp_idf_hal::adc::config::Config {
 | 
					        let adc_config = esp_idf_hal::adc::config::Config {
 | 
				
			||||||
@@ -867,8 +1004,6 @@ impl CreatePlantHal<'_> for PlantHal {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        let rv = Mutex::new(PlantCtrlBoard {
 | 
					        let rv = Mutex::new(PlantCtrlBoard {
 | 
				
			||||||
            shift_register,
 | 
					            shift_register,
 | 
				
			||||||
            last_watering_timestamp,
 | 
					 | 
				
			||||||
            consecutive_watering_plant,
 | 
					 | 
				
			||||||
            low_voltage_detected,
 | 
					            low_voltage_detected,
 | 
				
			||||||
            tank_driver,
 | 
					            tank_driver,
 | 
				
			||||||
            tank_channel,
 | 
					            tank_channel,
 | 
				
			||||||
@@ -882,6 +1017,7 @@ impl CreatePlantHal<'_> for PlantHal {
 | 
				
			|||||||
            signal_counter: counter_unit1,
 | 
					            signal_counter: counter_unit1,
 | 
				
			||||||
            wifi_driver,
 | 
					            wifi_driver,
 | 
				
			||||||
            mqtt_client: None,
 | 
					            mqtt_client: None,
 | 
				
			||||||
 | 
					            battery_driver,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        Ok(rv)
 | 
					        Ok(rv)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use core::cell::RefCell;
 | 
					use core::cell::RefCell;
 | 
				
			||||||
use core::mem::{self, MaybeUninit};
 | 
					use core::mem::{self, MaybeUninit};
 | 
				
			||||||
 | 
					use std::convert::Infallible;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::hal::digital::v2::OutputPin;
 | 
					use hal::digital::OutputPin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait ShiftRegisterInternal {
 | 
					trait ShiftRegisterInternal {
 | 
				
			||||||
    fn update(&self, index: usize, command: bool) -> Result<(), ()>;
 | 
					    fn update(&self, index: usize, command: bool) -> Result<(), ()>;
 | 
				
			||||||
@@ -24,16 +25,18 @@ impl<'a> ShiftRegisterPin<'a> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl OutputPin for ShiftRegisterPin<'_> {
 | 
					impl embedded_hal::digital::ErrorType for ShiftRegisterPin<'_> {
 | 
				
			||||||
    type Error = ();
 | 
					    type Error = Infallible;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_low(&mut self) -> Result<(), Self::Error> {
 | 
					impl OutputPin for ShiftRegisterPin<'_> {
 | 
				
			||||||
        self.shift_register.update(self.index, false)?;
 | 
					    fn set_low(&mut self) -> Result<(), Infallible> {
 | 
				
			||||||
 | 
					        self.shift_register.update(self.index, false).unwrap();
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn set_high(&mut self) -> Result<(), Self::Error> {
 | 
					    fn set_high(&mut self) -> Result<(), Infallible> {
 | 
				
			||||||
        self.shift_register.update(self.index, true)?;
 | 
					        self.shift_register.update(self.index, true).unwrap();
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,10 @@ use std::{
 | 
				
			|||||||
    sync::{atomic::AtomicBool, Arc},
 | 
					    sync::{atomic::AtomicBool, Arc},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::BOARD_ACCESS;
 | 
					use crate::{espota::OtaUpdate, BOARD_ACCESS};
 | 
				
			||||||
 | 
					use core::result::Result::Ok;
 | 
				
			||||||
use embedded_svc::http::Method;
 | 
					use embedded_svc::http::Method;
 | 
				
			||||||
use esp_idf_svc::http::server::{Configuration, EspHttpServer};
 | 
					use esp_idf_svc::http::server::{Configuration, EspHttpServer};
 | 
				
			||||||
use esp_ota::OtaUpdate;
 | 
					 | 
				
			||||||
use heapless::String;
 | 
					use heapless::String;
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,7 +28,7 @@ pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>>
 | 
				
			|||||||
        .fn_handler("/", Method::Get, move |request| {
 | 
					        .fn_handler("/", Method::Get, move |request| {
 | 
				
			||||||
            let mut response = request.into_ok_response()?;
 | 
					            let mut response = request.into_ok_response()?;
 | 
				
			||||||
            response.write(include_bytes!("initial_config.html"))?;
 | 
					            response.write(include_bytes!("initial_config.html"))?;
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,7 +48,7 @@ pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>>
 | 
				
			|||||||
                    response.write(ssid_json.as_bytes())?;
 | 
					                    response.write(ssid_json.as_bytes())?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,7 +62,7 @@ pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>>
 | 
				
			|||||||
                request
 | 
					                request
 | 
				
			||||||
                    .into_status_response(500)?
 | 
					                    .into_status_response(500)?
 | 
				
			||||||
                    .write(error_text.as_bytes())?;
 | 
					                    .write(error_text.as_bytes())?;
 | 
				
			||||||
                return Ok(());
 | 
					                return anyhow::Ok(());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let actual_data = &buf[0..read.unwrap()];
 | 
					            let actual_data = &buf[0..read.unwrap()];
 | 
				
			||||||
            println!("raw {:?}", actual_data);
 | 
					            println!("raw {:?}", actual_data);
 | 
				
			||||||
@@ -75,14 +75,14 @@ pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>>
 | 
				
			|||||||
                request
 | 
					                request
 | 
				
			||||||
                    .into_status_response(500)?
 | 
					                    .into_status_response(500)?
 | 
				
			||||||
                    .write(error_text.as_bytes())?;
 | 
					                    .write(error_text.as_bytes())?;
 | 
				
			||||||
                return Ok(());
 | 
					                return anyhow::Ok(());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let mut board = BOARD_ACCESS.lock().unwrap();
 | 
					            let mut board = BOARD_ACCESS.lock().unwrap();
 | 
				
			||||||
            board.set_wifi(&wifi_config.unwrap())?;
 | 
					            board.set_wifi(&wifi_config.unwrap())?;
 | 
				
			||||||
            let mut response = request.into_status_response(202)?;
 | 
					            let mut response = request.into_status_response(202)?;
 | 
				
			||||||
            response.write("saved".as_bytes())?;
 | 
					            response.write("saved".as_bytes())?;
 | 
				
			||||||
            reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
 | 
					            reboot_now.store(true, std::sync::atomic::Ordering::Relaxed);
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -90,7 +90,7 @@ pub fn httpd_initial(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>>
 | 
				
			|||||||
        .fn_handler("/boardtest", Method::Post, move |_| {
 | 
					        .fn_handler("/boardtest", Method::Post, move |_| {
 | 
				
			||||||
            let mut board = BOARD_ACCESS.lock().unwrap();
 | 
					            let mut board = BOARD_ACCESS.lock().unwrap();
 | 
				
			||||||
            board.test()?;
 | 
					            board.test()?;
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -104,14 +104,14 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
 | 
				
			|||||||
        .fn_handler("/", Method::Get, move |request| {
 | 
					        .fn_handler("/", Method::Get, move |request| {
 | 
				
			||||||
            let mut response = request.into_ok_response()?;
 | 
					            let mut response = request.into_ok_response()?;
 | 
				
			||||||
            response.write(include_bytes!("config.html"))?;
 | 
					            response.write(include_bytes!("config.html"))?;
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    server
 | 
					    server
 | 
				
			||||||
        .fn_handler("/get_config", Method::Get, move |request| {
 | 
					        .fn_handler("/get_config", Method::Get, move |request| {
 | 
				
			||||||
            let mut response = request.into_ok_response()?;
 | 
					            let mut response = request.into_ok_response()?;
 | 
				
			||||||
            let mut board = BOARD_ACCESS.lock()?;
 | 
					            let mut board = BOARD_ACCESS.lock().unwrap();
 | 
				
			||||||
            match board.get_config() {
 | 
					            match board.get_config() {
 | 
				
			||||||
                Ok(config) => {
 | 
					                Ok(config) => {
 | 
				
			||||||
                    let config_json = serde_json::to_string(&config)?;
 | 
					                    let config_json = serde_json::to_string(&config)?;
 | 
				
			||||||
@@ -122,7 +122,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
 | 
				
			|||||||
                    response.write(config_json.as_bytes())?;
 | 
					                    response.write(config_json.as_bytes())?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -136,7 +136,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
 | 
				
			|||||||
                request
 | 
					                request
 | 
				
			||||||
                    .into_status_response(500)?
 | 
					                    .into_status_response(500)?
 | 
				
			||||||
                    .write(error_text.as_bytes())?;
 | 
					                    .write(error_text.as_bytes())?;
 | 
				
			||||||
                return Ok(());
 | 
					                return anyhow::Ok(());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let actual_data = &buf[0..read.unwrap()];
 | 
					            let actual_data = &buf[0..read.unwrap()];
 | 
				
			||||||
            println!("Raw data {}", from_utf8(actual_data).unwrap());
 | 
					            println!("Raw data {}", from_utf8(actual_data).unwrap());
 | 
				
			||||||
@@ -171,21 +171,21 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
 | 
				
			|||||||
        .fn_handler("/version", Method::Get, |request| {
 | 
					        .fn_handler("/version", Method::Get, |request| {
 | 
				
			||||||
            let mut response = request.into_ok_response()?;
 | 
					            let mut response = request.into_ok_response()?;
 | 
				
			||||||
            response.write(env!("VERGEN_GIT_DESCRIBE").as_bytes())?;
 | 
					            response.write(env!("VERGEN_GIT_DESCRIBE").as_bytes())?;
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
    server
 | 
					    server
 | 
				
			||||||
        .fn_handler("/bundle.js", Method::Get, |request| {
 | 
					        .fn_handler("/bundle.js", Method::Get, |request| {
 | 
				
			||||||
            let mut response = request.into_ok_response()?;
 | 
					            let mut response = request.into_ok_response()?;
 | 
				
			||||||
            response.write(include_bytes!("bundle.js"))?;
 | 
					            response.write(include_bytes!("bundle.js"))?;
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
    server
 | 
					    server
 | 
				
			||||||
        .fn_handler("/favicon.ico", Method::Get, |request| {
 | 
					        .fn_handler("/favicon.ico", Method::Get, |request| {
 | 
				
			||||||
            let mut response = request.into_ok_response()?;
 | 
					            let mut response = request.into_ok_response()?;
 | 
				
			||||||
            response.write(include_bytes!("favicon.ico"))?;
 | 
					            response.write(include_bytes!("favicon.ico"))?;
 | 
				
			||||||
            Ok(())
 | 
					            anyhow::Ok(())
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
    server
 | 
					    server
 | 
				
			||||||
@@ -196,7 +196,7 @@ pub fn shared() -> Box<EspHttpServer<'static>> {
 | 
				
			|||||||
                request
 | 
					                request
 | 
				
			||||||
                    .into_status_response(500)?
 | 
					                    .into_status_response(500)?
 | 
				
			||||||
                    .write(error_text.as_bytes())?;
 | 
					                    .write(error_text.as_bytes())?;
 | 
				
			||||||
                return Ok(());
 | 
					                return anyhow::Ok(());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let mut ota = ota.unwrap();
 | 
					            let mut ota = ota.unwrap();
 | 
				
			||||||
            println!("start ota");
 | 
					            println!("start ota");
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user