Compare commits

...

4 Commits

13 changed files with 285443 additions and 41 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,8 +5,8 @@ target = "riscv32imac-esp-espidf"
[target.riscv32imac-esp-espidf] [target.riscv32imac-esp-espidf]
linker = "ldproxy" linker = "ldproxy"
#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 --baud 921600 --flash-size 16mb --partition-table partitions.csv" #runner = "espflash flash --monitor --baud 921600 --flash-size 16mb --partition-table partitions.csv"
#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

View File

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use crate::PLANT_COUNT; use crate::PLANT_COUNT;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
pub struct NetworkConfig { pub struct NetworkConfig {
pub ap_ssid: heapless::String<32>, pub ap_ssid: heapless::String<32>,
pub ssid: Option<heapless::String<32>>, pub ssid: Option<heapless::String<32>>,
@ -25,22 +26,30 @@ impl Default for NetworkConfig {
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
pub struct NightLampConfig { pub struct NightLampConfig {
pub enabled: bool,
pub night_lamp_hour_start: u8, pub night_lamp_hour_start: u8,
pub night_lamp_hour_end: u8, pub night_lamp_hour_end: u8,
pub night_lamp_only_when_dark: bool, pub night_lamp_only_when_dark: bool,
pub low_soc_cutoff: u8,
pub low_soc_restore: u8
} }
impl Default for NightLampConfig { impl Default for NightLampConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
enabled: true,
night_lamp_hour_start: 19, night_lamp_hour_start: 19,
night_lamp_hour_end: 2, night_lamp_hour_end: 2,
night_lamp_only_when_dark: true, night_lamp_only_when_dark: true,
low_soc_cutoff: 30,
low_soc_restore: 50
} }
} }
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
pub struct TankConfig { pub struct TankConfig {
pub tank_sensor_enabled: bool, pub tank_sensor_enabled: bool,
pub tank_allow_pumping_if_sensor_error: bool, pub tank_allow_pumping_if_sensor_error: bool,
@ -63,6 +72,7 @@ impl Default for TankConfig {
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
#[serde(default)]
pub struct PlantControllerConfig { pub struct PlantControllerConfig {
pub network: NetworkConfig, pub network: NetworkConfig,
pub tank: TankConfig, pub tank: TankConfig,
@ -71,6 +81,7 @@ pub struct PlantControllerConfig {
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(default)]
pub struct PlantConfig { pub struct PlantConfig {
pub mode: Mode, pub mode: Mode,
pub target_moisture: u8, pub target_moisture: u8,

View File

@ -39,8 +39,8 @@ pub mod plant_hal;
const TIME_ZONE: Tz = Berlin; const TIME_ZONE: Tz = Berlin;
const MOIST_SENSOR_MAX_FREQUENCY: u32 = 250000; // 60kHz (500Hz margin) const MOIST_SENSOR_MAX_FREQUENCY: u32 = 5000; // 60kHz (500Hz margin)
const MOIST_SENSOR_MIN_FREQUENCY: u32 = 10000; // 0.5kHz (500Hz margin) const MOIST_SENSOR_MIN_FREQUENCY: u32 = 150; // this is really really dry, think like cactus levels
const FROM: (f32, f32) = ( const FROM: (f32, f32) = (
MOIST_SENSOR_MIN_FREQUENCY as f32, MOIST_SENSOR_MIN_FREQUENCY as f32,
@ -76,6 +76,8 @@ impl WaitType {
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)] #[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
/// Light State tracking data for mqtt /// Light State tracking data for mqtt
struct LightState { struct LightState {
/// is enabled in config
enabled: bool,
/// led is on /// led is on
active: bool, active: bool,
/// led should not be on at this time of day /// led should not be on at this time of day
@ -510,28 +512,39 @@ fn safe_main() -> anyhow::Result<()> {
} }
update_plant_state(&mut plantstate, &mut board, &config); update_plant_state(&mut plantstate, &mut board, &config);
let is_day = board.is_day();
let state_of_charge = board.state_charge_percent().unwrap_or(0);
let mut light_state = LightState { let mut light_state = LightState {
enabled: config.night_lamp.enabled,
..Default::default() ..Default::default()
}; };
let is_day = board.is_day(); if light_state.enabled {
light_state.is_day = is_day; light_state.is_day = is_day;
light_state.out_of_work_hour = !in_time_range( light_state.out_of_work_hour = !in_time_range(
&timezone_time, &timezone_time,
config.night_lamp.night_lamp_hour_start, config.night_lamp.night_lamp_hour_start,
config.night_lamp.night_lamp_hour_end, config.night_lamp.night_lamp_hour_end,
); );
let state_of_charge = board.state_charge_percent().unwrap_or(0); if state_of_charge < config.night_lamp.low_soc_cutoff {
if state_of_charge < 30 { board.set_low_voltage_in_cycle();
board.set_low_voltage_in_cycle(); } else if state_of_charge > config.night_lamp.low_soc_restore {
} else if state_of_charge > 50 { board.clear_low_voltage_in_cycle();
board.clear_low_voltage_in_cycle(); }
} light_state.battery_low = board.low_voltage_in_cycle();
light_state.battery_low = board.low_voltage_in_cycle();
if !light_state.out_of_work_hour {
if !light_state.out_of_work_hour { if config.night_lamp.night_lamp_only_when_dark {
if config.night_lamp.night_lamp_only_when_dark { if !light_state.is_day {
if !light_state.is_day { if light_state.battery_low {
board.light(false).unwrap();
} else {
light_state.active = true;
board.light(true).unwrap();
}
}
} else {
if light_state.battery_low { if light_state.battery_low {
board.light(false).unwrap(); board.light(false).unwrap();
} else { } else {
@ -540,20 +553,13 @@ fn safe_main() -> anyhow::Result<()> {
} }
} }
} else { } else {
if light_state.battery_low { light_state.active = false;
board.light(false).unwrap(); board.light(false).unwrap();
} else {
light_state.active = true;
board.light(true).unwrap();
}
} }
} else {
light_state.active = false; println!("Lightstate is {:?}", light_state);
board.light(false).unwrap();
} }
println!("Lightstate is {:?}", light_state);
match serde_json::to_string(&light_state) { match serde_json::to_string(&light_state) {
Ok(state) => { Ok(state) => {
let _ = board.mqtt_publish(&config, "/light", state.as_bytes()); let _ = board.mqtt_publish(&config, "/light", state.as_bytes());

View File

@ -54,6 +54,11 @@ pub struct WebBackupHeader{
size: usize size: usize
} }
#[derive(Deserialize)]
pub struct NightLampCommand {
active: bool
}
fn write_time( fn write_time(
request: &mut Request<&mut EspHttpConnection>, request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> { ) -> Result<Option<std::string::String>, anyhow::Error> {
@ -236,6 +241,16 @@ fn pump_test(
anyhow::Ok(None) anyhow::Ok(None)
} }
fn night_lamp_test(
request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> {
let actual_data = read_up_to_bytes_from_request(request, None)?;
let light_command: NightLampCommand = serde_json::from_slice(&actual_data)?;
let mut board = BOARD_ACCESS.lock().unwrap();
board.light(light_command.active)?;
anyhow::Ok(None)
}
fn wifi_scan( fn wifi_scan(
_request: &mut Request<&mut EspHttpConnection>, _request: &mut Request<&mut EspHttpConnection>,
) -> Result<Option<std::string::String>, anyhow::Error> { ) -> Result<Option<std::string::String>, anyhow::Error> {
@ -399,6 +414,11 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
handle_error_to500(request, pump_test) handle_error_to500(request, pump_test)
}) })
.unwrap(); .unwrap();
server
.fn_handler("/lamptest", Method::Post, |request| {
handle_error_to500(request, night_lamp_test)
})
.unwrap();
server server
.fn_handler("/boardtest", Method::Post, move |_| { .fn_handler("/boardtest", Method::Post, move |_| {
BOARD_ACCESS.lock().unwrap().test() BOARD_ACCESS.lock().unwrap().test()

View File

@ -25,9 +25,16 @@ interface FileInfo{
} }
interface NightLampConfig { interface NightLampConfig {
enabled: boolean,
night_lamp_hour_start: number, night_lamp_hour_start: number,
night_lamp_hour_end: number, night_lamp_hour_end: number,
night_lamp_only_when_dark: boolean, night_lamp_only_when_dark: boolean,
low_soc_cutoff: number,
low_soc_restore: number
}
interface NightLampCommand {
active: boolean
} }
interface TankConfig { interface TankConfig {

View File

@ -225,7 +225,16 @@ export class Controller {
} }
} }
testNightLamp(active: boolean){
var body: NightLampCommand = {
active: active
}
var pretty = JSON.stringify(body, undefined, 1);
fetch(PUBLIC_URL + "/lamptest", {
method: "POST",
body: pretty
})
}
testPlant(plantId: number) { testPlant(plantId: number) {
let counter = 0 let counter = 0
@ -349,11 +358,19 @@ export class Controller {
waitForReboot() { waitForReboot() {
console.log("Check if controller online again") console.log("Check if controller online again")
fetch(PUBLIC_URL + "/version", { fetch(PUBLIC_URL + "/version", {
method: "POST", method: "GET",
signal: AbortSignal.timeout(5000) signal: AbortSignal.timeout(5000)
}).then(response => { }).then(response => {
console.log("Reached controller, reloading") if (response.status != 200){
window.location.reload(); console.log("Not reached yet, retrying")
setTimeout(controller.waitForReboot, 1000)
} else {
console.log("Reached controller, reloading")
controller.progressview.addIndeterminate("rebooting", "Reached Controller, reloading")
setTimeout(function(){
window.location.reload()
}, 2000);
}
}) })
.catch(err => { .catch(err => {
console.log("Not reached yet, retrying") console.log("Not reached yet, retrying")

View File

@ -16,9 +16,13 @@
</style> </style>
<div class="subtitle">Light:</div> <div class="subtitle">Light:</div>
<div class="flexcontainer">
<div class="lightkey">Test Nightlight</div>
<input class="lightcheckbox" type="checkbox" id="night_lamp_test">
</div>
<div class="flexcontainer" style="text-decoration-line: line-through;"> <div class="flexcontainer" style="text-decoration-line: line-through;">
<div class="lightkey">Enable Nightlight</div> <div class="lightkey">Enable Nightlight</div>
<input class="lightcheckbox" type="checkbox" id="night_lamp_enabled" checked="false"> <input class="lightcheckbox" type="checkbox" id="night_lamp_enabled">
</div> </div>
<div class="flexcontainer"> <div class="flexcontainer">
<div class="lightkey">Light only when dark</div> <div class="lightkey">Light only when dark</div>
@ -33,4 +37,12 @@
<div class="lightkey">Stop</div> <div class="lightkey">Stop</div>
<select class="lightnumberbox" type="time" id="night_lamp_time_end"> <select class="lightnumberbox" type="time" id="night_lamp_time_end">
</select> </select>
</div>
<div class="flexcontainer">
<div class="lightkey">Disable if Battery below %</div>
<input class="lightcheckbox" type="number" id="night_lamp_soc_low" min="0" max="100">
</div>
<div class="flexcontainer">
<div class="lightkey">Reenable if Battery higher %</div>
<input class="lightcheckbox" type="number" id="night_lamp_soc_restore" min="0" max="100">
</div> </div>

View File

@ -4,12 +4,26 @@ export class NightLampView {
private readonly night_lamp_only_when_dark: HTMLInputElement; private readonly night_lamp_only_when_dark: HTMLInputElement;
private readonly night_lamp_time_start: HTMLSelectElement; private readonly night_lamp_time_start: HTMLSelectElement;
private readonly night_lamp_time_end: HTMLSelectElement; private readonly night_lamp_time_end: HTMLSelectElement;
private readonly night_lamp_test: HTMLInputElement;
private readonly night_lamp_enabled: HTMLInputElement;
private readonly night_lamp_soc_low: HTMLInputElement;
private readonly night_lamp_soc_restore: HTMLInputElement;
constructor(controller:Controller){ constructor(controller:Controller){
(document.getElementById("lightview") as HTMLElement).innerHTML = require('./nightlightview.html') as string; (document.getElementById("lightview") as HTMLElement).innerHTML = require('./nightlightview.html') as string;
this.night_lamp_only_when_dark = document.getElementById("night_lamp_only_when_dark") as HTMLInputElement; this.night_lamp_only_when_dark = document.getElementById("night_lamp_only_when_dark") as HTMLInputElement;
this.night_lamp_only_when_dark.onchange = controller.configChanged this.night_lamp_only_when_dark.onchange = controller.configChanged
this.night_lamp_enabled = document.getElementById("night_lamp_enabled") as HTMLInputElement;
this.night_lamp_enabled.onchange = controller.configChanged
this.night_lamp_soc_low = document.getElementById("night_lamp_soc_low") as HTMLInputElement;
this.night_lamp_soc_low.onchange = controller.configChanged
this.night_lamp_soc_restore = document.getElementById("night_lamp_soc_restore") as HTMLInputElement;
this.night_lamp_soc_restore.onchange = controller.configChanged
this.night_lamp_time_start = document.getElementById("night_lamp_time_start") as HTMLSelectElement; this.night_lamp_time_start = document.getElementById("night_lamp_time_start") as HTMLSelectElement;
this.night_lamp_time_start.onchange = controller.configChanged this.night_lamp_time_start.onchange = controller.configChanged
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
@ -31,12 +45,21 @@ export class NightLampView {
option.innerText = i.toString(); option.innerText = i.toString();
this.night_lamp_time_end.appendChild(option); this.night_lamp_time_end.appendChild(option);
} }
let night_lamp_test = document.getElementById("night_lamp_test") as HTMLInputElement;
this.night_lamp_test = night_lamp_test
this.night_lamp_test.onchange = () => {
controller.testNightLamp(night_lamp_test.checked)
}
} }
setConfig(nightLamp: NightLampConfig) { setConfig(nightLamp: NightLampConfig) {
this.night_lamp_only_when_dark.checked = nightLamp.night_lamp_only_when_dark this.night_lamp_only_when_dark.checked = nightLamp.night_lamp_only_when_dark
this.night_lamp_time_start.value = nightLamp.night_lamp_hour_start.toString(); this.night_lamp_time_start.value = nightLamp.night_lamp_hour_start.toString();
this.night_lamp_time_end.value = nightLamp.night_lamp_hour_end.toString(); this.night_lamp_time_end.value = nightLamp.night_lamp_hour_end.toString();
this.night_lamp_enabled.checked = nightLamp.enabled;
this.night_lamp_soc_low.value = nightLamp.low_soc_cutoff.toString();
this.night_lamp_soc_restore.value = nightLamp.low_soc_restore.toString();
} }
getConfig(): NightLampConfig { getConfig(): NightLampConfig {
@ -44,6 +67,9 @@ export class NightLampView {
night_lamp_hour_start: +this.night_lamp_time_start.value, night_lamp_hour_start: +this.night_lamp_time_start.value,
night_lamp_hour_end: +this.night_lamp_time_end.value, night_lamp_hour_end: +this.night_lamp_time_end.value,
night_lamp_only_when_dark: this.night_lamp_only_when_dark.checked, night_lamp_only_when_dark: this.night_lamp_only_when_dark.checked,
enabled: this.night_lamp_enabled.checked,
low_soc_cutoff: +this.night_lamp_soc_low.value,
low_soc_restore: +this.night_lamp_soc_restore.value
} }
} }
} }

View File

@ -15,7 +15,7 @@
<div class="flexcontainer"> <div class="flexcontainer">
<div class="subtitle">Tank:</div> <div class="subtitle">Tank:</div>
</div> </div>
<div class="flexcontainer" style="text-decoration-line: line-through;"> <div class="flexcontainer">
<span class="tankkey">Enable Tank Sensor</span> <span class="tankkey">Enable Tank Sensor</span>
<input class="tankcheckbox" type="checkbox" id="tank_sensor_enabled"> <input class="tankcheckbox" type="checkbox" id="tank_sensor_enabled">
</div> </div>