update: improve documentation and restructure code for modular hardware integration, add CAN communication to HAL, and update KiCad layouts
This commit is contained in:
@@ -12996,7 +12996,7 @@
|
|||||||
(drill 1)
|
(drill 1)
|
||||||
(layers "*.Cu" "*.Mask")
|
(layers "*.Cu" "*.Mask")
|
||||||
(remove_unused_layers no)
|
(remove_unused_layers no)
|
||||||
(net 44 "CONFIG2")
|
(net 22 "BOOT_SEL")
|
||||||
(pinfunction "Pin_3")
|
(pinfunction "Pin_3")
|
||||||
(pintype "passive")
|
(pintype "passive")
|
||||||
(uuid "7e1e9d83-af98-48df-9773-39ee7824a6f6")
|
(uuid "7e1e9d83-af98-48df-9773-39ee7824a6f6")
|
||||||
@@ -30814,6 +30814,14 @@
|
|||||||
(net 22)
|
(net 22)
|
||||||
(uuid "a0e70239-5b1c-4c3c-a476-64be66d07299")
|
(uuid "a0e70239-5b1c-4c3c-a476-64be66d07299")
|
||||||
)
|
)
|
||||||
|
(segment
|
||||||
|
(start 216.33 52.67)
|
||||||
|
(end 215.255 52.67)
|
||||||
|
(width 0.2)
|
||||||
|
(layer "F.Cu")
|
||||||
|
(net 22)
|
||||||
|
(uuid "a39be493-05ee-456d-b821-95927582c5ad")
|
||||||
|
)
|
||||||
(segment
|
(segment
|
||||||
(start 217.33 53.496)
|
(start 217.33 53.496)
|
||||||
(end 217.5 53.496)
|
(end 217.5 53.496)
|
||||||
@@ -30862,6 +30870,14 @@
|
|||||||
(net 22)
|
(net 22)
|
||||||
(uuid "f1e984ca-47cc-4a62-83f5-873ad9276c13")
|
(uuid "f1e984ca-47cc-4a62-83f5-873ad9276c13")
|
||||||
)
|
)
|
||||||
|
(segment
|
||||||
|
(start 215.255 52.67)
|
||||||
|
(end 215.08 52.845)
|
||||||
|
(width 0.2)
|
||||||
|
(layer "F.Cu")
|
||||||
|
(net 22)
|
||||||
|
(uuid "f6609741-7a8c-4e92-b2a7-a12cb5094a7e")
|
||||||
|
)
|
||||||
(segment
|
(segment
|
||||||
(start 217.7 53.496)
|
(start 217.7 53.496)
|
||||||
(end 215.731 53.496)
|
(end 215.731 53.496)
|
||||||
@@ -30878,6 +30894,14 @@
|
|||||||
(net 22)
|
(net 22)
|
||||||
(uuid "02ec00f7-269e-4eee-bd30-b44c72206ead")
|
(uuid "02ec00f7-269e-4eee-bd30-b44c72206ead")
|
||||||
)
|
)
|
||||||
|
(via
|
||||||
|
(at 216.33 52.67)
|
||||||
|
(size 0.8)
|
||||||
|
(drill 0.4)
|
||||||
|
(layers "F.Cu" "B.Cu")
|
||||||
|
(net 22)
|
||||||
|
(uuid "81681c98-2e02-4af5-8ed6-fab73d76574b")
|
||||||
|
)
|
||||||
(via
|
(via
|
||||||
(at 247.33 57.67)
|
(at 247.33 57.67)
|
||||||
(size 0.8)
|
(size 0.8)
|
||||||
@@ -30894,6 +30918,14 @@
|
|||||||
(net 22)
|
(net 22)
|
||||||
(uuid "668f3e0f-6796-4544-9fff-44be590ce44b")
|
(uuid "668f3e0f-6796-4544-9fff-44be590ce44b")
|
||||||
)
|
)
|
||||||
|
(segment
|
||||||
|
(start 210.28 47.97)
|
||||||
|
(end 211.63 47.97)
|
||||||
|
(width 0.2)
|
||||||
|
(layer "B.Cu")
|
||||||
|
(net 22)
|
||||||
|
(uuid "67850407-ea7d-4962-911e-4068f92ec668")
|
||||||
|
)
|
||||||
(segment
|
(segment
|
||||||
(start 241.08 51.42)
|
(start 241.08 51.42)
|
||||||
(end 247.33 57.67)
|
(end 247.33 57.67)
|
||||||
@@ -30902,6 +30934,14 @@
|
|||||||
(net 22)
|
(net 22)
|
||||||
(uuid "6f9fed78-f413-48d8-be42-a9dd34d1adfe")
|
(uuid "6f9fed78-f413-48d8-be42-a9dd34d1adfe")
|
||||||
)
|
)
|
||||||
|
(segment
|
||||||
|
(start 211.63 47.97)
|
||||||
|
(end 216.33 52.67)
|
||||||
|
(width 0.2)
|
||||||
|
(layer "B.Cu")
|
||||||
|
(net 22)
|
||||||
|
(uuid "9980ac60-b158-4195-ae96-1692b27bf812")
|
||||||
|
)
|
||||||
(segment
|
(segment
|
||||||
(start 245.88 48.17)
|
(start 245.88 48.17)
|
||||||
(end 246.48 48.17)
|
(end 246.48 48.17)
|
||||||
@@ -31574,30 +31614,6 @@
|
|||||||
(net 44)
|
(net 44)
|
||||||
(uuid "ea858ae5-b45b-4221-888a-e8a2838d0cf3")
|
(uuid "ea858ae5-b45b-4221-888a-e8a2838d0cf3")
|
||||||
)
|
)
|
||||||
(via
|
|
||||||
(at 221.98 51.37)
|
|
||||||
(size 0.8)
|
|
||||||
(drill 0.4)
|
|
||||||
(layers "F.Cu" "B.Cu")
|
|
||||||
(net 44)
|
|
||||||
(uuid "cbd287e0-ce8b-4721-9efe-d472968d1c74")
|
|
||||||
)
|
|
||||||
(segment
|
|
||||||
(start 218.58 47.97)
|
|
||||||
(end 221.98 51.37)
|
|
||||||
(width 0.2)
|
|
||||||
(layer "In2.Cu")
|
|
||||||
(net 44)
|
|
||||||
(uuid "3c7ed4a9-a06a-422f-aedb-2cd3dc699c8a")
|
|
||||||
)
|
|
||||||
(segment
|
|
||||||
(start 210.28 47.97)
|
|
||||||
(end 218.58 47.97)
|
|
||||||
(width 0.2)
|
|
||||||
(layer "In2.Cu")
|
|
||||||
(net 44)
|
|
||||||
(uuid "46e80a90-509c-4b27-bf19-e8d7851b0dd5")
|
|
||||||
)
|
|
||||||
(segment
|
(segment
|
||||||
(start 186.655 113.695)
|
(start 186.655 113.695)
|
||||||
(end 186.655 112.621)
|
(end 186.655 112.621)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"board": {
|
"board": {
|
||||||
"active_layer": 0,
|
"active_layer": 2,
|
||||||
"active_layer_preset": "All Layers",
|
"active_layer_preset": "All Layers",
|
||||||
"auto_track_width": false,
|
"auto_track_width": false,
|
||||||
"hidden_netclasses": [],
|
"hidden_netclasses": [],
|
||||||
|
|||||||
@@ -8333,28 +8333,6 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(global_label "CONFIG2"
|
|
||||||
(shape input)
|
|
||||||
(at 142.24 44.45 180)
|
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
(justify right)
|
|
||||||
)
|
|
||||||
(uuid "4aed43a7-6995-42f6-b313-69653f7d36d4")
|
|
||||||
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
|
||||||
(at 131.8051 44.45 0)
|
|
||||||
(effects
|
|
||||||
(font
|
|
||||||
(size 1.27 1.27)
|
|
||||||
)
|
|
||||||
(justify right)
|
|
||||||
(hide yes)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(global_label "3_3V"
|
(global_label "3_3V"
|
||||||
(shape input)
|
(shape input)
|
||||||
(at 224.79 57.15 0)
|
(at 224.79 57.15 0)
|
||||||
@@ -9147,6 +9125,28 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
(global_label "BOOT_SEL"
|
||||||
|
(shape input)
|
||||||
|
(at 142.24 44.45 180)
|
||||||
|
(fields_autoplaced yes)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(justify right)
|
||||||
|
)
|
||||||
|
(uuid "8202cf4c-8ab8-4793-979c-ae5015ef80e9")
|
||||||
|
(property "Intersheetrefs" "${INTERSHEET_REFS}"
|
||||||
|
(at 130.6562 44.45 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(justify right)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
(global_label "GND"
|
(global_label "GND"
|
||||||
(shape input)
|
(shape input)
|
||||||
(at 113.03 123.19 270)
|
(at 113.03 123.19 270)
|
||||||
|
|||||||
@@ -541,15 +541,15 @@ impl PlantHal {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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, config, esp)?
|
// initial_hal::create_initial_board(free_pins, config, esp)?
|
||||||
}
|
//}
|
||||||
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)
|
||||||
.await?
|
.await?;
|
||||||
}
|
//}
|
||||||
};
|
//};
|
||||||
|
|
||||||
HAL { board_hal }
|
HAL { board_hal }
|
||||||
}
|
}
|
||||||
@@ -566,11 +566,9 @@ impl PlantHal {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
HAL {
|
HAL {
|
||||||
board_hal: initial_hal::create_initial_board(
|
board_hal: v4_hal::create_v4(free_pins, esp, PlantControllerConfig::default(),
|
||||||
free_pins,
|
Box::new(NoBatteryMonitor {}), rtc_module)
|
||||||
PlantControllerConfig::default(),
|
.await?
|
||||||
esp,
|
|
||||||
)?,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use embassy_time::{Duration, Timer, WithTimeout};
|
|||||||
use embedded_can::{Frame, Id};
|
use embedded_can::{Frame, Id};
|
||||||
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
|
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
|
||||||
use esp_hal::i2c::master::I2c;
|
use esp_hal::i2c::master::I2c;
|
||||||
|
use esp_hal::peripherals;
|
||||||
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode};
|
use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration, TwaiMode};
|
||||||
use esp_hal::{twai, Async, Blocking};
|
use esp_hal::{twai, Async, Blocking};
|
||||||
use ina219::address::{Address, Pin};
|
use ina219::address::{Address, Pin};
|
||||||
@@ -129,7 +130,9 @@ pub struct V4<'a> {
|
|||||||
pump_ina: Option<
|
pump_ina: Option<
|
||||||
SyncIna219<I2cDevice<'a, CriticalSectionRawMutex, I2c<'static, Blocking>>, UnCalibrated>,
|
SyncIna219<I2cDevice<'a, CriticalSectionRawMutex, I2c<'static, Blocking>>, UnCalibrated>,
|
||||||
>,
|
>,
|
||||||
twai_config: Option<TwaiConfiguration<'static, Blocking>>,
|
twai_peripheral: Option<esp_hal::peripherals::TWAI0<'static>>,
|
||||||
|
twai_rx_pin: Option<esp_hal::peripherals::GPIO2<'static>>,
|
||||||
|
twai_tx_pin: Option<esp_hal::peripherals::GPIO0<'static>>,
|
||||||
can_power: Output<'static>,
|
can_power: Output<'static>,
|
||||||
|
|
||||||
extra1: Output<'a>,
|
extra1: Output<'a>,
|
||||||
@@ -150,6 +153,12 @@ pub(crate) async fn create_v4(
|
|||||||
let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
|
let mut general_fault = Output::new(peripherals.gpio23, Level::Low, OutputConfig::default());
|
||||||
general_fault.set_low();
|
general_fault.set_low();
|
||||||
|
|
||||||
|
let twai_peripheral = Some(peripherals.twai);
|
||||||
|
|
||||||
|
|
||||||
|
let twai_rx_pin = Some(peripherals.gpio2);
|
||||||
|
let twai_tx_pin = Some(peripherals.gpio0);
|
||||||
|
|
||||||
let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
|
let extra1 = Output::new(peripherals.gpio6, Level::Low, OutputConfig::default());
|
||||||
let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
|
let extra2 = Output::new(peripherals.gpio15, Level::Low, OutputConfig::default());
|
||||||
|
|
||||||
@@ -169,19 +178,13 @@ pub(crate) async fn create_v4(
|
|||||||
peripherals.pcnt1,
|
peripherals.pcnt1,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let twai_config = Some(TwaiConfiguration::new(
|
|
||||||
peripherals.twai,
|
|
||||||
peripherals.gpio2,
|
|
||||||
peripherals.gpio0,
|
|
||||||
TWAI_BAUDRATE,
|
|
||||||
TwaiMode::Normal,
|
|
||||||
));
|
|
||||||
let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default());
|
let can_power = Output::new(peripherals.gpio22, Level::Low, OutputConfig::default());
|
||||||
|
|
||||||
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
|
let solar_is_day = Input::new(peripherals.gpio7, InputConfig::default());
|
||||||
let light = Output::new(peripherals.gpio10, Level::Low, Default::default());
|
let light = Output::new(peripherals.gpio10, Level::Low, Default::default());
|
||||||
let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default());
|
let charge_indicator = Output::new(peripherals.gpio3, Level::Low, Default::default());
|
||||||
|
|
||||||
|
info!("Start pump expander");
|
||||||
let pump_device = I2cDevice::new(I2C_DRIVER.get().await);
|
let pump_device = I2cDevice::new(I2C_DRIVER.get().await);
|
||||||
let mut pump_expander = Pca9535Immediate::new(pump_device, 32);
|
let mut pump_expander = Pca9535Immediate::new(pump_device, 32);
|
||||||
for pin in 0..8 {
|
for pin in 0..8 {
|
||||||
@@ -191,6 +194,7 @@ pub(crate) async fn create_v4(
|
|||||||
let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin);
|
let _ = pump_expander.pin_set_low(GPIOBank::Bank1, pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Start mppt");
|
||||||
let mppt_current = I2cDevice::new(I2C_DRIVER.get().await);
|
let mppt_current = I2cDevice::new(I2C_DRIVER.get().await);
|
||||||
let mppt_ina = match SyncIna219::new(mppt_current, Address::from_pins(Pin::Vcc, Pin::Gnd)) {
|
let mppt_ina = match SyncIna219::new(mppt_current, Address::from_pins(Pin::Vcc, Pin::Gnd)) {
|
||||||
Ok(mut ina) => {
|
Ok(mut ina) => {
|
||||||
@@ -211,6 +215,7 @@ pub(crate) async fn create_v4(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
info!("Start pump current sensor");
|
||||||
let pump_current_dev = I2cDevice::new(I2C_DRIVER.get().await);
|
let pump_current_dev = I2cDevice::new(I2C_DRIVER.get().await);
|
||||||
let pump_ina = match SyncIna219::new(pump_current_dev, Address::from_pins(Pin::Gnd, Pin::Sda)) {
|
let pump_ina = match SyncIna219::new(pump_current_dev, Address::from_pins(Pin::Gnd, Pin::Sda)) {
|
||||||
Ok(ina) => Some(ina),
|
Ok(ina) => Some(ina),
|
||||||
@@ -252,15 +257,37 @@ pub(crate) async fn create_v4(
|
|||||||
config,
|
config,
|
||||||
battery_monitor,
|
battery_monitor,
|
||||||
pump_ina,
|
pump_ina,
|
||||||
twai_config,
|
twai_peripheral,
|
||||||
|
twai_rx_pin,
|
||||||
|
twai_tx_pin,
|
||||||
charger,
|
charger,
|
||||||
extra1,
|
extra1,
|
||||||
extra2,
|
extra2,
|
||||||
can_power
|
can_power,
|
||||||
};
|
};
|
||||||
Ok(Box::new(v))
|
Ok(Box::new(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> V4<'a> {
|
||||||
|
fn teardown_twai(&mut self, old: TwaiConfiguration<Blocking>) {
|
||||||
|
drop(old);
|
||||||
|
// Re-acquire the peripheral and pins
|
||||||
|
let twai = unsafe { peripherals::TWAI0::steal() };
|
||||||
|
let rx_pin = unsafe { peripherals::GPIO2::steal() };
|
||||||
|
let tx_pin = unsafe { peripherals::GPIO0::steal() };
|
||||||
|
|
||||||
|
// Set pins to low to avoid parasitic powering
|
||||||
|
let mut rx = Input::new(rx_pin, InputConfig::default().with_pull(Pull::None));
|
||||||
|
let mut tx = Input::new(tx_pin, InputConfig::default().with_pull(Pull::None));
|
||||||
|
|
||||||
|
// Release the pins from Output back to raw pins and store everything
|
||||||
|
self.twai_peripheral = Some(twai);
|
||||||
|
self.twai_rx_pin = Some(unsafe { peripherals::GPIO2::steal() });
|
||||||
|
self.twai_tx_pin = Some(unsafe { peripherals::GPIO0::steal() });
|
||||||
|
self.can_power.set_low();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl<'a> BoardInteraction<'a> for V4<'a> {
|
impl<'a> BoardInteraction<'a> for V4<'a> {
|
||||||
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
|
fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError> {
|
||||||
@@ -352,8 +379,14 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
}
|
}
|
||||||
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
async fn measure_moisture_hz(&mut self) -> FatResult<Moistures> {
|
||||||
self.can_power.set_high();
|
self.can_power.set_high();
|
||||||
let config = self.twai_config.take().expect("twai config not set");
|
let twai_config = TwaiConfiguration::new(
|
||||||
let mut twai = config.into_async().start();
|
self.twai_peripheral.take().unwrap(),
|
||||||
|
self.twai_rx_pin.take().unwrap(),
|
||||||
|
self.twai_tx_pin.take().unwrap(),
|
||||||
|
TWAI_BAUDRATE,
|
||||||
|
TwaiMode::Normal,
|
||||||
|
);
|
||||||
|
let mut twai = twai_config.into_async().start();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let rec = twai.receive();
|
let rec = twai.receive();
|
||||||
@@ -372,11 +405,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
|
let _ = wait_for_can_measurements(&mut twai, &mut moistures)
|
||||||
.with_timeout(Duration::from_millis(2000))
|
.with_timeout(Duration::from_millis(2000))
|
||||||
.await;
|
.await;
|
||||||
|
self.teardown_twai(twai.stop().into_blocking());
|
||||||
self.can_power.set_low();
|
|
||||||
|
|
||||||
let config = twai.stop().into_blocking();
|
|
||||||
self.twai_config.replace(config);
|
|
||||||
|
|
||||||
Ok(moistures)
|
Ok(moistures)
|
||||||
}
|
}
|
||||||
@@ -445,9 +474,17 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn detect_sensors(&mut self) -> FatResult<DetectionResult> {
|
async fn detect_sensors(&mut self) -> FatResult<DetectionResult> {
|
||||||
// Power on CAN transceiver and start controller
|
// Power on CAN transceiver and start controller
|
||||||
self.can_power.set_high();
|
self.can_power.set_high();
|
||||||
let config = self.twai_config.take().expect("twai config not set");
|
let twai_config = TwaiConfiguration::new(
|
||||||
|
self.twai_peripheral.take().unwrap(),
|
||||||
|
self.twai_rx_pin.take().unwrap(),
|
||||||
|
self.twai_tx_pin.take().unwrap(),
|
||||||
|
TWAI_BAUDRATE,
|
||||||
|
TwaiMode::Normal,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
info!("convert can");
|
info!("convert can");
|
||||||
let mut as_async = config.into_async().start();
|
let mut as_async = twai_config.into_async().start();
|
||||||
// Give CAN some time to stabilize
|
// Give CAN some time to stabilize
|
||||||
Timer::after_millis(10).await;
|
Timer::after_millis(10).await;
|
||||||
|
|
||||||
@@ -487,8 +524,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let config = as_async.stop().into_blocking();
|
let config = as_async.stop().into_blocking();
|
||||||
self.can_power.set_low();
|
self.teardown_twai(config);
|
||||||
self.twai_config.replace(config);
|
|
||||||
|
|
||||||
let result = moistures.into();
|
let result = moistures.into();
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,6 @@ fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<L
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
website/.idea/website.iml
generated
5
website/.idea/website.iml
generated
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="JAVA_MODULE" version="4">
|
<module type="EMPTY_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
<component name="NewModuleRootManager">
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$" />
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ draft: false
|
|||||||
description: "a description"
|
description: "a description"
|
||||||
tags: ["battery", "bq34z100"]
|
tags: ["battery", "bq34z100"]
|
||||||
---
|
---
|
||||||
# BatteryManagment
|
# Battery Management Module
|
||||||
The project contains an additional companion board, with a bq34z100 battery management ic.
|
The project contains an additional companion board (Fuel Gauge), with a bq34z100 battery management IC.
|
||||||
|
|
||||||
It allows to track the health and charge for an external battery and is supposed to be soldered directly to the battery.
|
It allows to track the health and charge for an external battery and is supposed to be soldered directly to the battery.
|
||||||
The main board contains a xt30 connector for power, and additionally a two pin I2C bus next/combined with it, to communicate with the BatteryManagement.
|
The MainBoard contains a connector for power, and additionally a two-pin I2C bus to communicate with the Battery Management module.
|
||||||
|
|
||||||
|
<!-- TODO: Add photo of the new modular Battery Management board -->
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
{{< alert >}}
|
{{< alert >}}
|
||||||
@@ -28,7 +30,7 @@ A protected Battery is required. There is only a very simplistic output voltage
|
|||||||
2. Then the data/register for low voltage flash write protection should be set to 0V, as else with the voltage divider and no further configuration, the IC will refuse all write requests.
|
2. Then the data/register for low voltage flash write protection should be set to 0V, as else with the voltage divider and no further configuration, the IC will refuse all write requests.
|
||||||
3. After this the supplied golden image can be used, it will setup the battery for 6Ah and a 4S lifepo. Different values can be adjusted after this to the users liking.
|
3. After this the supplied golden image can be used, it will setup the battery for 6Ah and a 4S lifepo. Different values can be adjusted after this to the users liking.
|
||||||
{{< alert >}}
|
{{< alert >}}
|
||||||
The main board, does not care or process any of the charge discharge limits that can be set. Ensure that the battery can supply enough current as well as accept a 2A charging current from the MPPT system.
|
The main board, does not care or process any of the charge discharge limits that can be set. Ensure that the battery can supply enough current as well as accept a 2.4A charging current from the MPPT system.
|
||||||
{{< /alert >}}
|
{{< /alert >}}
|
||||||
|
|
||||||
The golden image sets the statups led up, to be in blinky mode. one very long interval means, that the battery is pretty much full. A few very short flashes mean that the battery is nearly empty. No light means, that the battery is in discharge protection and shut down.
|
The golden image sets the statups led up, to be in blinky mode. one very long interval means, that the battery is pretty much full. A few very short flashes mean that the battery is nearly empty. No light means, that the battery is in discharge protection and shut down.
|
||||||
|
|||||||
@@ -5,22 +5,39 @@ draft: false
|
|||||||
description: "a description"
|
description: "a description"
|
||||||
tags: ["MPPT", "solar"]
|
tags: ["MPPT", "solar"]
|
||||||
---
|
---
|
||||||
# MPPT
|
# MPPT Module
|
||||||
If the board is powered by solar, a on board MPPT converter tries to track the best current and voltage, so that the battery gets maximized charging current.
|
If the system is powered by solar, a dedicated MPPT (Maximum Power Point Tracking) module can be added. It tries to track the best current and voltage, so that the battery gets maximized charging current.
|
||||||
|
|
||||||
The currently used MPPT limits max current to 2A.
|
The currently used MPPT module is based on the CN3795 and limits max current to 2.4A.
|
||||||
|
|
||||||
There are two adjustable values for the MPPT, Input Power Point and Output Voltage.
|
<!-- TODO: Add photo of the new modular MPPT board -->
|
||||||
|
|
||||||
|
The module has two configurable values: **Input Power Point (MPPT)** and **Output Voltage (Battery Charge Voltage)**. These are set using fixed resistors rather than potentiometers to ensure stability and reduce costs.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
The voltages are determined by voltage dividers. The default values are:
|
||||||
|
- **MPPT Voltage:** ~17.1V (set by R2 and R5)
|
||||||
|
- **Battery Voltage:** ~14.7V (set by R8 and R1)
|
||||||
|
|
||||||
|
#### Formulas
|
||||||
|
The reference voltage for the CN3795 is 1.205V.
|
||||||
|
- $V_{MPPT} = 1.205 \times (1 + \frac{R2}{R5})$
|
||||||
|
- $V_{BAT} = 1.205 \times (1 + \frac{R8}{R1})$
|
||||||
|
|
||||||
|
Default resistor values:
|
||||||
|
- $R1, R5 = 2.491k\Omega$
|
||||||
|
- $R2 = 33k\Omega$
|
||||||
|
- $R8 = 27k\Omega$
|
||||||
|
|
||||||
|
### Status LEDs
|
||||||
|
- **D4 (Red):** Charging
|
||||||
|
- **D5 (Green):** Done / Standby
|
||||||
|
|
||||||
#Setup
|
#Setup
|
||||||
{{< alert >}}
|
{{< alert >}}
|
||||||
Note: Only protected Batteries are supported!
|
Note: Only protected Batteries are supported!
|
||||||
{{< /alert >}}
|
{{< /alert >}}
|
||||||
1. Supply the Ext Charge connector with 20V and a limited current (suggested 100mA).
|
1. **Verify Resistors:** Before connecting, ensure that R2 and R8 match your solar panel's MPPT voltage and your battery's required charging voltage.
|
||||||
2. If the board does not startup, adjust the MPPT untill it supplies a output voltage. Note: The ESP should be able to startup at any settable output voltage, as it uses a converter to 3.3v anyways.
|
2. **Initial Test:** Supply the Ext Charge connector with a voltage higher than the MPPT setpoint (e.g., 20V) and a limited current (suggested 100mA).
|
||||||
3. Adjust the Output Voltage to the correct charging voltage for your Chosen Battery.
|
3. **Check Output:** Measure the output voltage at the battery connector. It should match the calculated $V_{BAT}$.
|
||||||
4. Connect a sink (or the a non fully charged battery) to the Output, now adjust the MPPT so, that the Input Voltage drops to the optimal working Voltage of your Solar panel. The Converter will try to always keep the Panel at this Voltage by adjusting the Output Current accordingly.
|
4. **Connect Battery:** Once verified, you can connect the battery and the solar panel.
|
||||||
|
|
||||||
|
|
||||||
Setting the MPPT slightly lower than the suggested Voltage will ensure more reliable operation, as the optimal voltage might drop a bit due to ageing and dust on the panel. Normally during summer there is more than enough power, so a slight inefficentcy won't be noticable.
|
|
||||||
However during winter in low light and bad weather conditions, that might be the difference between charging at all or no power. (If the panel does not reach the MPPT voltage, there is no charging current)
|
|
||||||
|
|||||||
@@ -5,11 +5,36 @@ draft: false
|
|||||||
description: "a description"
|
description: "a description"
|
||||||
tags: ["nightlight"]
|
tags: ["nightlight"]
|
||||||
---
|
---
|
||||||
# Nightlight
|
# Nightlight Module
|
||||||
The board has a adjustable light output, which can be used to power a small led strip.
|
The system supports a dedicated light output module, which can be used to power a small LED strip for night lighting or growth support.
|
||||||
In the configuration, the working hours can be set, as well as if the light is allowed to be turned on, even if the solar panel still supplies power.
|
|
||||||
The supply should theoretically be able to supply up to 2A with a voltage lower than the battery.
|
|
||||||
|
|
||||||
|
The module is based on the **AP63200** synchronous buck converter. It provides an adjustable output voltage, allowing it to drive various types of LED strips or other low-voltage DC loads.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
The output voltage is adjustable via a potentiometer (**RV1**). This allows you to set the brightness or the correct operating voltage for your LED strip.
|
||||||
|
|
||||||
|
#### Formula
|
||||||
|
The reference voltage for the AP63200 is 0.8V. The output voltage is determined by the feedback divider:
|
||||||
|
- $V_{OUT} = 0.8 \times (1 + \frac{R_{high}}{R5})$
|
||||||
|
- $R_{high} = R1 + RV1 + R3$
|
||||||
|
|
||||||
|
Default values:
|
||||||
|
- $R1 = 10k\Omega$
|
||||||
|
- $R3 = 1k\Omega$
|
||||||
|
- $R5 = 5k\Omega$
|
||||||
|
- $RV1 = 0\Omega$ to $50k\Omega$ (Potentiometer)
|
||||||
|
|
||||||
|
With these values, the output voltage is adjustable from approximately **1.76V** to **9.76V**.
|
||||||
|
|
||||||
|
### Status LED
|
||||||
|
- **I1 (White):** Indicates that the light output is enabled.
|
||||||
|
|
||||||
|
# Setup
|
||||||
{{< alert >}}
|
{{< alert >}}
|
||||||
Ensure the voltage is correctly set, before connecting a load the first time!
|
Ensure the voltage is correctly set using **RV1** before connecting a load for the first time! High voltages can damage LED strips.
|
||||||
{{< /alert >}}
|
{{< /alert >}}
|
||||||
|
|
||||||
|
1. **Power On:** Enable the light output through the software interface.
|
||||||
|
2. **Measure Voltage:** Use a multimeter to measure the voltage at the output connector (**LIGHT1**).
|
||||||
|
3. **Adjust RV1:** Turn the potentiometer until you reach the desired voltage or brightness.
|
||||||
|
4. **Connect Load:** Once the voltage is set correctly, you can connect your LED strip.
|
||||||
@@ -5,21 +5,38 @@ draft: false
|
|||||||
description: "a description"
|
description: "a description"
|
||||||
tags: ["sensor"]
|
tags: ["sensor"]
|
||||||
---
|
---
|
||||||
|
# Sensors & Pumps Module
|
||||||
|
|
||||||
|
This functionality is now provided by dedicated modules that can be connected to the MainBoard.
|
||||||
|
|
||||||
# Sensors
|
# Sensors
|
||||||
The board can address up to 16 sensors, with an A und B sensor per plant.
|
The moisture sensing functionality is handled by a dedicated **CAN bus-based Sensor Module**. This modular approach allows for better scalability and reduces electrical interference by moving the measurement logic closer to the sensors and using digital communication.
|
||||||
The A sensor is always used, the B sensor is optional and use is suggested for larger planters.
|
|
||||||
|
|
||||||
The sensor itself are just two spikes with a defined distance. They can be bought readymade or simply be done DIY with two long nails (use galvanized or non rusting ones)
|
## Sensor Module (CAN bus)
|
||||||
|
The standard sensor module features its own **CH32V203 RISC-V microcontroller**, which handles the measurement of soil moisture and communicates the results back to the MainBoard via the CAN bus.
|
||||||
|
|
||||||
The Sensor is switched via multiplexer chips to a 555 oscilator circuit that uses he the earths resistance for charging and discharging a small capacitor. The ESP simply counts how often the charge pulse was seen.
|
* **Capacity:** Supports up to 16 sensors (typically 8 plants with an A and B sensor each).
|
||||||
While this sensor can be easily influenced since it runs in lower uA - mA range, due to the frequencys of several khz for normal moist plants, it is still very resistant, eg a 50hz failure signal from a nearbly power circuit would barely be registered.
|
* **Reliability:** Digital communication via CAN bus ensures data integrity even over longer cable runs and in electrically noisy environments.
|
||||||
Since the Sensor is changing polarity between charging and discharging, corrosion due to organic battery effects is minimized, also it prevents errors due having build a battery (You can easily charge a planter to several V if you do not change polarity).
|
* **Addressing:** The A sensor is always used; the B sensor is optional and suggested for larger planters to provide a better average of the soil moisture.
|
||||||
|
|
||||||
|
## Sensor Hardware
|
||||||
|
The sensors themselves remain simple and cost-effective:
|
||||||
|
* **Design:** Two spikes with a defined distance.
|
||||||
|
* **DIY Friendly:** Can be bought readymade or easily made with two long nails (galvanized or stainless steel suggested to prevent rusting).
|
||||||
|
|
||||||
|
## Measurement Principle
|
||||||
|
The new CAN-based sensor module uses a sophisticated measurement technique that replaces the outdated 555-oscillator and multiplexer design. By using a dedicated MCU for measurement:
|
||||||
|
* **Minimized Corrosion:** The system changes polarity between measurements, minimizing corrosion due to organic battery effects (electrolysis) and preventing errors caused by building up a DC voltage in the soil.
|
||||||
|
* **Interference Resistance:** The measurement is resistant to common failure signals, such as 50Hz hum from nearby power circuits.
|
||||||
|
* **Digital Accuracy:** The local MCU processes the analog signals and sends precise digital values to the MainBoard.
|
||||||
|
|
||||||
# Pumps
|
# Pumps
|
||||||
The board contains 8 low side switched pump outputs. The pumps are running directly from the battery without further voltage conversion, so ensure that the can survive the full voltage range of the battery.
|
The Pump module contains low side switched pump outputs. The pumps are running directly from the battery without further voltage conversion, so ensure that they can survive the full voltage range of the battery.
|
||||||
Each output can supply up to 3A continously.
|
Each output can supply up to 3A continously.
|
||||||
The board will never switch more than one output concurrently, so there is no need to size the battery for higher maximum load.
|
The board will never switch more than one output concurrently, so there is no need to size the battery for higher maximum load.
|
||||||
An additinal extra out exists, that is switched, when any of the pump outputs is supposed to run.
|
An additinal extra output exists, that is switched when any of the pump outputs is supposed to run.
|
||||||
|
|
||||||
|
<!-- TODO: Add photo of the new modular Pump and Sensor boards -->
|
||||||
|
|
||||||
This allows for multiple possible setups
|
This allows for multiple possible setups
|
||||||
## Layout Central Pump
|
## Layout Central Pump
|
||||||
|
|||||||
@@ -11,18 +11,35 @@ tags: ["esp32", "hardware"]
|
|||||||
<img src="pcb_back.png" class="grid-w50" />
|
<img src="pcb_back.png" class="grid-w50" />
|
||||||
{{< /gallery >}}
|
{{< /gallery >}}
|
||||||
|
|
||||||
|
<!-- TODO: Add new screenshots of the modular PCB setup -->
|
||||||
|
|
||||||
{{< gitea server="https://git.mannheim.ccc.de/" repo="C3MA/PlantCtrl" >}}
|
{{< gitea server="https://git.mannheim.ccc.de/" repo="C3MA/PlantCtrl" >}}
|
||||||
|
|
||||||
## ESP32-C6
|
## Modular Design
|
||||||
* RiscV
|
The PlantCtrl hardware has been redesigned from a single large PCB into a more flexible, modular system. This allows users to only build or use the components they actually need for their specific setup.
|
||||||
* Fully done in Kicad (#schematics here link)
|
|
||||||
* Supports 16 Moisture Sensors (two per plant)
|
The system now consists of a **MainBoard** which acts as the controller and several pluggable or connectable modules.
|
||||||
* Support for 8 Pumps/Valves
|
|
||||||
* Support for main Pump/Valve
|
## MainBoard (ESP32-C6)
|
||||||
* All Pump/Valve outputs are 7-24V based on supplied Voltage
|
* **Processor:** ESP32-C6 (RiscV)
|
||||||
* Use of efficient DC-DC Converters for 3.3V and for Led-Nightlight
|
* **Connectivity:** WiFi, Bluetooth, Zigbee/Thread support
|
||||||
* Embedded Solar MPPT Charger
|
* **Form Factor:** Compact main board with headers for modules
|
||||||
* Can also be used with a power supply
|
* **Fully Open Source:** Designed in KiCad
|
||||||
|
|
||||||
|
## Available Modules
|
||||||
|
* **MPPT Charger:** Efficient solar charging for batteries.
|
||||||
|
* **Pump Driver:** High-current outputs for pumps and valves.
|
||||||
|
* **Sensor Interface:** Support for multiple moisture sensors.
|
||||||
|
* **Light Controller:** For LED nightlights or growth lights.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
* **Moisture Sensors:** Supports multiple capacitive or resistive sensors via expansion modules.
|
||||||
|
* **Pumps/Valves:** Support for multiple independent watering zones.
|
||||||
|
* **Power:**
|
||||||
|
* Solar powered with MPPT
|
||||||
|
* Battery powered with optional Battery Management (Fuel Gauge)
|
||||||
|
* Can also be used with a standard power supply (7-24V)
|
||||||
|
* **Efficient Power:** Use of high-efficiency DC-DC converters for 3.3V and peripherals.
|
||||||
|
|
||||||
## Made with:
|
## Made with:
|
||||||
<a href="https://www.kicad.org/">
|
<a href="https://www.kicad.org/">
|
||||||
|
|||||||
@@ -5,24 +5,47 @@ draft: false
|
|||||||
description: "a description"
|
description: "a description"
|
||||||
tags: ["firmeware", "upload"]
|
tags: ["firmeware", "upload"]
|
||||||
---
|
---
|
||||||
# Prebuild
|
# From Source
|
||||||
1. Download image from
|
|
||||||
2. todo something espflash tool here
|
|
||||||
|
|
||||||
# From source
|
|
||||||
## Preconditions
|
## Preconditions
|
||||||
* rustup with current version
|
* **Rust:** Current version of `rustup`.
|
||||||
* espup with current version
|
* **ESP32 Toolchain:** `espup` installed and configured.
|
||||||
* npm /npx
|
* **espflash:** Installed via `cargo install espflash`.
|
||||||
* Connect the board via usb to the computer
|
* **Node.js:** `npm` installed (for the web interface).
|
||||||
* Ensure the esp is running (eg not in deepsleep if prior version was installed)
|
|
||||||
# Compiling and uploading
|
|
||||||
1. Clone the gitea
|
|
||||||
2. go to the rust/src_webroot
|
|
||||||
3. npm -i
|
|
||||||
4. cd ..
|
|
||||||
5. cargo run
|
|
||||||
|
|
||||||
Depending on the setup of the cargo runner, either a OTA image is build or the firmware is directly flashed to the esp.
|
## Flashing via USB
|
||||||
|
1. Connect the MainBoard to your computer via USB.
|
||||||
|
2. Ensure the board is powered and not in deep sleep.
|
||||||
|
3. Build the web interface (only required once or if you changed something in `src_webpack`):
|
||||||
|
```bash
|
||||||
|
cd Software/MainBoard/rust/src_webpack
|
||||||
|
npm install
|
||||||
|
npx webpack
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
4. Flash the firmware:
|
||||||
|
```bash
|
||||||
|
espflash flash --monitor --partition-table partitions.csv
|
||||||
|
```
|
||||||
|
*Note: If the flashing fails, you might need to put the ESP32-C6 into bootloader mode by holding the BOOT button while resetting or connecting USB.*
|
||||||
|
|
||||||
Note: If the bootloader is currently booting from ota_2, flashing ota_1 via usb will not make it switch. You might need to manually erase it in this case prior.
|
### Simplified Flashing with Scripts
|
||||||
|
You can use the provided bash scripts to automate the build and flash process:
|
||||||
|
* **`./flash.sh`**: Cleans temporary files, builds the web interface, compiles the firmware, and flashes it to the board.
|
||||||
|
* **`./all.sh`**: Similar to `flash.sh`, but also saves a local `image.bin` of the firmware.
|
||||||
|
|
||||||
|
## OTA (Over-the-Air) Update
|
||||||
|
You can also update the firmware wirelessly if the system is already running and connected to your network.
|
||||||
|
|
||||||
|
1. Generate the OTA binary:
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
2. The binary will be at `target/riscv32imac-unknown-none-elf/release/plant-ctrl2`.
|
||||||
|
3. Open the PlantCtrl web interface in your browser.
|
||||||
|
4. Navigate to the **OTA** section.
|
||||||
|
5. Upload the `plant-ctrl2` file.
|
||||||
|
6. The system will reboot into the new firmware once the upload and verification are complete.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
* **Bootloader Partition:** If the bootloader is currently booting from `ota_2`, flashing to the factory partition via USB might not automatically switch the active partition. You can use `espflash erase-parts otadata` to reset the OTA state.
|
||||||
|
* **Serial Terminal:** Use `espflash monitor` to view the serial logs for debugging.
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ Connect to the AccessPoint and browse to http://192.168.71.1 to set it up
|
|||||||
3. The ESP will now try to connect to a WIFI station if configured and start the webserver, if the connection to the station cannot be made, it will open an AccessPoint instead.
|
3. The ESP will now try to connect to a WIFI station if configured and start the webserver, if the connection to the station cannot be made, it will open an AccessPoint instead.
|
||||||
|
|
||||||
# MQTT
|
# MQTT
|
||||||
It is possible to use the topic /stay/alive to enter the config mode.
|
It is possible to use the MQTT topic `{base_topic}/stay_alive` to enter the config mode remotely.
|
||||||
|
|
||||||
If this topic contains a retained message with the value true, the ESP will enter ConfigMode during it's next cycle and stay there.
|
If this topic contains a retained message with the value `true` or `1`, the ESP will enter ConfigMode during its next cycle and stay there.
|
||||||
|
|
||||||
Note: You must set /stay/alive to retained false again after this, otherwise it will always enter configmode, even if you exist it via the webui.
|
Note: You must set `{base_topic}/stay_alive` to `false` or `0` (or clear the retained message) after this, otherwise it will always enter configmode, even if you exit it via the webui.
|
||||||
|
|
||||||
# USB reboot
|
# USB reboot
|
||||||
By connecting to the USB Terminal via a Serial Monitor, the ESP is reset and will enter configmode as if the button for normal operation was pressed
|
By connecting to the USB Terminal via a Serial Monitor, the ESP is reset and will enter configmode as if the button for normal operation was pressed
|
||||||
|
|||||||
@@ -6,22 +6,84 @@ description: "a description"
|
|||||||
tags: ["mqtt", "esp"]
|
tags: ["mqtt", "esp"]
|
||||||
---
|
---
|
||||||
# MQTT
|
# MQTT
|
||||||
An Mqtt server can be configured and will be used to dump all kinds of statistical data.
|
A configured MQTT server will receive statistical and status data from the controller.
|
||||||
|
|
||||||
|
### Topics
|
||||||
|
|
||||||
| Topic | Example | Description |
|
| Topic | Example | Description |
|
||||||
|/firmware/address|
|
|-------|---------|-------------|
|
||||||
|
| `firmware/address` | `192.168.1.2` | IP address in station mode |
|
||||||
|
| `firmware/state` | `VersionInfo { ... }` | Debug information about the current firmware and OTA slots |
|
||||||
|
| `firmware/last_online` | `2025-01-22T08:56:46.664+01:00` | Last time the board was online |
|
||||||
|
| `state` | `online` | Current state of the controller |
|
||||||
|
| `mppt` | `{"current_ma":1200,"voltage_ma":18500}` | MPPT charging metrics |
|
||||||
|
| `battery` | `{"Info":{"voltage_milli_volt":12860,"average_current_milli_ampere":-16,...}}` | Battery health and charge data |
|
||||||
|
| `water` | `{"enough_water":true,"warn_level":false,"left_ml":1337,...}` | Water tank status |
|
||||||
|
| `plant{1-8}` | `{"sensor_a":...,"sensor_b":...,"mode":"TargetMoisture",...}` | Detailed status for each plant slot |
|
||||||
|
| `pump{1-8}` | `{"enabled":true,"pump_ineffective":false,...}` | Metrics for the last pump activity |
|
||||||
|
| `light` | `{"enabled":true,"active":true,...}` | Night light status |
|
||||||
|
| `deepsleep` | `night 1h` | Why and how long the ESP will sleep |
|
||||||
|
|
||||||
| Topic | Example | Description |
|
### Data Structures
|
||||||
|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|
|
|
||||||
| firmware/address | 192.168.1.2 | The Ip address in station mode |
|
#### Firmware State (`firmware/state`)
|
||||||
| firmware/githash | feature/esp32c6@1ce4d74a | The branch and hash during build time |
|
Contains a debug dump of the `VersionInfo` struct:
|
||||||
| firmware/buildtime | 2025-01-21T20:56:18.168163570Z | Compile time |
|
- `git_hash`: Branch and commit hash
|
||||||
| firmware/last_online | 2025-01-22T08:56:46.664+01:00 | Last time this board was online |
|
- `build_time`: Compilation timestamp
|
||||||
| firmware/ota_state | Partition state is ESP_OTA_IMG_VALID | The OTA state, relevant for rollback |
|
- `current`: Current running partition
|
||||||
| firmware/partition_address | 0x10000 | The OTA partition used, 0x10000 is ota_1 |
|
- `slot0_state`: State of OTA slot 0
|
||||||
| state | online | Current State, expected are online or sleep |
|
- `slot1_state`: State of OTA slot 1
|
||||||
| battery | {<br/>"voltage_milli_volt":"12860",<br/>"current_milli_ampere":"-16",<br/>"cycle_count":"12",<br/>"design_milli_ampere":"6000",<br/>"remaining_milli_ampere":"806",<br/>"state_of_charge":"15",<br/>"state_of_health":"93",<br/>"temperature":"2957"<br/>} | Dump of battery data |
|
|
||||||
| water | {<br/>"enough_water":true,<br/>"warn_level":false,<br/>"left_ml":1337,<br/>"sensor_error":false,<br/>"raw":0,"water_frozen":<br/>"tank sensor error"<br/>} | Water Status dump |
|
#### MPPT (`mppt`)
|
||||||
| plant1 | {<br/>"a":"disabled",<br/>"a_raw":"0",<br/>"b":"disabled",<br/>"b_raw":"0",<br/>"mode":"OFF",<br/>"consecutive_pump_count":0,<br/>"dry":false,<br/>"active":false,<br/>"pump_error":false,<br/>"not_effective":false,<br/>"cooldown":false,<br/>"out_of_work_hour":false,<br/>"last_pump":"N/A",<br/>"next_pump":"N/A"<br/>} | Plant status dump |
|
- `current_ma`: Solar charging current in mA
|
||||||
| light | {<br/>"active":false,<br/>"out_of_work_hour":true,<br/>"battery_low":true,<br/>"is_day":false<br/>} | Light status dump |
|
- `voltage_ma`: Solar panel voltage in mV
|
||||||
| deepsleep | night 1h | Why and how long the ESP will sleep |
|
|
||||||
|
#### Battery (`battery`)
|
||||||
|
Can be `"Unknown"` or an `Info` object:
|
||||||
|
- `voltage_milli_volt`: Battery voltage
|
||||||
|
- `average_current_milli_ampere`: Current draw/charge
|
||||||
|
- `design_milli_ampere_hour`: Battery capacity
|
||||||
|
- `remaining_milli_ampere_hour`: Remaining capacity
|
||||||
|
- `state_of_charge`: Charge percentage (0-100)
|
||||||
|
- `state_of_health`: Health percentage (0-100)
|
||||||
|
- `temperature`: Temperature in degrees Celsius
|
||||||
|
|
||||||
|
#### Water (`water`)
|
||||||
|
- `enough_water`: Boolean, true if level is above empty threshold
|
||||||
|
- `warn_level`: Boolean, true if level is below warning threshold
|
||||||
|
- `left_ml`: Estimated remaining water in ml
|
||||||
|
- `percent`: Estimated fill level in percent
|
||||||
|
- `raw`: Raw sensor voltage in mV
|
||||||
|
- `sensor_error`: Details if the level sensor fails
|
||||||
|
- `water_frozen`: Boolean, true if temperature is below freezing
|
||||||
|
- `water_temp`: Water temperature in degrees Celsius
|
||||||
|
- `temp_sensor_error`: Details if the temperature sensor fails
|
||||||
|
|
||||||
|
#### Plant (`plant{1-8}`)
|
||||||
|
- `sensor_a` / `sensor_b`: Moisture sensor status
|
||||||
|
- `Disabled`
|
||||||
|
- `{"MoistureValue":{"raw_hz":5000,"moisture_percent":65}}`
|
||||||
|
- `{"SensorError":{"ShortCircuit":{"hz":...,"max":...}}}`
|
||||||
|
- `mode`: Watering mode (`Off`, `TargetMoisture`, `MinMoisture`, `TimerOnly`)
|
||||||
|
- `do_water`: Boolean, true if watering is currently required
|
||||||
|
- `dry`: Boolean, true if moisture is below target
|
||||||
|
- `cooldown`: Boolean, true if the pump is in cooldown period
|
||||||
|
- `out_of_work_hour`: Boolean, true if currently outside allowed watering hours
|
||||||
|
- `consecutive_pump_count`: Number of pump cycles without reaching target
|
||||||
|
- `pump_error`: Details if the pump is failing
|
||||||
|
- `last_pump`: Timestamp of last activity
|
||||||
|
- `next_pump`: Estimated timestamp for next allowed activity
|
||||||
|
|
||||||
|
#### Pump (`pump{1-8}`)
|
||||||
|
- `enabled`: Boolean, pump was active
|
||||||
|
- `pump_ineffective`: Boolean, no flow detected during pumping
|
||||||
|
- `median_current_ma`: Median pump current
|
||||||
|
- `max_current_ma`: Peak pump current
|
||||||
|
- `min_current_ma`: Minimum pump current
|
||||||
|
|
||||||
|
#### Light (`light`)
|
||||||
|
- `enabled`: Boolean, is enabled in config
|
||||||
|
- `active`: Boolean, led is currently on
|
||||||
|
- `out_of_work_hour`: Boolean, led should not be on at this time of day
|
||||||
|
- `battery_low`: Boolean, battery is low so led usage is restricted
|
||||||
|
- `is_day`: Boolean, the sun is up (determined by solar panel voltage)
|
||||||
@@ -5,37 +5,57 @@ draft: false
|
|||||||
description: "How to compile the project"
|
description: "How to compile the project"
|
||||||
tags: ["clone", "compile"]
|
tags: ["clone", "compile"]
|
||||||
---
|
---
|
||||||
# Preconditons:
|
# Preconditions:
|
||||||
* NPM is installed
|
* **Rust:** `rustup` installed.
|
||||||
* rustup is installed
|
* **ESP32 Toolchain:** `espup` installed.
|
||||||
* espup is installed
|
* **Build Utilities:** `ldproxy` and `espflash` installed.
|
||||||
|
* **Node.js:** `npm` installed (for the web interface).
|
||||||
|
|
||||||
|
# Cloning the Repository
|
||||||
|
Clone the repository including submodules:
|
||||||
|
```bash
|
||||||
|
git clone --recursive https://git.mannheim.ccc.de/C3MA/PlantCtrl.git
|
||||||
|
cd PlantCtrl/Software/MainBoard/rust
|
||||||
|
```
|
||||||
|
|
||||||
# Cloning Git
|
# Toolchain Setup
|
||||||
Clone the git via the tool of your choice to your local computer
|
1. **Install Rust:** If not already done, visit [rustup.rs](https://rustup.rs/).
|
||||||
```
|
2. **Install ldproxy:**
|
||||||
git clone https://git.mannheim.ccc.de/C3MA/PlantCtrl.git
|
```bash
|
||||||
```
|
|
||||||
switch to the newly cloned folder
|
|
||||||
```
|
|
||||||
cd PlantCtrl/rust
|
|
||||||
```
|
|
||||||
# Install rust
|
|
||||||
rustup description
|
|
||||||
install ldproxy
|
|
||||||
```
|
|
||||||
cargo install ldproxy
|
cargo install ldproxy
|
||||||
```
|
```
|
||||||
# Esp Toolchain
|
3. **Install espup:**
|
||||||
espup description
|
```bash
|
||||||
# Webpack install
|
cargo install espup
|
||||||
The buildin config website is currently build inline via npm - typescript - webpack and then directly embedded into the binary, so it is required to have webpack build working.
|
|
||||||
```
|
```
|
||||||
|
4. **Install ESP toolchain:**
|
||||||
|
```bash
|
||||||
|
espup install
|
||||||
|
```
|
||||||
|
5. **Install espflash:**
|
||||||
|
```bash
|
||||||
|
cargo install espflash
|
||||||
|
```
|
||||||
|
|
||||||
|
# Building the Web Interface
|
||||||
|
The configuration website is built using TypeScript and Webpack, then embedded into the Rust binary.
|
||||||
|
```bash
|
||||||
cd src_webpack/
|
cd src_webpack/
|
||||||
npm install
|
npm install
|
||||||
```
|
|
||||||
Check the webpack build is working
|
|
||||||
```
|
|
||||||
npx webpack
|
npx webpack
|
||||||
|
cd ..
|
||||||
```
|
```
|
||||||
# Cargo Build
|
|
||||||
|
# Compiling the Firmware
|
||||||
|
Build the project using Cargo:
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
The resulting binary will be located in `target/riscv32imac-unknown-none-elf/release/plant-ctrl2`.
|
||||||
|
|
||||||
|
# Using Build Scripts
|
||||||
|
To simplify the process, several bash scripts are provided in the `Software/MainBoard/rust` directory:
|
||||||
|
|
||||||
|
* **`image_build.sh`**: Automatically builds the web interface, compiles the Rust firmware in release mode, and creates a flashable `image.bin`.
|
||||||
|
* **`all.sh`**: Performs all steps from `image_build.sh` and additionally flashes the firmware to a connected device and starts the serial monitor.
|
||||||
|
* **`wokwi_build.sh`**: Builds a debug version of the firmware and creates a full 16MB flash image for use with the [Wokwi simulator](https://wokwi.com).
|
||||||
|
|||||||
@@ -2,13 +2,50 @@
|
|||||||
title: "Software"
|
title: "Software"
|
||||||
date: 2025-01-24
|
date: 2025-01-24
|
||||||
draft: false
|
draft: false
|
||||||
description: "Information about the Firmware"
|
description: "Information about the PlantCtrl Firmware"
|
||||||
tags: ["software"]
|
tags: ["software", "rust", "esp32-c6", "firmware"]
|
||||||
---
|
---
|
||||||
* esp-idf-hal based rust project
|
|
||||||
* can be run standalon or with wifi
|
The PlantCtrl firmware is a modern, reliable, and efficient system designed specifically for the ESP32-C6 RISC-V microcontroller. It is written entirely in **Rust**, leveraging the power of asynchronous programming to ensure low power consumption and high responsiveness.
|
||||||
* MQTT
|
|
||||||
* SNTP to sync the RTC/Clock
|
## Core Technology Stack
|
||||||
* Abitrary file upload and download into SPIFS volume
|
|
||||||
* Flash Battery Controller from file in Flash
|
* **Language:** [Rust](https://www.rust-lang.org/) (no_std)
|
||||||
* Flash Firmware via OTA directly from website
|
* **Async Runtime:** [Embassy](https://embassy.dev/)
|
||||||
|
* **Hardware Abstraction:** `esp-hal` for ESP32-C6
|
||||||
|
* **Filesystem:** [LittleFS2](https://github.com/littlefs-project/littlefs) for robust persistent storage
|
||||||
|
* **Web Framework:** `edge-http` for the integrated web server
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Intelligent Watering
|
||||||
|
The software manages up to 8 independent watering zones.
|
||||||
|
* **Moisture-Based Control:** Automatically triggers pumps when soil moisture drops below a configurable threshold.
|
||||||
|
* **Safety Limits:** Includes pump time limits, volume limits (requires flow sensor), and mandatory cooldown periods.
|
||||||
|
* **Scheduling:** Define specific hours during which watering is allowed to occur.
|
||||||
|
* **Pump Protection:** Monitors pump current to detect dry running or blockages (using INA219).
|
||||||
|
|
||||||
|
### Modular Sensor Integration
|
||||||
|
The firmware communicates with the new **CAN bus-based sensor modules**.
|
||||||
|
* **Digital Accuracy:** Receives precise moisture data over the robust CAN bus protocol.
|
||||||
|
* **Multi-Sensor Support:** Supports A and B sensor pairs for each plant to provide better averaging in large containers.
|
||||||
|
|
||||||
|
### Networking & IoT
|
||||||
|
* **WiFi:** Supports both Station mode (connecting to your home network) and Access Point mode (for initial setup).
|
||||||
|
* **MQTT & Home Assistant:** Full integration with Home Assistant via the `mcutie` crate. Reports battery status, moisture levels, tank levels, and allows remote configuration.
|
||||||
|
* **Time Sync:** Uses **SNTP** to keep the internal Real-Time Clock (RTC) accurate.
|
||||||
|
* **OTA Updates:** Firmware can be updated wirelessly via the web interface.
|
||||||
|
|
||||||
|
### Power Management
|
||||||
|
* **Solar Optimized:** Works in tandem with the MPPT module to maximize solar charging.
|
||||||
|
* **Battery Aware:** Monitors battery State of Charge (SoC). It can automatically disable non-essential features (like the nightlight) or enter a deep-sleep "rescue mode" if the battery is critically low.
|
||||||
|
* **Low Power:** Uses ESP32-C6 power-saving features to ensure long autonomous operation.
|
||||||
|
|
||||||
|
### Integrated Web Interface
|
||||||
|
The firmware hosts a comprehensive web dashboard built with **TypeScript** and **Webpack**.
|
||||||
|
* **Real-time Monitoring:** View current moisture, battery health, and solar production.
|
||||||
|
* **Easy Configuration:** Change all system settings (WiFi, MQTT, watering rules) directly from your browser.
|
||||||
|
* **Diagnostics:** Access system logs and hardware status information.
|
||||||
|
|
||||||
|
## Emergency Rescue Mode
|
||||||
|
If the system detects a missing configuration or an invalid system time, it automatically enters **Emergency Rescue Mode**. In this mode, it opens a WiFi Access Point ("PlantCtrl Init") allowing you to perform initial setup or recover the system.
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
---
|
---
|
||||||
title: "Software"
|
title: "PlantCtrl"
|
||||||
date: 2025-01-24
|
date: 2025-01-24
|
||||||
draft: false
|
draft: false
|
||||||
description: "Information about the Firmware"
|
description: "Solar Powered Plant Monitoring and Watering"
|
||||||
tags: ["software"]
|
tags: ["iot", "plants", "solar"]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
PlantCtrl is an open-source, modular, solar-powered system for monitoring and watering your plants.
|
||||||
|
|
||||||
Initial creation story here
|
## Key Features
|
||||||
|
* **Modular Design:** Choose the components you need (MPPT, Pumps, Sensors, Lights).
|
||||||
|
* **Solar Powered:** Integrated MPPT charger for battery operation.
|
||||||
<div align="left">
|
* **Low Power:** Optimized for long-term autonomous operation.
|
||||||
|
* **IoT Enabled:** MQTT support for integration with Home Assistant or other systems.
|
||||||
|
* **Rust Firmware:** Reliable and efficient firmware based on ESP-IDF and Rust.
|
||||||
|
|
||||||
## Target group:
|
## Target group:
|
||||||
* DIY
|
* DIY Enthusiasts
|
||||||
* OpenSource
|
* Open Source supporters
|
||||||
* Small scale (as in balcony/garden not agriculture)
|
* Small scale use (balcony, greenhouse, indoor garden)
|
||||||
* Automatic Watering for a limited amount of time via a water tank (I do not recommend pipe connection due to flooding risks)
|
* Automatic Watering from a water tank
|
||||||
* Use as is, or improve as you see fit
|
|
||||||
</div>
|
|
||||||
|
|
||||||
[Hardware]({{< relref "/hardware" >}})
|
[Explore Hardware]({{< relref "/hardware" >}})
|
||||||
|
|
||||||
[Software]({{< relref "/software" >}})
|
[Explore Software]({{< relref "/software" >}})
|
||||||
|
|
||||||
{{< article link="/hardware/" >}}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user