13 Commits

25 changed files with 3258 additions and 2573 deletions

View File

@@ -460,6 +460,7 @@
"single_global_label": "ignore", "single_global_label": "ignore",
"unannotated": "error", "unannotated": "error",
"unconnected_wire_endpoint": "warning", "unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error", "unit_value_mismatch": "error",
"unresolved_variable": "error", "unresolved_variable": "error",
"wire_dangling": "error" "wire_dangling": "error"

View File

@@ -1,26 +1,29 @@
[build] [build]
#target = "xtensa-esp32-espidf" rustflags = [
target = "riscv32imac-esp-espidf" # Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.)
# NOTE: May negatively impact performance of produced code
"-C", "force-frame-pointers",
"-Z", "stack-protector=all",
"-C", "link-arg=-Tlinkall.x",
]
[target.riscv32imac-esp-espidf] target = "riscv32imac-unknown-none-elf"
linker = "ldproxy"
[target.riscv32imac-unknown-none-elf]
runner = "espflash flash --monitor --chip esp32c6 --baud 921600 --partition-table partitions.csv"
#runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl #runner = "espflash flash --monitor --baud 921600 --partition-table partitions.csv -b no-reset" # Select this runner in case of usb ttl
runner = "espflash flash --monitor" #runner = "espflash flash --monitor"
#runner = "cargo runner" #runner = "cargo runner"
#runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # create upgrade image file for webupload #runner = "espflash flash --monitor --partition-table partitions.csv -b no-reset" # create upgrade image file for webupload
# runner = espflash erase-parts otadata //ensure flash is clean # runner = espflash erase-parts otadata //ensure flash is clean
rustflags = ["--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[unstable]
build-std = ["std", "panic_abort"]
[env] [env]
MCU = "esp32c6"
# Note: this variable is not used by the pio builder (`cargo build --features pio`)
ESP_IDF_VERSION = "v5.2.1"
CHRONO_TZ_TIMEZONE_FILTER = "UTC|America/New_York|America/Chicago|America/Los_Angeles|Europe/London|Europe/Berlin|Europe/Paris|Asia/Tokyo|Asia/Shanghai|Asia/Kolkata|Australia/Sydney|America/Sao_Paulo|Africa/Johannesburg|Asia/Dubai|Pacific/Auckland" CHRONO_TZ_TIMEZONE_FILTER = "UTC|America/New_York|America/Chicago|America/Los_Angeles|Europe/London|Europe/Berlin|Europe/Paris|Asia/Tokyo|Asia/Shanghai|Asia/Kolkata|Australia/Sydney|America/Sao_Paulo|Africa/Johannesburg|Asia/Dubai|Pacific/Auckland"
CARGO_WORKSPACE_DIR = { value = "", relative = true } CARGO_WORKSPACE_DIR = { value = "", relative = true }
RUST_BACKTRACE = "full" ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]

View File

@@ -1,31 +1,17 @@
[package] [package]
name = "plant-ctrl2"
version = "0.1.0"
authors = ["Empire Phoenix"]
edition = "2021" edition = "2021"
resolver = "2" name = "plant-ctrl2"
#rust-version = "1.71" rust-version = "1.86"
version = "0.1.0"
[profile.dev] # Explicitly configure the binary target and disable building it as a test/bench.
# Explicitly disable LTO which the Xtensa codegen backend has issues [[bin]]
lto = false name = "plant-ctrl2"
strip = false path = "src/main.rs"
debug = true # Prevent IDEs/Cargo from trying to compile a test harness for this no_std binary.
overflow-checks = true test = false
panic = "abort" bench = false
incremental = true doc = false
opt-level = 2
[profile.release]
# Explicitly disable LTO which the Xtensa codegen backend has issues
lto = false
strip = true
debug = false
overflow-checks = false
panic = "abort"
incremental = true
opt-level = "z"
[package.metadata.cargo_runner] [package.metadata.cargo_runner]
# The string `$TARGET_FILE` will be replaced with the path from cargo. # The string `$TARGET_FILE` will be replaced with the path from cargo.
@@ -40,74 +26,143 @@ command = [
"partitions.csv" "partitions.csv"
] ]
[profile.dev]
lto = true
strip = false
debug = false
overflow-checks = true
panic = "abort"
incremental = true
opt-level = 3
[profile.release]
# Explicitly disable LTO which the Xtensa codegen backend has issues
lto = true
strip = true
debug = false
overflow-checks = true
panic = "abort"
incremental = true
opt-level = 3
[package.metadata.espflash] [package.metadata.espflash]
partition_table = "partitions.csv" partition_table = "partitions.csv"
[features]
default = ["std", "esp-idf-svc/native"]
pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
alloc = ["esp-idf-svc/alloc"]
nightly = ["esp-idf-svc/nightly"]
experimental = ["esp-idf-svc/experimental"]
#embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
[dependencies] [dependencies]
#ESP stuff #ESP stuff
embedded-svc = { version = "0.28.1", features = ["experimental"] } esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32c6"] }
esp-idf-hal = "0.45.2" esp-hal = { version = "=1.0.0-rc.0", features = [
esp-idf-sys = { version = "0.36.1", features = ["binstart", "native"] } "esp32c6",
esp-idf-svc = { version = "0.51.0", default-features = false } "log-04",
"unstable",
"rt"
] }
log = "0.4.27"
embassy-net = { version = "0.7.0", features = [
"dhcpv4",
"log",
"medium-ethernet",
"tcp",
"udp",
] }
embedded-io = "0.6.1"
embedded-io-async = "0.6.1"
esp-alloc = "0.8.0"
esp-backtrace = { version = "0.17.0", features = [
"esp32c6",
"exception-handler",
"panic-handler",
"println",
"colors",
"custom-halt"
] }
esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] }
# for more networking protocol support see https://crates.io/crates/edge-net
embassy-executor = { version = "0.7.0", features = [
"log",
"task-arena-size-131072"
] }
embassy-time = { version = "0.5.0", features = ["log"] }
esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] }
esp-storage = { version = "0.7.0", features = ["esp32c6"] }
esp-wifi = { version = "0.15.0", features = [
"builtin-scheduler",
"esp-alloc",
"esp32c6",
"log-04",
"smoltcp",
"wifi",
] }
smoltcp = { version = "0.12.0", default-features = false, features = [
"alloc",
"log",
"medium-ethernet",
"multicast",
"proto-dhcpv4",
"proto-dns",
"proto-ipv4",
"socket-dns",
"socket-icmp",
"socket-raw",
"socket-tcp",
"socket-udp",
] }
#static_cell = "2.1.1"
embedded-hal = "1.0.0" embedded-hal = "1.0.0"
heapless = { version = "0.8", features = ["serde"] } heapless = { version = "0.8", features = ["serde"] }
embedded-hal-bus = { version = "0.3.0", features = ["std"] } embedded-hal-bus = { version = "0.3.0" }
#Hardware additional driver #Hardware additional driver
ds18b20 = "0.1.1" #ds18b20 = "0.1.1"
bq34z100 = { version = "0.3.0", features = ["flashstream"] } #bq34z100 = { version = "0.3.0", default-features = false }
one-wire-bus = "0.1.1" one-wire-bus = "0.1.1"
ds323x = "0.6.0" ds323x = "0.6.0"
#pure code dependencies #pure code dependencies
once_cell = "1.19.0" #once_cell = "1.19.0"
anyhow = { version = "1.0.75", features = ["std", "backtrace"] } anyhow = { version = "1.0.75", default-features = false }
strum = { version = "0.27.0", features = ["derive"] } #strum = { version = "0.27.0", default-feature = false, features = ["derive"] }
measurements = "0.11.0" measurements = "0.11.0"
#json #json
serde = { version = "1.0.192", features = ["derive"] } serde = { version = "1.0.219", features = ["derive", "alloc"], default-features = false }
serde_json = "1.0.108" serde_json = { version = "1.0.143", default-features = false, features = ["alloc"] }
#timezone #timezone
chrono = { version = "0.4.23", default-features = false, features = ["iana-time-zone", "alloc", "serde"] } chrono = { version = "0.4.42", default-features = false, features = ["iana-time-zone", "alloc", "serde"] }
chrono-tz = { version = "0.10.3", default-features = false, features = ["filter-by-regex"] } chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-by-regex"] }
eeprom24x = "0.7.2" eeprom24x = "0.7.2"
url = "2.5.3" #url = "2.5.3"
crc = "3.2.1" crc = "3.2.1"
bincode = "2.0.1" bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"] }
ringbuffer = "0.15.0" ringbuffer = "0.15.0"
text-template = "0.1.0"
strum_macros = "0.27.0" strum_macros = "0.27.0"
esp-ota = { version = "0.2.2", features = ["log"] } #esp-ota = { version = "0.2.2", features = ["log"] }
unit-enum = "1.4.1" unit-enum = "1.4.1"
pca9535 = { version = "2.0.0", features = ["std"] } pca9535 = { version = "2.0.0" }
ina219 = { version = "0.2.0", features = ["std"] } ina219 = { version = "0.2.0" }
embedded-storage = "=0.3.1" embedded-storage = "=0.3.1"
ekv = "1.0.0" ekv = "1.0.0"
embedded-can = "0.4.1" embedded-can = "0.4.1"
portable-atomic = "1.11.1"
embassy-sync = { version = "0.7.2", features = ["log"] }
async-trait = "0.1.89"
bq34z100 = { version = "0.4.0", default-features = false }
edge-dhcp = "0.6.0"
edge-nal = "0.5.0"
edge-nal-embassy = "0.6.0"
static_cell = "2.1.1"
cfg-if = "1.0.3"
edge-http = { version = "0.6.1", features = ["log"] }
[patch.crates-io] [patch.crates-io]
#esp-idf-hal = { git = "https://github.com/esp-rs/esp-idf-hal.git" }
#esp-idf-hal = { git = "https://github.com/empirephoenix/esp-idf-hal.git" }
#esp-idf-sys = { git = "https://github.com/empirephoenix/esp-idf-sys.git" }
#esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys.git" }
#esp-idf-svc = { git = "https://github.com/esp-rs/esp-idf-svc.git" }
#bq34z100 = { path = "../../bq34z100_rust" } #bq34z100 = { path = "../../bq34z100_rust" }
[build-dependencies] [build-dependencies]
cc = "=1.1.30"
embuild = { version = "0.32.0", features = ["espidf"] }
vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] } vergen = { version = "8.2.6", features = ["build", "git", "gitcl"] }

View File

@@ -1,8 +1,62 @@
use std::process::Command; use std::process::Command;
use vergen::EmitBuilder; use vergen::EmitBuilder;
fn linker_be_nice() {
let args: Vec<String> = std::env::args().collect();
if args.len() > 1 {
let kind = &args[1];
let what = &args[2];
match kind.as_str() {
"undefined-symbol" => match what.as_str() {
"_defmt_timestamp" => {
eprintln!();
eprintln!("💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`");
eprintln!();
}
"_stack_start" => {
eprintln!();
eprintln!("💡 Is the linker script `linkall.x` missing?");
eprintln!();
}
"esp_wifi_preempt_enable"
| "esp_wifi_preempt_yield_task"
| "esp_wifi_preempt_task_create" => {
eprintln!();
eprintln!("💡 `esp-wifi` has no scheduler enabled. Make sure you have the `builtin-scheduler` feature enabled, or that you provide an external scheduler.");
eprintln!();
}
"embedded_test_linker_file_not_added_to_rustflags" => {
eprintln!();
eprintln!("💡 `embedded-test` not found - make sure `embedded-test.x` is added as a linker script for tests");
eprintln!();
}
_ => (),
},
// we don't have anything helpful for "missing-lib" yet
_ => {
std::process::exit(1);
}
}
std::process::exit(0);
}
println!(
"cargo:rustc-link-arg=--error-handling-script={}",
std::env::current_exe().unwrap().display()
);
}
fn main() { fn main() {
println!("cargo:rerun-if-changed=./src/src_webpack"); webpack();
linker_be_nice();
let _ = EmitBuilder::builder().all_git().all_build().emit();
}
fn webpack() {
//println!("cargo:rerun-if-changed=./src/src_webpack");
Command::new("rm") Command::new("rm")
.arg("./src/webserver/bundle.js") .arg("./src/webserver/bundle.js")
.output() .output()
@@ -64,7 +118,4 @@ fn main() {
.unwrap(); .unwrap();
} }
} }
embuild::espidf::sysenv::output();
let _ = EmitBuilder::builder().all_git().all_build().emit();
} }

View File

@@ -1,5 +1,6 @@
partition_table = "partitions.csv" format = "EspIdf"
[connection]
serial = "/dev/ttyACM0" [idf_format_args]
[flash] [flash]
size = "16MB" size = "16MB"

View File

@@ -10,14 +10,12 @@ use crate::{
}; };
use anyhow::{bail, Ok, Result}; use anyhow::{bail, Ok, Result};
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use esp_idf_hal::{
gpio::{AnyInputPin, IOPin, InputOutput, PinDriver, Pull},
pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex},
};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en};
use measurements::{Current, Voltage}; use measurements::{Current, Voltage};
use plant_ctrl2::sipo::ShiftRegister40; use plant_ctrl2::sipo::ShiftRegister40;
use std::result::Result::Ok as OkStd; use core::result::Result::Ok as OkStd;
use alloc::string::ToString;
use alloc::boxed::Box;
use esp_hall::gpio::Pull;
const PUMP8_BIT: usize = 0; const PUMP8_BIT: usize = 0;
const PUMP1_BIT: usize = 1; const PUMP1_BIT: usize = 1;
@@ -94,7 +92,7 @@ pub(crate) fn create_v3(
battery_monitor: Box<dyn BatteryInteraction + Send>, battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>, rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>> { ) -> Result<Box<dyn BoardInteraction<'static> + Send>> {
println!("Start v3"); log::info!("Start v3");
let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?; let mut clock = PinDriver::input_output(peripherals.gpio15.downgrade())?;
clock.set_pull(Pull::Floating)?; clock.set_pull(Pull::Floating)?;
let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?; let mut latch = PinDriver::input_output(peripherals.gpio3.downgrade())?;

View File

@@ -12,12 +12,6 @@ use crate::log::{log, LogMessage};
use anyhow::bail; use anyhow::bail;
use embedded_hal::digital::OutputPin; use embedded_hal::digital::OutputPin;
use embedded_hal_bus::i2c::MutexDevice; use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::gpio::{AnyInputPin, IOPin, InputOutput, Output, PinDriver, Pull};
use esp_idf_hal::i2c::I2cDriver;
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex,
};
use esp_idf_sys::{gpio_hold_dis, gpio_hold_en};
use ina219::address::{Address, Pin}; use ina219::address::{Address, Pin};
use ina219::calibration::UnCalibrated; use ina219::calibration::UnCalibrated;
use ina219::configuration::{Configuration, OperatingMode}; use ina219::configuration::{Configuration, OperatingMode};
@@ -27,7 +21,9 @@ use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use std::result::Result::Ok as OkStd; use std::result::Result::Ok as OkStd;
use embedded_can::Frame; use embedded_can::Frame;
use embedded_can::StandardId; use embedded_can::StandardId;
use esp_idf_hal::can; use alloc::string::ToString;
use alloc::boxed::Box;
use esp_hal::gpio::Pull;
pub enum Charger<'a> { pub enum Charger<'a> {
SolarMpptV1 { SolarMpptV1 {
@@ -52,7 +48,7 @@ impl Charger<'_> {
operating_mode: OperatingMode::PowerDown, operating_mode: OperatingMode::PowerDown,
}) })
.map_err(|e| { .map_err(|e| {
println!( log::info!(
"Error setting ina mppt configuration during deep sleep preparation{:?}", "Error setting ina mppt configuration during deep sleep preparation{:?}",
e e
); );
@@ -133,7 +129,7 @@ pub(crate) fn create_v4(
battery_monitor: Box<dyn BatteryInteraction + Send>, battery_monitor: Box<dyn BatteryInteraction + Send>,
rtc_module: Box<dyn RTCModuleInteraction + Send>, rtc_module: Box<dyn RTCModuleInteraction + Send>,
) -> anyhow::Result<Box<dyn BoardInteraction<'static> + Send + 'static>> { ) -> anyhow::Result<Box<dyn BoardInteraction<'static> + Send + 'static>> {
println!("Start v4"); log::info!("Start v4");
let mut awake = PinDriver::output(peripherals.gpio21.downgrade())?; let mut awake = PinDriver::output(peripherals.gpio21.downgrade())?;
awake.set_high()?; awake.set_high()?;
@@ -163,7 +159,7 @@ pub(crate) fn create_v4(
let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34); let mut sensor_expander = Pca9535Immediate::new(MutexDevice::new(&I2C_DRIVER), 34);
let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) { let sensor = match sensor_expander.pin_into_output(GPIOBank::Bank0, 0) {
Ok(_) => { Ok(_) => {
println!("SensorExpander answered"); log::info!("SensorExpander answered");
//pulse counter version //pulse counter version
let mut signal_counter = PcntDriver::new( let mut signal_counter = PcntDriver::new(
peripherals.pcnt0, peripherals.pcnt0,
@@ -200,7 +196,7 @@ pub(crate) fn create_v4(
} }
} }
Err(_) => { Err(_) => {
println!("Can bus mode "); log::info!("Can bus mode ");
let timing = can::config::Timing::B25K; let timing = can::config::Timing::B25K;
let config = can::config::Config::new().timing(timing); let config = can::config::Config::new().timing(timing);
let can = can::CanDriver::new(peripherals.can, peripherals.gpio0, peripherals.gpio2, &config).unwrap(); let can = can::CanDriver::new(peripherals.can, peripherals.gpio0, peripherals.gpio2, &config).unwrap();
@@ -211,7 +207,7 @@ pub(crate) fn create_v4(
can.transmit(&tx_frame, 1000).unwrap(); can.transmit(&tx_frame, 1000).unwrap();
if let Ok(rx_frame) = can.receive(1000) { if let Ok(rx_frame) = can.receive(1000) {
println!("rx {:}:", rx_frame); log::info!("rx {:}:", rx_frame);
} }
//can bus version //can bus version
SensorImpl::CanBus { SensorImpl::CanBus {
@@ -274,7 +270,7 @@ pub(crate) fn create_v4(
) { ) {
Ok(pump_ina) => Some(pump_ina), Ok(pump_ina) => Some(pump_ina),
Err(err) => { Err(err) => {
println!("Error creating pump ina: {:?}", err); log::info!("Error creating pump ina: {:?}", err);
None None
} }
}; };

View File

@@ -1,16 +1,17 @@
use crate::hal::Sensor;
use crate::log::{log, LogMessage};
use alloc::string::ToString;
use embedded_hal_bus::i2c::MutexDevice; use embedded_hal_bus::i2c::MutexDevice;
use esp_idf_hal::can::CanDriver; use esp_idf_hal::can::CanDriver;
use esp_idf_hal::delay::Delay; use esp_idf_hal::delay::Delay;
use esp_idf_hal::i2c::I2cDriver; use esp_idf_hal::i2c::I2cDriver;
use esp_idf_hal::pcnt::PcntDriver; use esp_idf_hal::pcnt::PcntDriver;
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
use crate::hal::Sensor;
use crate::log::{log, LogMessage};
const REPEAT_MOIST_MEASURE: usize = 10; const REPEAT_MOIST_MEASURE: usize = 10;
pub trait SensorInteraction { pub trait SensorInteraction {
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32>; async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32>;
} }
const MS0: u8 = 1_u8; const MS0: u8 = 1_u8;
@@ -21,19 +22,23 @@ const MS4: u8 = 2_u8;
const SENSOR_ON: u8 = 5_u8; const SENSOR_ON: u8 = 5_u8;
pub enum SensorImpl<'a> { pub enum SensorImpl<'a> {
PulseCounter{ PulseCounter {
signal_counter: PcntDriver<'a>, signal_counter: PcntDriver<'a>,
sensor_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>, sensor_expander: Pca9535Immediate<MutexDevice<'a, I2cDriver<'a>>>,
}, },
CanBus{ CanBus {
can: CanDriver<'a> can: CanDriver<'a>,
} },
} }
impl SensorInteraction for SensorImpl<'_> { impl SensorInteraction for SensorImpl<'_> {
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32> { fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> anyhow::Result<f32> {
match self { match self {
SensorImpl::PulseCounter { signal_counter, sensor_expander, .. } => { SensorImpl::PulseCounter {
signal_counter,
sensor_expander,
..
} => {
let mut results = [0_f32; REPEAT_MOIST_MEASURE]; let mut results = [0_f32; REPEAT_MOIST_MEASURE];
for repeat in 0..REPEAT_MOIST_MEASURE { for repeat in 0..REPEAT_MOIST_MEASURE {
signal_counter.counter_pause()?; signal_counter.counter_pause()?;
@@ -70,8 +75,7 @@ impl SensorInteraction for SensorImpl<'_> {
} }
sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?; sensor_expander.pin_set_low(GPIOBank::Bank0, MS4)?;
sensor_expander sensor_expander.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?;
.pin_set_high(GPIOBank::Bank0, SENSOR_ON)?;
let delay = Delay::new_default(); let delay = Delay::new_default();
let measurement = 100; // TODO what is this scaling factor? what is its purpose? let measurement = 100; // TODO what is this scaling factor? what is its purpose?
@@ -83,8 +87,7 @@ impl SensorInteraction for SensorImpl<'_> {
delay.delay_ms(measurement); delay.delay_ms(measurement);
signal_counter.counter_pause()?; signal_counter.counter_pause()?;
sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?; sensor_expander.pin_set_high(GPIOBank::Bank0, MS4)?;
sensor_expander sensor_expander.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?;
.pin_set_low(GPIOBank::Bank0, SENSOR_ON)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?; sensor_expander.pin_set_low(GPIOBank::Bank0, MS0)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?; sensor_expander.pin_set_low(GPIOBank::Bank0, MS1)?;
sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?; sensor_expander.pin_set_low(GPIOBank::Bank0, MS2)?;

176
rust/scratch/water.rs Normal file
View File

@@ -0,0 +1,176 @@
use crate::hal::TANK_MULTI_SAMPLE;
use anyhow::{anyhow, bail};
use ds18b20::Ds18b20;
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution, ADC1};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull};
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1,
};
use esp_idf_sys::EspError;
use one_wire_bus::OneWire;
pub struct TankSensor<'a> {
// one_wire_bus: OneWire<PinDriver<'a, AnyIOPin, InputOutput>>,
// tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>,
// tank_power: PinDriver<'a, AnyIOPin, InputOutput>,
// flow_counter: PcntDriver<'a>,
// delay: Delay,
}
impl<'a> TankSensor<'a> {
pub(crate) fn create(
// one_wire_pin: AnyIOPin,
// adc1: ADC1,
// gpio5: Gpio5,
// tank_power_pin: AnyIOPin,
// flow_sensor_pin: AnyIOPin,
// pcnt1: PCNT1,
) -> anyhow::Result<TankSensor<'a>> {
// let mut one_wire_pin =
// PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin");
// one_wire_pin
// .set_pull(Pull::Floating)
// .expect("Failed to set pull");
//
// let adc_config = AdcChannelConfig {
// attenuation: attenuation::DB_11,
// resolution: Resolution::Resolution12Bit,
// calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
// };
// let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC");
// let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config)
// .expect("Failed to configure ADC channel");
//
// let mut tank_power =
// PinDriver::input_output(tank_power_pin).expect("Failed to configure pin");
// tank_power
// .set_pull(Pull::Floating)
// .expect("Failed to set pull");
//
// let one_wire_bus =
// OneWire::new(one_wire_pin).expect("OneWire bus did not pull up after release");
//
// let mut flow_counter = PcntDriver::new(
// pcnt1,
// Some(flow_sensor_pin),
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// Option::<AnyInputPin>::None,
// )?;
//
// flow_counter.channel_config(
// PcntChannel::Channel1,
// PinIndex::Pin0,
// PinIndex::Pin1,
// &PcntChannelConfig {
// lctrl_mode: PcntControlMode::Keep,
// hctrl_mode: PcntControlMode::Keep,
// pos_mode: PcntCountMode::Increment,
// neg_mode: PcntCountMode::Hold,
// counter_h_lim: i16::MAX,
// counter_l_lim: 0,
// },
// )?;
//
// Ok(TankSensor {
// one_wire_bus,
// tank_channel,
// tank_power,
// flow_counter,
// delay: Default::default(),
// })
bail!("Tank sensor not implemented");
}
pub fn reset_flow_meter(&mut self) {
// self.flow_counter.counter_pause().unwrap();
// self.flow_counter.counter_clear().unwrap();
}
pub fn start_flow_meter(&mut self) {
//self.flow_counter.counter_resume().unwrap();
}
pub fn get_flow_meter_value(&mut self) -> i16 {
//self.flow_counter.get_counter_value().unwrap()
5_i16
}
pub fn stop_flow_meter(&mut self) -> i16 {
//self.flow_counter.counter_pause().unwrap();
self.get_flow_meter_value()
}
pub async fn water_temperature_c(&mut self) -> anyhow::Result<f32> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let water_temp: Result<f32, anyhow::Error> = loop {
let temp = self.single_temperature_c().await;
match &temp {
Ok(res) => {
log::info!("Water temp is {}", res);
break temp;
}
Err(err) => {
log::info!("Could not get water temp {} attempt {}", err, attempt)
}
}
if attempt == 5 {
break temp;
}
attempt += 1;
};
water_temp
}
async fn single_temperature_c(&mut self) -> anyhow::Result<f32> {
bail!("err");
// self.one_wire_bus
// .reset(&mut self.delay)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// let first = self.one_wire_bus.devices(false, &mut self.delay).next();
// if first.is_none() {
// bail!("Not found any one wire Ds18b20");
// }
// let device_address = first
// .unwrap()
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
//
// water_temp_sensor
// .start_temp_measurement(&mut self.one_wire_bus, &mut self.delay)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.delay);
// let sensor_data = water_temp_sensor
// .read_data(&mut self.one_wire_bus, &mut self.delay)
// .map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
// if sensor_data.temperature == 85_f32 {
// bail!("Ds18b20 dummy temperature returned");
// }
//anyhow::Ok(sensor_data.temperature / 10_f32)
Ok(13_f32)
}
pub async fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> {
// self.tank_power.set_high()?;
// //let stabilize
// self.delay.delay_ms(100);
//
// let mut store = [0_u16; TANK_MULTI_SAMPLE];
// for multisample in 0..TANK_MULTI_SAMPLE {
// let value = self.tank_channel.read()?;
// store[multisample] = value;
// }
// self.tank_power.set_low()?;
//
// store.sort();
// let median_mv = store[6] as f32 / 1000_f32;
let median_mv = 10_f32;
anyhow::Ok(median_mv)
}
}

View File

@@ -1,7 +1,8 @@
use alloc::string::String;
use core::str::FromStr;
use crate::hal::PLANT_COUNT; use crate::hal::PLANT_COUNT;
use crate::plant_state::PlantWateringMode; use crate::plant_state::PlantWateringMode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)] #[serde(default)]

View File

@@ -1,22 +1,27 @@
use anyhow::anyhow; use crate::hal::Box;
use bq34z100::{Bq34Z100Error, Bq34z100g1, Bq34z100g1Driver}; use alloc::string::String;
use embedded_hal_bus::i2c::MutexDevice; use async_trait::async_trait;
use esp_idf_hal::delay::Delay; use core::error::Error;
use esp_idf_hal::i2c::{I2cDriver, I2cError};
use measurements::Temperature;
use serde::Serialize; use serde::Serialize;
#[async_trait]
pub trait BatteryInteraction { pub trait BatteryInteraction {
fn state_charge_percent(&mut self) -> Result<f32, BatteryError>; async fn state_charge_percent(&mut self) -> Result<f32, BatteryError>;
fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>; async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError>;
fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>; async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError>;
fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>; async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError>;
fn cycle_count(&mut self) -> Result<u16, BatteryError>; async fn cycle_count(&mut self) -> Result<u16, BatteryError>;
fn state_health_percent(&mut self) -> Result<u16, BatteryError>; async fn state_health_percent(&mut self) -> Result<u16, BatteryError>;
fn bat_temperature(&mut self) -> Result<u16, BatteryError>; async fn bat_temperature(&mut self) -> Result<u16, BatteryError>;
fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>; async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError>;
}
impl From<BatteryError> for anyhow::Error {
fn from(err: BatteryError) -> Self {
anyhow::anyhow!(err)
}
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@@ -37,13 +42,13 @@ pub enum BatteryError {
CommunicationError(String), CommunicationError(String),
} }
impl From<Bq34Z100Error<esp_idf_hal::i2c::I2cError>> for BatteryError { // impl From<Bq34Z100Error<esp_idf_hal::i2c::I2cError>> for BatteryError {
fn from(err: Bq34Z100Error<esp_idf_hal::i2c::I2cError>) -> Self { // fn from(err: Bq34Z100Error<esp_idf_hal::i2c::I2cError>) -> Self {
BatteryError::CommunicationError( // BatteryError::CommunicationError(
anyhow!("failed to communicate with battery monitor: {:?}", err).to_string(), // anyhow!("failed to communicate with battery monitor: {:?}", err).to_string(),
) // )
} // }
} // }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub enum BatteryState { pub enum BatteryState {
@@ -53,45 +58,45 @@ pub enum BatteryState {
/// If no battery monitor is installed this implementation will be used /// If no battery monitor is installed this implementation will be used
pub struct NoBatteryMonitor {} pub struct NoBatteryMonitor {}
#[async_trait]
impl BatteryInteraction for NoBatteryMonitor { impl BatteryInteraction for NoBatteryMonitor {
fn state_charge_percent(&mut self) -> Result<f32, BatteryError> { async fn state_charge_percent(&mut self) -> Result<f32, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { async fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { async fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { async fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> { async fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> { async fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn cycle_count(&mut self) -> Result<u16, BatteryError> { async fn cycle_count(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn state_health_percent(&mut self) -> Result<u16, BatteryError> { async fn state_health_percent(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn bat_temperature(&mut self) -> Result<u16, BatteryError> { async fn bat_temperature(&mut self) -> Result<u16, BatteryError> {
Err(BatteryError::NoBatteryMonitor) Err(BatteryError::NoBatteryMonitor)
} }
fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> { async fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> {
Ok(BatteryState::Unknown) Ok(BatteryState::Unknown)
} }
} }
@@ -100,115 +105,115 @@ impl BatteryInteraction for NoBatteryMonitor {
#[allow(dead_code)] #[allow(dead_code)]
pub struct WchI2cSlave {} pub struct WchI2cSlave {}
pub struct BQ34Z100G1<'a> { // pub struct BQ34Z100G1<'a> {
pub battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>, // pub battery_driver: Bq34z100g1Driver<MutexDevice<'a, I2cDriver<'a>>, Delay>,
} // }
//
impl BatteryInteraction for BQ34Z100G1<'_> { // impl BatteryInteraction for BQ34Z100G1<'_> {
fn state_charge_percent(&mut self) -> Result<f32, BatteryError> { // fn state_charge_percent(&mut self) -> Result<f32, BatteryError> {
Ok(self.battery_driver.state_of_charge().map(f32::from)?) // Ok(self.battery_driver.state_of_charge().map(f32::from)?)
} // }
//
fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { // fn remaining_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.remaining_capacity()?) // Ok(self.battery_driver.remaining_capacity()?)
} // }
//
fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { // fn max_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.full_charge_capacity()?) // Ok(self.battery_driver.full_charge_capacity()?)
} // }
//
fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> { // fn design_milli_ampere_hour(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.design_capacity()?) // Ok(self.battery_driver.design_capacity()?)
} // }
//
fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> { // fn voltage_milli_volt(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.voltage()?) // Ok(self.battery_driver.voltage()?)
} // }
//
fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> { // fn average_current_milli_ampere(&mut self) -> Result<i16, BatteryError> {
Ok(self.battery_driver.average_current()?) // Ok(self.battery_driver.average_current()?)
} // }
//
fn cycle_count(&mut self) -> Result<u16, BatteryError> { // fn cycle_count(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.cycle_count()?) // Ok(self.battery_driver.cycle_count()?)
} // }
//
fn state_health_percent(&mut self) -> Result<u16, BatteryError> { // fn state_health_percent(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.state_of_health()?) // Ok(self.battery_driver.state_of_health()?)
} // }
//
fn bat_temperature(&mut self) -> Result<u16, BatteryError> { // fn bat_temperature(&mut self) -> Result<u16, BatteryError> {
Ok(self.battery_driver.temperature()?) // Ok(self.battery_driver.temperature()?)
} // }
//
fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> { // fn get_battery_state(&mut self) -> Result<BatteryState, BatteryError> {
Ok(BatteryState::Info(BatteryInfo { // Ok(BatteryState::Info(BatteryInfo {
voltage_milli_volt: self.voltage_milli_volt()?, // voltage_milli_volt: self.voltage_milli_volt()?,
average_current_milli_ampere: self.average_current_milli_ampere()?, // average_current_milli_ampere: self.average_current_milli_ampere()?,
cycle_count: self.cycle_count()?, // cycle_count: self.cycle_count()?,
design_milli_ampere_hour: self.design_milli_ampere_hour()?, // design_milli_ampere_hour: self.design_milli_ampere_hour()?,
remaining_milli_ampere_hour: self.remaining_milli_ampere_hour()?, // remaining_milli_ampere_hour: self.remaining_milli_ampere_hour()?,
state_of_charge: self.state_charge_percent()?, // state_of_charge: self.state_charge_percent()?,
state_of_health: self.state_health_percent()?, // state_of_health: self.state_health_percent()?,
temperature: self.bat_temperature()?, // temperature: self.bat_temperature()?,
})) // }))
} // }
} // }
//
pub fn print_battery_bq34z100( // pub fn print_battery_bq34z100(
battery_driver: &mut Bq34z100g1Driver<MutexDevice<I2cDriver<'_>>, Delay>, // battery_driver: &mut Bq34z100g1Driver<MutexDevice<I2cDriver<'_>>, Delay>,
) -> anyhow::Result<(), Bq34Z100Error<I2cError>> { // ) -> anyhow::Result<(), Bq34Z100Error<I2cError>> {
println!("Try communicating with battery"); // log::info!("Try communicating with battery");
let fwversion = battery_driver.fw_version().unwrap_or_else(|e| { // let fwversion = battery_driver.fw_version().unwrap_or_else(|e| {
println!("Firmware {:?}", e); // log::info!("Firmware {:?}", e);
0 // 0
}); // });
println!("fw version is {}", fwversion); // log::info!("fw version is {}", fwversion);
//
let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| { // let design_capacity = battery_driver.design_capacity().unwrap_or_else(|e| {
println!("Design capacity {:?}", e); // log::info!("Design capacity {:?}", e);
0 // 0
}); // });
println!("Design Capacity {}", design_capacity); // log::info!("Design Capacity {}", design_capacity);
if design_capacity == 1000 { // if design_capacity == 1000 {
println!("Still stock configuring battery, readouts are likely to be wrong!"); // log::info!("Still stock configuring battery, readouts are likely to be wrong!");
} // }
//
let flags = battery_driver.get_flags_decoded()?; // let flags = battery_driver.get_flags_decoded()?;
println!("Flags {:?}", flags); // log::info!("Flags {:?}", flags);
//
let chem_id = battery_driver.chem_id().unwrap_or_else(|e| { // let chem_id = battery_driver.chem_id().unwrap_or_else(|e| {
println!("Chemid {:?}", e); // log::info!("Chemid {:?}", e);
0 // 0
}); // });
//
let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| { // let bat_temp = battery_driver.internal_temperature().unwrap_or_else(|e| {
println!("Bat Temp {:?}", e); // log::info!("Bat Temp {:?}", e);
0 // 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().unwrap_or_else(|e| { // let voltage = battery_driver.voltage().unwrap_or_else(|e| {
println!("Bat volt {:?}", e); // log::info!("Bat volt {:?}", e);
0 // 0
}); // });
let current = battery_driver.current().unwrap_or_else(|e| { // let current = battery_driver.current().unwrap_or_else(|e| {
println!("Bat current {:?}", e); // log::info!("Bat current {:?}", e);
0 // 0
}); // });
let state = battery_driver.state_of_charge().unwrap_or_else(|e| { // let state = battery_driver.state_of_charge().unwrap_or_else(|e| {
println!("Bat Soc {:?}", e); // log::info!("Bat Soc {:?}", e);
0 // 0
}); // });
let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| { // let charge_voltage = battery_driver.charge_voltage().unwrap_or_else(|e| {
println!("Bat Charge Volt {:?}", e); // log::info!("Bat Charge Volt {:?}", e);
0 // 0
}); // });
let charge_current = battery_driver.charge_current().unwrap_or_else(|e| { // let charge_current = battery_driver.charge_current().unwrap_or_else(|e| {
println!("Bat Charge Current {:?}", e); // log::info!("Bat Charge Current {:?}", e);
0 // 0
}); // });
println!("ChemId: {} Current voltage {} and current {} with charge {}% and temp {} CVolt: {} CCur {}", chem_id, voltage, current, state, temp_c, charge_voltage, charge_current); // log::info!("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.unsealed();
let _ = battery_driver.it_enable(); // let _ = battery_driver.it_enable();
anyhow::Result::Ok(()) // anyhow::Result::Ok(())
} // }

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,22 @@
use crate::hal::esp::Esp; use crate::hal::esp::Esp;
use crate::hal::rtc::{BackupHeader, RTCModuleInteraction}; use crate::hal::rtc::{BackupHeader, RTCModuleInteraction};
use crate::hal::water::TankSensor; use alloc::vec::Vec;
use crate::hal::{deep_sleep, BoardInteraction, FreePeripherals, Sensor}; //use crate::hal::water::TankSensor;
use crate::alloc::boxed::Box;
use crate::hal::{BoardInteraction, FreePeripherals, Sensor};
use crate::{ use crate::{
config::PlantControllerConfig, config::PlantControllerConfig,
hal::battery::{BatteryInteraction, NoBatteryMonitor}, hal::battery::{BatteryInteraction, NoBatteryMonitor},
}; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use embedded_hal::digital::OutputPin; use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_idf_hal::gpio::{IOPin, Pull}; use log::info;
use esp_idf_hal::gpio::{InputOutput, PinDriver};
use measurements::{Current, Voltage}; use measurements::{Current, Voltage};
pub struct Initial<'a> { pub struct Initial<'a> {
pub(crate) general_fault: PinDriver<'a, esp_idf_hal::gpio::AnyIOPin, InputOutput>, pub(crate) general_fault: Output<'a>,
pub(crate) esp: Esp<'a>, pub(crate) esp: Esp<'a>,
pub(crate) config: PlantControllerConfig, pub(crate) config: PlantControllerConfig,
pub(crate) battery: Box<dyn BatteryInteraction + Send>, pub(crate) battery: Box<dyn BatteryInteraction + Send>,
@@ -23,42 +25,36 @@ pub struct Initial<'a> {
struct NoRTC {} struct NoRTC {}
#[async_trait]
impl RTCModuleInteraction for NoRTC { impl RTCModuleInteraction for NoRTC {
fn get_backup_info(&mut self) -> Result<BackupHeader> { async fn get_backup_info(&mut self) -> Result<BackupHeader> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn get_backup_config(&mut self) -> Result<Vec<u8>> { async fn get_backup_config(&mut self) -> Result<Vec<u8>> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> { async fn backup_config(&mut self, _bytes: &[u8]) -> Result<()> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> { async fn get_rtc_time(&mut self) -> Result<DateTime<Utc>> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> { async fn set_rtc_time(&mut self, _time: &DateTime<Utc>) -> Result<()> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
} }
pub(crate) fn create_initial_board( pub(crate) fn create_initial_board(
free_pins: FreePeripherals, free_pins: FreePeripherals<'static>,
fs_mount_error: bool,
config: PlantControllerConfig, config: PlantControllerConfig,
esp: Esp<'static>, esp: Esp<'static>,
) -> Result<Box<dyn BoardInteraction<'static> + Send>> { ) -> Result<Box<dyn BoardInteraction<'static> + Send>> {
println!("Start initial"); log::info!("Start initial");
let mut general_fault = PinDriver::input_output(free_pins.gpio6.downgrade())?; let general_fault = Output::new(free_pins.gpio23, Level::Low, OutputConfig::default());
general_fault.set_pull(Pull::Floating)?;
general_fault.set_low()?;
if fs_mount_error {
general_fault.set_high()?
}
let v = Initial { let v = Initial {
general_fault, general_fault,
config, config,
@@ -69,10 +65,11 @@ pub(crate) fn create_initial_board(
Ok(Box::new(v)) Ok(Box::new(v))
} }
#[async_trait]
impl<'a> BoardInteraction<'a> for Initial<'a> { impl<'a> BoardInteraction<'a> for Initial<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> { // fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>> {
None // None
} // }
fn get_esp(&mut self) -> &mut Esp<'a> { fn get_esp(&mut self) -> &mut Esp<'a> {
&mut self.esp &mut self.esp
@@ -94,8 +91,8 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! {
deep_sleep(duration_in_ms) self.esp.deep_sleep(duration_in_ms).await;
} }
fn is_day(&self) -> bool { fn is_day(&self) -> bool {
false false
@@ -104,41 +101,42 @@ impl<'a> BoardInteraction<'a> for Initial<'a> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> { async fn pump(&mut self, _plant: usize, _enable: bool) -> Result<()> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn pump_current(&mut self, _plant: usize) -> Result<Current> { async fn pump_current(&mut self, _plant: usize) -> Result<Current> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> { async fn fault(&mut self, _plant: usize, _enable: bool) -> Result<()> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn measure_moisture_hz(&mut self, _plant: usize, _sensor: Sensor) -> Result<f32> { async fn measure_moisture_hz(&mut self, _plant: usize, _sensor: Sensor) -> Result<f32> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn general_fault(&mut self, enable: bool) { async fn general_fault(&mut self, enable: bool) {
let _ = self.general_fault.set_state(enable.into()); self.general_fault.set_level(enable.into());
} }
fn test(&mut self) -> Result<()> { async fn test(&mut self) -> Result<()> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> { async fn set_config(&mut self, config: PlantControllerConfig) -> anyhow::Result<()> {
self.config = config; self.config = config;
self.esp.save_config(&self.config)?; //TODO
// self.esp.save_config(&self.config)?;
anyhow::Ok(()) anyhow::Ok(())
} }
fn get_mptt_voltage(&mut self) -> Result<Voltage> { async fn get_mptt_voltage(&mut self) -> Result<Voltage> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
fn get_mptt_current(&mut self) -> Result<Current> { async fn get_mptt_current(&mut self) -> Result<Current> {
bail!("Please configure board revision") bail!("Please configure board revision")
} }
} }

View File

@@ -1,80 +1,56 @@
pub(crate) mod battery; pub(crate) mod battery;
mod esp; pub mod esp;
mod initial_hal; mod initial_hal;
mod rtc; mod rtc;
mod v3_hal; //mod water;
mod v4_hal;
mod water;
mod v4_sensor;
use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; use crate::alloc::string::ToString;
use crate::hal::water::TankSensor; use crate::hal::rtc::RTCModuleInteraction;
use esp_hal::peripherals::Peripherals;
use esp_hal::peripherals::GPIO23;
use esp_hal::peripherals::GPIO6;
//use crate::hal::water::TankSensor;
use crate::{ use crate::{
config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig}, config::{BatteryBoardVersion, BoardVersion, PlantControllerConfig},
hal::{ hal::{
battery::{print_battery_bq34z100, BatteryInteraction, NoBatteryMonitor}, battery::{BatteryInteraction, NoBatteryMonitor},
esp::Esp, esp::Esp,
}, },
log::{log, LogMessage}, log::{log, LogMessage},
}; };
use alloc::boxed::Box;
use alloc::format;
use alloc::sync::Arc;
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use battery::BQ34Z100G1; use async_trait::async_trait;
use bq34z100::Bq34z100g1Driver; use embassy_executor::Spawner;
use ds323x::{DateTimeAccess, Ds323x}; //use battery::BQ34Z100G1;
use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; //use bq34z100::Bq34z100g1Driver;
use embedded_hal_bus::i2c::MutexDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use esp_idf_hal::pcnt::PCNT1; use esp_bootloader_esp_idf::partitions::{
use esp_idf_hal::{ AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry,
adc::ADC1,
delay::Delay,
gpio::{
Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18,
Gpio2, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3,
Gpio30, Gpio4, Gpio5, Gpio6, Gpio7, Gpio8, IOPin, PinDriver, Pull,
},
i2c::{APBTickType, I2cConfig, I2cDriver},
pcnt::PCNT0,
prelude::Peripherals,
reset::ResetReason,
units::FromValueType,
}; };
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition, wifi::EspWifi}; use esp_hal::clock::CpuClock;
use esp_idf_sys::{ use esp_hal::gpio::{Input, InputConfig, Io, Pull};
esp_deep_sleep, esp_restart, esp_sleep_enable_ext1_wakeup, use esp_println::println;
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
};
use esp_ota::mark_app_valid;
use measurements::{Current, Voltage}; use measurements::{Current, Voltage};
use once_cell::sync::Lazy;
use std::result::Result::Ok as OkStd; use embassy_sync::mutex::Mutex;
use std::sync::Mutex; use esp_alloc as _;
use std::time::Duration; use esp_backtrace as _;
use esp_idf_hal::can::CAN; use esp_bootloader_esp_idf::ota::Slot;
use esp_hal::rng::Rng;
use esp_hal::timer::timg::TimerGroup;
use esp_storage::FlashStorage;
use esp_wifi::{init, EspWifiController};
//Only support for 8 right now! //Only support for 8 right now!
pub const PLANT_COUNT: usize = 8; pub const PLANT_COUNT: usize = 8;
const TANK_MULTI_SAMPLE: usize = 11; const TANK_MULTI_SAMPLE: usize = 11;
pub static I2C_DRIVER: Lazy<Mutex<I2cDriver<'static>>> = Lazy::new(PlantHal::create_i2c); //pub static I2C_DRIVER: LazyLock<Mutex<CriticalSectionRawMutex,I2cDriver<'static>>> = LazyLock::new(PlantHal::create_i2c);
fn deep_sleep(duration_in_ms: u64) -> ! {
unsafe {
//if we don't do this here, we might just revert newly flashed firmware
mark_app_valid();
//allow early wakeup by pressing the boot button
if duration_in_ms == 0 {
esp_restart();
} else {
//configure gpio 1 to wakeup on low, reused boot button for this
esp_sleep_enable_ext1_wakeup(
0b10u64,
esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW,
);
esp_deep_sleep(duration_in_ms);
}
};
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Sensor { pub enum Sensor {
@@ -88,171 +64,258 @@ pub struct HAL<'a> {
pub board_hal: Box<dyn BoardInteraction<'a> + Send>, pub board_hal: Box<dyn BoardInteraction<'a> + Send>,
} }
#[async_trait]
pub trait BoardInteraction<'a> { pub trait BoardInteraction<'a> {
fn get_tank_sensor(&mut self) -> Option<&mut TankSensor<'a>>; //fn get_tank_sensor(&mut self) -> Option<&mut TankSensor>;
fn get_esp(&mut self) -> &mut Esp<'a>; fn get_esp(&mut self) -> &mut Esp<'a>;
fn get_config(&mut self) -> &PlantControllerConfig; fn get_config(&mut self) -> &PlantControllerConfig;
fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>; fn get_battery_monitor(&mut self) -> &mut Box<dyn BatteryInteraction + Send>;
fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>; fn get_rtc_module(&mut self) -> &mut Box<dyn RTCModuleInteraction + Send>;
fn set_charge_indicator(&mut self, charging: bool) -> Result<()>; fn set_charge_indicator(&mut self, charging: bool) -> Result<()>;
fn deep_sleep(&mut self, duration_in_ms: u64) -> !; async fn deep_sleep(&mut self, duration_in_ms: u64) -> !;
fn is_day(&self) -> bool; fn is_day(&self) -> bool;
//should be multsampled //should be multsampled
fn light(&mut self, enable: bool) -> Result<()>; fn light(&mut self, enable: bool) -> Result<()>;
fn pump(&mut self, plant: usize, enable: bool) -> Result<()>; async fn pump(&mut self, plant: usize, enable: bool) -> Result<()>;
fn pump_current(&mut self, plant: usize) -> Result<Current>; async fn pump_current(&mut self, plant: usize) -> Result<Current>;
fn fault(&mut self, plant: usize, enable: bool) -> Result<()>; async fn fault(&mut self, plant: usize, enable: bool) -> Result<()>;
fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>; async fn measure_moisture_hz(&mut self, plant: usize, sensor: Sensor) -> Result<f32>;
fn general_fault(&mut self, enable: bool); async fn general_fault(&mut self, enable: bool);
fn test(&mut self) -> Result<()>; async fn test(&mut self) -> Result<()>;
fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>; async fn set_config(&mut self, config: PlantControllerConfig) -> Result<()>;
fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>; async fn get_mptt_voltage(&mut self) -> anyhow::Result<Voltage>;
fn get_mptt_current(&mut self) -> anyhow::Result<Current>; async fn get_mptt_current(&mut self) -> anyhow::Result<Current>;
} }
impl dyn BoardInteraction<'_> { impl dyn BoardInteraction<'_> {
//the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons //the counter is just some arbitrary number that increases whenever some progress was made, try to keep the updates < 10 per second for ux reasons
fn _progress(&mut self, counter: u32) { async fn _progress(&mut self, counter: u32) {
let even = counter % 2 == 0; let even = counter % 2 == 0;
let current = counter / (PLANT_COUNT as u32); let current = counter / (PLANT_COUNT as u32);
for led in 0..PLANT_COUNT { for led in 0..PLANT_COUNT {
self.fault(led, current == led as u32).unwrap(); self.fault(led, current == led as u32).await.unwrap();
} }
let _ = self.general_fault(even.into()); let _ = self.general_fault(even.into());
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct FreePeripherals { pub struct FreePeripherals<'a> {
pub gpio0: Gpio0, // pub gpio0: Gpio0,
pub gpio1: Gpio1, // pub gpio1: Gpio1,
pub gpio2: Gpio2, // pub gpio2: Gpio2,
pub gpio3: Gpio3, // pub gpio3: Gpio3,
pub gpio4: Gpio4, // pub gpio4: Gpio4,
pub gpio5: Gpio5, // pub gpio5: Gpio5,
pub gpio6: Gpio6, pub gpio6: GPIO6<'a>,
pub gpio7: Gpio7, // pub gpio7: Gpio7,
pub gpio8: Gpio8, // pub gpio8: Gpio8,
//config button here // //config button here
pub gpio10: Gpio10, // pub gpio10: Gpio10,
pub gpio11: Gpio11, // pub gpio11: Gpio11,
pub gpio12: Gpio12, // pub gpio12: Gpio12,
pub gpio13: Gpio13, // pub gpio13: Gpio13,
pub gpio14: Gpio14, // pub gpio14: Gpio14,
pub gpio15: Gpio15, // pub gpio15: Gpio15,
pub gpio16: Gpio16, // pub gpio16: Gpio16,
pub gpio17: Gpio17, // pub gpio17: Gpio17,
pub gpio18: Gpio18, // pub gpio18: Gpio18,
//i2c here // //i2c here
pub gpio21: Gpio21, // pub gpio21: Gpio21,
pub gpio22: Gpio22, // pub gpio22: Gpio22,
pub gpio23: Gpio23, pub gpio23: GPIO23<'a>,
pub gpio24: Gpio24, // pub gpio24: Gpio24,
pub gpio25: Gpio25, // pub gpio25: Gpio25,
pub gpio26: Gpio26, // pub gpio26: Gpio26,
pub gpio27: Gpio27, // pub gpio27: Gpio27,
pub gpio28: Gpio28, // pub gpio28: Gpio28,
pub gpio29: Gpio29, // pub gpio29: Gpio29,
pub gpio30: Gpio30, // pub gpio30: Gpio30,
pub pcnt0: PCNT0, // pub pcnt0: PCNT0,
pub pcnt1: PCNT1, // pub pcnt1: PCNT1,
pub adc1: ADC1, // pub adc1: ADC1,
pub can: CAN, // pub can: CAN,
} }
macro_rules! mk_static {
($t:ty,$val:expr) => {{
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
#[deny(unused_attributes)]
let x = STATIC_CELL.uninit().write(($val));
x
}};
}
const GW_IP_ADDR_ENV: Option<&'static str> = option_env!("GATEWAY_IP");
impl PlantHal { impl PlantHal {
fn create_i2c() -> Mutex<I2cDriver<'static>> { // fn create_i2c() -> Mutex<CriticalSectionRawMutex, I2cDriver<'static>> {
let peripherals = unsafe { Peripherals::new() }; // let peripherals = unsafe { Peripherals::new() };
//
// let config = I2cConfig::new()
// .scl_enable_pullup(true)
// .sda_enable_pullup(true)
// .baudrate(100_u32.kHz().into())
// .timeout(APBTickType::from(Duration::from_millis(100)));
//
// let i2c = peripherals.i2c0;
// let scl = peripherals.pins.gpio19.downgrade();
// let sda = peripherals.pins.gpio20.downgrade();
//
// Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap())
// }
let config = I2cConfig::new() pub async fn create(spawner: Spawner) -> Result<Mutex<CriticalSectionRawMutex, HAL<'static>>> {
.scl_enable_pullup(true) let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
.sda_enable_pullup(true) let peripherals: Peripherals = esp_hal::init(config);
.baudrate(100_u32.kHz().into())
.timeout(APBTickType::from(Duration::from_millis(100)));
let i2c = peripherals.i2c0; esp_alloc::heap_allocator!(size: 64 * 1024);
let scl = peripherals.pins.gpio19.downgrade(); let systimer = SystemTimer::new(peripherals.SYSTIMER);
let sda = peripherals.pins.gpio20.downgrade();
Mutex::new(I2cDriver::new(i2c, sda, scl, &config).unwrap()) let boot_button = Input::new(
peripherals.GPIO9,
InputConfig::default().with_pull(Pull::None),
);
let rng = Rng::new(peripherals.RNG);
let timg0 = TimerGroup::new(peripherals.TIMG0);
let esp_wifi_ctrl = &*mk_static!(
EspWifiController<'static>,
init(timg0.timer0, rng.clone()).unwrap()
);
let (controller, interfaces) =
esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).unwrap();
use esp_hal::timer::systimer::SystemTimer;
esp_hal_embassy::init(systimer.alarm0);
//let mut adc1 = Adc::new(peripherals.ADC1, adc1_config);
//
let free_pins = FreePeripherals {
// can: peripherals.can,
// adc1: peripherals.adc1,
// pcnt0: peripherals.pcnt0,
// pcnt1: peripherals.pcnt1,
// gpio0: peripherals.pins.gpio0,
// gpio1: peripherals.pins.gpio1,
// gpio2: peripherals.pins.gpio2,
// gpio3: peripherals.pins.gpio3,
// gpio4: peripherals.pins.gpio4,
// gpio5: peripherals.pins.gpio5,
gpio6: peripherals.GPIO6,
// gpio7: peripherals.pins.gpio7,
// gpio8: peripherals.pins.gpio8,
// gpio10: peripherals.pins.gpio10,
// gpio11: peripherals.pins.gpio11,
// gpio12: peripherals.pins.gpio12,
// gpio13: peripherals.pins.gpio13,
// gpio14: peripherals.pins.gpio14,
// gpio15: peripherals.pins.gpio15,
// gpio16: peripherals.pins.gpio16,
// gpio17: peripherals.pins.gpio17,
// gpio18: peripherals.pins.gpio18,
// gpio21: peripherals.pins.gpio21,
// gpio22: peripherals.pins.gpio22,
gpio23: peripherals.GPIO23,
// gpio24: peripherals.pins.gpio24,
// gpio25: peripherals.pins.gpio25,
// gpio26: peripherals.pins.gpio26,
// gpio27: peripherals.pins.gpio27,
// gpio28: peripherals.pins.gpio28,
// gpio29: peripherals.pins.gpio29,
// gpio30: peripherals.pins.gpio30,
};
//
let tablebuffer: [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN] =
[0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN];
let tablebuffer = mk_static!(
[u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN],
tablebuffer
);
let storage_ota = mk_static!(FlashStorage, FlashStorage::new());
let pt =
esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?;
// List all partitions - this is just FYI
for i in 0..pt.len() {
println!("{:?}", pt.get_partition(i));
} }
pub fn create() -> Result<Mutex<HAL<'static>>> { let ota_data = pt
let peripherals = Peripherals::take()?; .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
let sys_loop = EspSystemEventLoop::take()?; DataPartitionSubType::Ota,
let nvs = EspDefaultNvsPartition::take()?; ))?
let wifi_driver = EspWifi::new(peripherals.modem, sys_loop, Some(nvs))?; .unwrap();
let mut boot_button = PinDriver::input(peripherals.pins.gpio9.downgrade())?; let ota_data = mk_static!(PartitionEntry, ota_data);
boot_button.set_pull(Pull::Floating)?;
let free_pins = FreePeripherals { let ota_data = ota_data.as_embedded_storage(storage_ota);
can: peripherals.can, let ota_data = mk_static!(FlashRegion<FlashStorage>, ota_data);
adc1: peripherals.adc1,
pcnt0: peripherals.pcnt0, let mut ota = esp_bootloader_esp_idf::ota::Ota::new(ota_data)?;
pcnt1: peripherals.pcnt1,
gpio0: peripherals.pins.gpio0, let ota_partition = match ota.current_slot()? {
gpio1: peripherals.pins.gpio1, Slot::None => {
gpio2: peripherals.pins.gpio2, panic!("No OTA slot found");
gpio3: peripherals.pins.gpio3, }
gpio4: peripherals.pins.gpio4, Slot::Slot0 => pt
gpio5: peripherals.pins.gpio5, .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
gpio6: peripherals.pins.gpio6, AppPartitionSubType::Ota0,
gpio7: peripherals.pins.gpio7, ))?
gpio8: peripherals.pins.gpio8, .unwrap(),
gpio10: peripherals.pins.gpio10, Slot::Slot1 => pt
gpio11: peripherals.pins.gpio11, .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App(
gpio12: peripherals.pins.gpio12, AppPartitionSubType::Ota1,
gpio13: peripherals.pins.gpio13, ))?
gpio14: peripherals.pins.gpio14, .unwrap(),
gpio15: peripherals.pins.gpio15,
gpio16: peripherals.pins.gpio16,
gpio17: peripherals.pins.gpio17,
gpio18: peripherals.pins.gpio18,
gpio21: peripherals.pins.gpio21,
gpio22: peripherals.pins.gpio22,
gpio23: peripherals.pins.gpio23,
gpio24: peripherals.pins.gpio24,
gpio25: peripherals.pins.gpio25,
gpio26: peripherals.pins.gpio26,
gpio27: peripherals.pins.gpio27,
gpio28: peripherals.pins.gpio28,
gpio29: peripherals.pins.gpio29,
gpio30: peripherals.pins.gpio30,
}; };
let ota_next = mk_static!(PartitionEntry, ota_partition);
let storage_ota_next = mk_static!(FlashStorage, FlashStorage::new());
let ota_next = mk_static!(
FlashRegion<FlashStorage>,
ota_next.as_embedded_storage(storage_ota_next)
);
let mut esp = Esp { let mut esp = Esp {
mqtt_client: None, rng,
wifi_driver, controller: Arc::new(Mutex::new(controller)),
interfaces: Some(interfaces),
boot_button, boot_button,
delay: Delay::new(1000), mqtt_client: None,
ota,
ota_next,
wall_clock_offset: 0,
}; };
//init,reset rtc memory depending on cause //init,reset rtc memory depending on cause
let mut init_rtc_store: bool = false; let mut init_rtc_store: bool = false;
let mut to_config_mode: bool = false; let mut to_config_mode: bool = false;
let reasons = ResetReason::get(); let reasons = "";
match reasons { // let reasons = ResetReason::get();
ResetReason::Software => {} // match reasons {
ResetReason::ExternalPin => {} // ResetReason::Software => {}
ResetReason::Watchdog => { // ResetReason::ExternalPin => {}
init_rtc_store = true; // ResetReason::Watchdog => {
} // init_rtc_store = true;
ResetReason::Sdio => init_rtc_store = true, // }
ResetReason::Panic => init_rtc_store = true, // ResetReason::Sdio => init_rtc_store = true,
ResetReason::InterruptWatchdog => init_rtc_store = true, // ResetReason::Panic => init_rtc_store = true,
ResetReason::PowerOn => init_rtc_store = true, // ResetReason::InterruptWatchdog => init_rtc_store = true,
ResetReason::Unknown => init_rtc_store = true, // ResetReason::PowerOn => init_rtc_store = true,
ResetReason::Brownout => init_rtc_store = true, // ResetReason::Unknown => init_rtc_store = true,
ResetReason::TaskWatchdog => init_rtc_store = true, // ResetReason::Brownout => init_rtc_store = true,
ResetReason::DeepSleep => {} // ResetReason::TaskWatchdog => init_rtc_store = true,
ResetReason::USBPeripheral => { // ResetReason::DeepSleep => {}
init_rtc_store = true; // ResetReason::USBPeripheral => {
to_config_mode = true; // init_rtc_store = true;
} // to_config_mode = true;
ResetReason::JTAG => init_rtc_store = true, // }
}; // ResetReason::JTAG => init_rtc_store = true,
// };
log( log(
LogMessage::ResetReason, LogMessage::ResetReason,
init_rtc_store as u32, init_rtc_store as u32,
@@ -266,71 +329,77 @@ impl PlantHal {
let config = esp.load_config(); let config = esp.load_config();
println!("Init rtc driver"); log::info!("Init rtc driver");
let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER)); // let mut rtc = Ds323x::new_ds3231(MutexDevice::new(&I2C_DRIVER));
//
// log::info!("Init rtc eeprom driver");
// let eeprom = {
// Eeprom24x::new_24x32(
// MutexDevice::new(&I2C_DRIVER),
// SlaveAddr::Alternative(true, true, true),
// )
// };
// let rtc_time = rtc.datetime();
// match rtc_time {
// OkStd(tt) => {
// log::info!("Rtc Module reports time at UTC {}", tt);
// }
// Err(err) => {
// log::info!("Rtc Module could not be read {:?}", err);
// }
// }
println!("Init rtc eeprom driver"); // let storage = Storage::new(eeprom, Delay::new(1000));
let eeprom = { //let rtc_module: Box<dyn RTCModuleInteraction + Send> =
Eeprom24x::new_24x32( // Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
MutexDevice::new(&I2C_DRIVER),
SlaveAddr::Alternative(true, true, true),
)
};
let rtc_time = rtc.datetime();
match rtc_time {
OkStd(tt) => {
println!("Rtc Module reports time at UTC {}", tt);
}
Err(err) => {
println!("Rtc Module could not be read {:?}", err);
}
}
let storage = Storage::new(eeprom, Delay::new(1000));
let rtc_module: Box<dyn RTCModuleInteraction + Send> =
Box::new(DS3231Module { rtc, storage }) as Box<dyn RTCModuleInteraction + Send>;
let hal = match config { let hal = match config {
Result::Ok(config) => { Result::Ok(config) => {
let battery_interaction: Box<dyn BatteryInteraction + Send> = let battery_interaction: Box<dyn BatteryInteraction + Send> =
match config.hardware.battery { match config.hardware.battery {
BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}), BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}),
BatteryBoardVersion::BQ34Z100G1 => { // BatteryBoardVersion::BQ34Z100G1 => {
let mut battery_driver = Bq34z100g1Driver { // let mut battery_driver = Bq34z100g1Driver {
i2c: MutexDevice::new(&I2C_DRIVER), // i2c: MutexDevice::new(&I2C_DRIVER),
delay: Delay::new(0), // delay: Delay::new(0),
flash_block_data: [0; 32], // flash_block_data: [0; 32],
}; // };
let status = print_battery_bq34z100(&mut battery_driver); // let status = print_battery_bq34z100(&mut battery_driver);
match status { // match status {
OkStd(_) => {} // Ok(_) => {}
Err(err) => { // Err(err) => {
log( // log(
LogMessage::BatteryCommunicationError, // LogMessage::BatteryCommunicationError,
0u32, // 0u32,
0, // 0,
"", // "",
&format!("{err:?})"), // &format!("{err:?})"),
); // );
} // }
} // }
Box::new(BQ34Z100G1 { battery_driver }) // Box::new(BQ34Z100G1 { battery_driver })
} // }
BatteryBoardVersion::WchI2cSlave => { BatteryBoardVersion::WchI2cSlave => {
// TODO use correct implementation once availible // TODO use correct implementation once availible
Box::new(NoBatteryMonitor {}) Box::new(NoBatteryMonitor {})
} }
_ => {
todo!()
}
}; };
let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board { let board_hal: Box<dyn BoardInteraction + Send> = match config.hardware.board {
BoardVersion::INITIAL => { BoardVersion::INITIAL => {
initial_hal::create_initial_board(free_pins, fs_mount_error, config, esp)? initial_hal::create_initial_board(free_pins, config, esp)?
} }
BoardVersion::V3 => { // BoardVersion::V3 => {
v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)? // v3_hal::create_v3(free_pins, esp, config, battery_interaction, rtc_module)?
} // }
BoardVersion::V4 => { // BoardVersion::V4 => {
v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)? // v4_hal::create_v4(free_pins, esp, config, battery_interaction, rtc_module)?
// }
_ => {
todo!()
} }
}; };
@@ -347,7 +416,6 @@ impl PlantHal {
HAL { HAL {
board_hal: initial_hal::create_initial_board( board_hal: initial_hal::create_initial_board(
free_pins, free_pins,
fs_mount_error,
PlantControllerConfig::default(), PlantControllerConfig::default(),
esp, esp,
)?, )?,

View File

@@ -1,132 +1,138 @@
use anyhow::{anyhow, bail}; use crate::hal::Box;
use bincode::config::Configuration; use alloc::vec::Vec;
use bincode::{config, Decode, Encode}; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use ds323x::{DateTimeAccess, Ds323x};
use eeprom24x::addr_size::TwoBytes;
use eeprom24x::page_size::B32;
use eeprom24x::unique_serial::No;
use eeprom24x::Storage;
use embedded_hal_bus::i2c::MutexDevice;
use embedded_storage::ReadStorage as embedded_storage_ReadStorage;
use embedded_storage::Storage as embedded_storage_Storage;
use esp_idf_hal::delay::Delay;
use esp_idf_hal::i2c::I2cDriver;
use serde::{Deserialize, Serialize};
use std::result::Result::Ok as OkStd;
const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
const CONFIG: Configuration = config::standard();
// use crate::hal::Box;
// use alloc::vec::Vec;
// use anyhow::{anyhow, bail};
// use async_trait::async_trait;
// use bincode::config::Configuration;
// use bincode::{config, Decode, Encode};
// use chrono::{DateTime, Utc};
// use ds323x::{DateTimeAccess, Ds323x};
// use eeprom24x::addr_size::TwoBytes;
// use eeprom24x::page_size::B32;
// use eeprom24x::unique_serial::No;
// use eeprom24x::Storage;
// use embedded_storage::ReadStorage as embedded_storage_ReadStorage;
// use embedded_storage::Storage as embedded_storage_Storage;
// use serde::{Deserialize, Serialize};
//
// const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC);
// const CONFIG: Configuration = config::standard();
//
#[async_trait]
pub trait RTCModuleInteraction { pub trait RTCModuleInteraction {
fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>; async fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader>;
fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>; async fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>>;
fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>; async fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()>;
fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>; async fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>>;
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>; async fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()>;
} }
//
const BACKUP_HEADER_MAX_SIZE: usize = 64; // const BACKUP_HEADER_MAX_SIZE: usize = 64;
#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)] // #[derive(Serialize, Deserialize, PartialEq, Debug, Default, Encode, Decode)]
pub struct BackupHeader { pub struct BackupHeader {
pub timestamp: i64, pub timestamp: i64,
crc16: u16, crc16: u16,
pub size: u16, pub size: u16,
} }
//
pub struct DS3231Module<'a> { // pub struct DS3231Module<'a> {
pub(crate) rtc: // pub(crate) rtc:
Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>, // Ds323x<ds323x::interface::I2cInterface<MutexDevice<'a, I2cDriver<'a>>>, ds323x::ic::DS3231>,
//
pub(crate) storage: Storage<MutexDevice<'a, I2cDriver<'a>>, B32, TwoBytes, No, Delay>, // pub(crate) storage: Storage<MutexDevice<'a, I2cDriver<'a>>, B32, TwoBytes, No, Delay>,
} // }
//
impl RTCModuleInteraction for DS3231Module<'_> { // impl RTCModuleInteraction for DS3231Module<'_> {
fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> { // fn get_backup_info(&mut self) -> anyhow::Result<BackupHeader> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; // let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
//
self.storage // self.storage
.read(0, &mut header_page_buffer) // .read(0, &mut header_page_buffer)
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; // .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
//
let (header, len): (BackupHeader, usize) = // let (header, len): (BackupHeader, usize) =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; // bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
//
println!("Raw header is {:?} with size {}", header_page_buffer, len); // log::info!("Raw header is {:?} with size {}", header_page_buffer, len);
anyhow::Ok(header) // anyhow::Ok(header)
} // }
//
fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> { // fn get_backup_config(&mut self) -> anyhow::Result<Vec<u8>> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; // let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
//
self.storage // self.storage
.read(0, &mut header_page_buffer) // .read(0, &mut header_page_buffer)
.map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?; // .map_err(|err| anyhow!("Error reading eeprom header {:?}", err))?;
let (header, _header_size): (BackupHeader, usize) = // let (header, _header_size): (BackupHeader, usize) =
bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?; // bincode::decode_from_slice(&header_page_buffer[..], CONFIG)?;
//
let mut data_buffer = vec![0_u8; header.size as usize]; // let mut data_buffer = vec![0_u8; header.size as usize];
//read the specified number of bytes after the header // //read the specified number of bytes after the header
self.storage // self.storage
.read(BACKUP_HEADER_MAX_SIZE as u32, &mut data_buffer) // .read(BACKUP_HEADER_MAX_SIZE as u32, &mut data_buffer)
.map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?; // .map_err(|err| anyhow!("Error reading eeprom data {:?}", err))?;
//
let checksum = X25.checksum(&data_buffer); // let checksum = X25.checksum(&data_buffer);
if checksum != header.crc16 { // if checksum != header.crc16 {
bail!( // bail!(
"Invalid checksum, got {} but expected {}", // "Invalid checksum, got {} but expected {}",
checksum, // checksum,
header.crc16 // header.crc16
); // );
} // }
//
anyhow::Ok(data_buffer) // anyhow::Ok(data_buffer)
} // }
fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> { // fn backup_config(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE]; // let mut header_page_buffer = [0_u8; BACKUP_HEADER_MAX_SIZE];
//
let time = self.get_rtc_time()?.timestamp_millis(); // let time = self.get_rtc_time()?.timestamp_millis();
let checksum = X25.checksum(bytes); // let checksum = X25.checksum(bytes);
//
let header = BackupHeader { // let header = BackupHeader {
crc16: checksum, // crc16: checksum,
timestamp: time, // timestamp: time,
size: bytes.len() as u16, // size: bytes.len() as u16,
}; // };
let config = config::standard(); // let config = config::standard();
let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?; // let encoded = bincode::encode_into_slice(&header, &mut header_page_buffer, config)?;
println!( // log::info!(
"Raw header is {:?} with size {}", // "Raw header is {:?} with size {}",
header_page_buffer, encoded // header_page_buffer,
); // encoded
self.storage // );
.write(0, &header_page_buffer) // self.storage
.map_err(|err| anyhow!("Error writing header {:?}", err))?; // .write(0, &header_page_buffer)
// .map_err(|err| anyhow!("Error writing header {:?}", err))?;
//write rest after the header //
self.storage // //write rest after the header
.write(BACKUP_HEADER_MAX_SIZE as u32, &bytes) // self.storage
.map_err(|err| anyhow!("Error writing body {:?}", err))?; // .write(BACKUP_HEADER_MAX_SIZE as u32, &bytes)
// .map_err(|err| anyhow!("Error writing body {:?}", err))?;
anyhow::Ok(()) //
} // anyhow::Ok(())
// }
fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> { //
match self.rtc.datetime() { // fn get_rtc_time(&mut self) -> anyhow::Result<DateTime<Utc>> {
OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()), // match self.rtc.datetime() {
Err(err) => { // OkStd(rtc_time) => anyhow::Ok(rtc_time.and_utc()),
bail!("Error getting rtc time {:?}", err) // Err(err) => {
} // bail!("Error getting rtc time {:?}", err)
} // }
} // }
// }
fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> { //
let naive_time = time.naive_utc(); // fn set_rtc_time(&mut self, time: &DateTime<Utc>) -> anyhow::Result<()> {
match self.rtc.set_datetime(&naive_time) { // let naive_time = time.naive_utc();
OkStd(_) => anyhow::Ok(()), // match self.rtc.set_datetime(&naive_time) {
Err(err) => { // OkStd(_) => anyhow::Ok(()),
bail!("Error getting rtc time {:?}", err) // Err(err) => {
} // bail!("Error getting rtc time {:?}", err)
} // }
} // }
} // }
// }

View File

@@ -1,171 +0,0 @@
use crate::hal::TANK_MULTI_SAMPLE;
use anyhow::{anyhow, bail};
use ds18b20::Ds18b20;
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver};
use esp_idf_hal::adc::{attenuation, Resolution, ADC1};
use esp_idf_hal::delay::Delay;
use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull};
use esp_idf_hal::pcnt::{
PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1,
};
use esp_idf_sys::EspError;
use one_wire_bus::OneWire;
pub struct TankSensor<'a> {
one_wire_bus: OneWire<PinDriver<'a, AnyIOPin, InputOutput>>,
tank_channel: AdcChannelDriver<'a, Gpio5, AdcDriver<'a, ADC1>>,
tank_power: PinDriver<'a, AnyIOPin, InputOutput>,
flow_counter: PcntDriver<'a>,
delay: Delay,
}
impl<'a> TankSensor<'a> {
pub(crate) fn create(
one_wire_pin: AnyIOPin,
adc1: ADC1,
gpio5: Gpio5,
tank_power_pin: AnyIOPin,
flow_sensor_pin: AnyIOPin,
pcnt1: PCNT1,
) -> anyhow::Result<TankSensor<'a>> {
let mut one_wire_pin =
PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin");
one_wire_pin
.set_pull(Pull::Floating)
.expect("Failed to set pull");
let adc_config = AdcChannelConfig {
attenuation: attenuation::DB_11,
resolution: Resolution::Resolution12Bit,
calibration: esp_idf_hal::adc::oneshot::config::Calibration::Curve,
};
let tank_driver = AdcDriver::new(adc1).expect("Failed to configure ADC");
let tank_channel = AdcChannelDriver::new(tank_driver, gpio5, &adc_config)
.expect("Failed to configure ADC channel");
let mut tank_power =
PinDriver::input_output(tank_power_pin).expect("Failed to configure pin");
tank_power
.set_pull(Pull::Floating)
.expect("Failed to set pull");
let one_wire_bus =
OneWire::new(one_wire_pin).expect("OneWire bus did not pull up after release");
let mut flow_counter = PcntDriver::new(
pcnt1,
Some(flow_sensor_pin),
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
Option::<AnyInputPin>::None,
)?;
flow_counter.channel_config(
PcntChannel::Channel1,
PinIndex::Pin0,
PinIndex::Pin1,
&PcntChannelConfig {
lctrl_mode: PcntControlMode::Keep,
hctrl_mode: PcntControlMode::Keep,
pos_mode: PcntCountMode::Increment,
neg_mode: PcntCountMode::Hold,
counter_h_lim: i16::MAX,
counter_l_lim: 0,
},
)?;
Ok(TankSensor {
one_wire_bus,
tank_channel,
tank_power,
flow_counter,
delay: Default::default(),
})
}
pub fn reset_flow_meter(&mut self) {
self.flow_counter.counter_pause().unwrap();
self.flow_counter.counter_clear().unwrap();
}
pub fn start_flow_meter(&mut self) {
self.flow_counter.counter_resume().unwrap();
}
pub fn get_flow_meter_value(&mut self) -> i16 {
self.flow_counter.get_counter_value().unwrap()
}
pub fn stop_flow_meter(&mut self) -> i16 {
self.flow_counter.counter_pause().unwrap();
self.get_flow_meter_value()
}
pub fn water_temperature_c(&mut self) -> anyhow::Result<f32> {
//multisample should be moved to water_temperature_c
let mut attempt = 1;
let water_temp: Result<f32, anyhow::Error> = loop {
let temp = self.single_temperature_c();
match &temp {
Ok(res) => {
println!("Water temp is {}", res);
break temp;
}
Err(err) => {
println!("Could not get water temp {} attempt {}", err, attempt)
}
}
if attempt == 5 {
break temp;
}
attempt += 1;
};
water_temp
}
fn single_temperature_c(&mut self) -> anyhow::Result<f32> {
self.one_wire_bus
.reset(&mut self.delay)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
let first = self.one_wire_bus.devices(false, &mut self.delay).next();
if first.is_none() {
bail!("Not found any one wire Ds18b20");
}
let device_address = first
.unwrap()
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
let water_temp_sensor = Ds18b20::new::<EspError>(device_address)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
water_temp_sensor
.start_temp_measurement(&mut self.one_wire_bus, &mut self.delay)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
ds18b20::Resolution::Bits12.delay_for_measurement_time(&mut self.delay);
let sensor_data = water_temp_sensor
.read_data(&mut self.one_wire_bus, &mut self.delay)
.map_err(|err| -> anyhow::Error { anyhow!("Missing attribute: {:?}", err) })?;
if sensor_data.temperature == 85_f32 {
bail!("Ds18b20 dummy temperature returned");
}
anyhow::Ok(sensor_data.temperature / 10_f32)
}
pub fn tank_sensor_voltage(&mut self) -> anyhow::Result<f32> {
self.tank_power.set_high()?;
//let stabilize
self.delay.delay_ms(100);
let mut store = [0_u16; TANK_MULTI_SAMPLE];
for multisample in 0..TANK_MULTI_SAMPLE {
let value = self.tank_channel.read()?;
store[multisample] = value;
}
self.tank_power.set_low()?;
store.sort();
let median_mv = store[6] as f32 / 1000_f32;
anyhow::Ok(median_mv)
}
}

View File

@@ -1,4 +0,0 @@
#![allow(dead_code)]
extern crate embedded_hal as hal;
pub mod sipo;

View File

@@ -1,12 +1,16 @@
use crate::vec;
use alloc::string::ToString;
use alloc::vec::Vec;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::lazy_lock::LazyLock;
use embassy_sync::mutex::Mutex;
use embassy_time::Instant;
use esp_println::println;
use log::info;
use serde::Serialize; use serde::Serialize;
use std::{collections::HashMap, sync::Mutex};
use strum::EnumIter;
use strum_macros::IntoStaticStr; use strum_macros::IntoStaticStr;
use esp_idf_svc::systime::EspSystemTime;
use once_cell::sync::Lazy;
use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
use text_template::Template;
use unit_enum::UnitEnum; use unit_enum::UnitEnum;
const TXT_SHORT_LENGTH: usize = 8; const TXT_SHORT_LENGTH: usize = 8;
@@ -18,8 +22,9 @@ const BUFFER_SIZE: usize = 220;
static mut BUFFER: ConstGenericRingBuffer<LogEntry, BUFFER_SIZE> = static mut BUFFER: ConstGenericRingBuffer<LogEntry, BUFFER_SIZE> =
ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new(); ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new();
#[allow(static_mut_refs)] #[allow(static_mut_refs)]
static BUFFER_ACCESS: Lazy<Mutex<&mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>> = static BUFFER_ACCESS: LazyLock<
Lazy::new(|| unsafe { Mutex::new(&mut BUFFER) }); Mutex<CriticalSectionRawMutex, &mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>,
> = LazyLock::new(|| unsafe { Mutex::new(&mut BUFFER) });
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
pub struct LogEntry { pub struct LogEntry {
@@ -31,11 +36,11 @@ pub struct LogEntry {
pub txt_long: heapless::String<TXT_LONG_LENGTH>, pub txt_long: heapless::String<TXT_LONG_LENGTH>,
} }
pub fn init() { pub async fn init() {
unsafe { unsafe {
BUFFER = ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new(); BUFFER = ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new();
}; };
let mut access = BUFFER_ACCESS.lock().unwrap(); let mut access = BUFFER_ACCESS.get().lock().await;
access.drain().for_each(|_| {}); access.drain().for_each(|_| {});
} }
@@ -57,8 +62,8 @@ fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<L
} }
} }
pub fn get_log() -> Vec<LogEntry> { pub async fn get_log() -> Vec<LogEntry> {
let buffer = BUFFER_ACCESS.lock().unwrap(); let buffer = BUFFER_ACCESS.get().lock().await;
let mut read_copy = Vec::new(); let mut read_copy = Vec::new();
for entry in buffer.iter() { for entry in buffer.iter() {
let copy = entry.clone(); let copy = entry.clone();
@@ -68,32 +73,32 @@ pub fn get_log() -> Vec<LogEntry> {
read_copy read_copy
} }
pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) { pub async fn log(
message_key: LogMessage,
number_a: u32,
number_b: u32,
txt_short: &str,
txt_long: &str,
) {
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new(); let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new(); let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
limit_length(txt_short, &mut txt_short_stack); limit_length(txt_short, &mut txt_short_stack);
limit_length(txt_long, &mut txt_long_stack); limit_length(txt_long, &mut txt_long_stack);
let time = EspSystemTime {}.now().as_millis() as u64; //TODO
let time = Instant::now().as_secs();
// let time = EspSystemTime {}.now().as_millis() as u64;
//
let ordinal = message_key.ordinal() as u16; let ordinal = message_key.ordinal() as u16;
let template_string: &str = message_key.into(); let template: &str = message_key.into();
let mut template_string = template.to_string();
template_string = template_string.replace("${number_a}", number_a.to_string().as_str());
template_string = template_string.replace("${number_b}", number_b.to_string().as_str());
template_string = template_string.replace("${txt_long}", txt_long);
template_string = template_string.replace("${txt_short}", txt_short);
let mut values: HashMap<&str, &str> = HashMap::new(); info!("LOG: {} : {}", time, template_string);
let number_a_str = number_a.to_string();
let number_b_str = number_b.to_string();
values.insert("number_a", &number_a_str);
values.insert("number_b", &number_b_str);
values.insert("txt_short", txt_short);
values.insert("txt_long", txt_long);
let template = Template::from(template_string);
let serial_entry = template.fill_in(&values);
println!("{serial_entry}");
//TODO push to mqtt?
let entry = LogEntry { let entry = LogEntry {
timestamp: time, timestamp: time,
@@ -104,29 +109,10 @@ pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &st
txt_long: txt_long_stack, txt_long: txt_long_stack,
}; };
let mut buffer = BUFFER_ACCESS.lock().unwrap(); let mut buffer = BUFFER_ACCESS.get().lock().await;
buffer.push(entry); buffer.push(entry);
} }
#[derive(IntoStaticStr, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn within_limit() {
let test = "12345678";
let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
limit_length(test, &mut txt_short_stack);
limit_length(test, &mut txt_long_stack);
assert_eq!(txt_short_stack.as_str(), test);
assert_eq!(txt_long_stack.as_str(), test);
}
}
#[derive(IntoStaticStr, EnumIter, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)]
pub enum LogMessage { pub enum LogMessage {
#[strum( #[strum(
serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}" serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}"

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ use crate::{
hal::{Sensor, HAL}, hal::{Sensor, HAL},
in_time_range, in_time_range,
}; };
use alloc::string::{String, ToString};
use chrono::{DateTime, TimeDelta, Utc}; use chrono::{DateTime, TimeDelta, Utc};
use chrono_tz::Tz; use chrono_tz::Tz;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -115,9 +116,9 @@ fn map_range_moisture(
} }
impl PlantState { impl PlantState {
pub fn read_hardware_state(plant_id: usize, board: &mut HAL) -> Self { pub async fn read_hardware_state(plant_id: usize, board: &mut HAL<'_>) -> Self {
let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a { let sensor_a = if board.board_hal.get_config().plants[plant_id].sensor_a {
match board.board_hal.measure_moisture_hz(plant_id, Sensor::A) { match board.board_hal.measure_moisture_hz(plant_id, Sensor::A).await {
Ok(raw) => match map_range_moisture( Ok(raw) => match map_range_moisture(
raw, raw,
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency, board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
@@ -138,7 +139,7 @@ impl PlantState {
}; };
let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b { let sensor_b = if board.board_hal.get_config().plants[plant_id].sensor_b {
match board.board_hal.measure_moisture_hz(plant_id, Sensor::B) { match board.board_hal.measure_moisture_hz(plant_id, Sensor::B).await {
Ok(raw) => match map_range_moisture( Ok(raw) => match map_range_moisture(
raw, raw,
board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency, board.board_hal.get_config().plants[plant_id].moisture_sensor_min_frequency,
@@ -263,50 +264,50 @@ impl PlantState {
PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time), PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time),
} }
} }
//
pub fn to_mqtt_info( // pub fn to_mqtt_info(
&self, // &self,
plant_conf: &PlantConfig, // plant_conf: &PlantConfig,
current_time: &DateTime<Tz>, // current_time: &DateTime<Tz>,
) -> PlantInfo<'_> { // ) -> PlantInfo<'_> {
PlantInfo { // PlantInfo {
sensor_a: &self.sensor_a, // sensor_a: &self.sensor_a,
sensor_b: &self.sensor_b, // sensor_b: &self.sensor_b,
mode: plant_conf.mode, // mode: plant_conf.mode,
do_water: self.needs_to_be_watered(plant_conf, current_time), // do_water: self.needs_to_be_watered(plant_conf, current_time),
dry: if let Some(moisture_percent) = self.plant_moisture().0 { // dry: if let Some(moisture_percent) = self.plant_moisture().0 {
moisture_percent < plant_conf.target_moisture // moisture_percent < plant_conf.target_moisture
} else { // } else {
false // false
}, // },
cooldown: self.pump_in_timeout(plant_conf, current_time), // cooldown: self.pump_in_timeout(plant_conf, current_time),
out_of_work_hour: in_time_range( // out_of_work_hour: in_time_range(
current_time, // current_time,
plant_conf.pump_hour_start, // plant_conf.pump_hour_start,
plant_conf.pump_hour_end, // plant_conf.pump_hour_end,
), // ),
consecutive_pump_count: self.pump.consecutive_pump_count, // consecutive_pump_count: self.pump.consecutive_pump_count,
pump_error: self.pump.is_err(plant_conf), // pump_error: self.pump.is_err(plant_conf),
last_pump: self // last_pump: self
.pump // .pump
.previous_pump // .previous_pump
.map(|t| t.with_timezone(&current_time.timezone())), // .map(|t| t.with_timezone(&current_time.timezone())),
next_pump: if matches!( // next_pump: if matches!(
plant_conf.mode, // plant_conf.mode,
PlantWateringMode::TimerOnly // PlantWateringMode::TimerOnly
| PlantWateringMode::TargetMoisture // | PlantWateringMode::TargetMoisture
| PlantWateringMode::MinMoisture // | PlantWateringMode::MinMoisture
) { // ) {
self.pump.previous_pump.and_then(|last_pump| { // self.pump.previous_pump.and_then(|last_pump| {
last_pump // last_pump
.checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into())) // .checked_add_signed(TimeDelta::minutes(plant_conf.pump_cooldown_min.into()))
.map(|t| t.with_timezone(&current_time.timezone())) // .map(|t| t.with_timezone(&current_time.timezone()))
}) // })
} else { // } else {
None // None
}, // },
} // }
} // }
} }
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, Serialize)]
@@ -329,8 +330,8 @@ pub struct PlantInfo<'a> {
/// how often has the pump been watered without reaching target moisture /// how often has the pump been watered without reaching target moisture
consecutive_pump_count: u32, consecutive_pump_count: u32,
pump_error: Option<PumpError>, pump_error: Option<PumpError>,
/// last time when the pump was active // /// last time when the pump was active
last_pump: Option<DateTime<Tz>>, // last_pump: Option<DateTime<Tz>>,
/// next time when pump should activate // /// next time when pump should activate
next_pump: Option<DateTime<Tz>>, // next_pump: Option<DateTime<Tz>>,
} }

View File

@@ -1,10 +1,11 @@
//! Serial-in parallel-out shift register //! Serial-in parallel-out shift register
use core::cell::RefCell; use core::cell::RefCell;
use core::convert::Infallible;
use core::iter::Iterator;
use core::mem::{self, MaybeUninit}; use core::mem::{self, MaybeUninit};
use std::convert::Infallible; use core::result::{Result, Result::Ok};
use embedded_hal::digital::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<(), ()>;

View File

@@ -1,5 +1,5 @@
use crate::{config::TankConfig, hal::HAL}; use crate::alloc::string::{String, ToString};
use anyhow::Context; use crate::config::TankConfig;
use serde::Serialize; use serde::Serialize;
const OPEN_TANK_VOLTAGE: f32 = 3.0; const OPEN_TANK_VOLTAGE: f32 = 3.0;
@@ -150,21 +150,21 @@ impl TankState {
} }
} }
pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState { // pub fn determine_tank_state(board: &mut std::sync::MutexGuard<'_, HAL<'_>>) -> TankState {
if board.board_hal.get_config().tank.tank_sensor_enabled { // if board.board_hal.get_config().tank.tank_sensor_enabled {
match board // match board
.board_hal // .board_hal
.get_tank_sensor() // .get_tank_sensor()
.context("no sensor") // .context("no sensor")
.and_then(|f| f.tank_sensor_voltage()) // .and_then(|f| f.tank_sensor_voltage())
{ // {
Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv), // Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
Err(err) => TankState::Error(TankError::BoardError(err.to_string())), // Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
} // }
} else { // } else {
TankState::Disabled // TankState::Disabled
} // }
} // }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
/// Information structure send to mqtt for monitoring purposes /// Information structure send to mqtt for monitoring purposes

File diff suppressed because it is too large Load Diff