Skip to content

Commit

Permalink
Limit number of pump doses in a watering cycle (#17)
Browse files Browse the repository at this point in the history
If there are too many pump doses in watering cycle, then switch to a
"watering paused" state and wait for 12 hours.

This is a safety features to avoid over-watering if something goes wrong,
such as unreliable moisture sensor readings.
  • Loading branch information
chl33 authored Oct 19, 2024
1 parent f415eee commit 6e554e7
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 5 deletions.
40 changes: 36 additions & 4 deletions lib/watering/watering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const char* Watering::s_state_names[] = {
"pump", // kStateDose
"pump done", // kStateEndOfDose
"soil is moist", // kStateWaitForNextCycle
"watering paused", // kStateWateringPaused
"watering disabled", // kStateDisabled
"pump test", // kStatePumpTest
"test", // kStateTest
Expand All @@ -46,6 +47,7 @@ int Watering::direction() const {
case kStateEval:
case kStateDose:
case kStateEndOfDose:
case kStateWateringPaused:
return +1;

case kStateWaitForNextCycle:
Expand Down Expand Up @@ -97,11 +99,13 @@ Watering::Watering(unsigned index, const char* name, uint8_t moisture_pin, uint8
m_between_doses_sec("between_doses_sec", kPumpOffSec, units::kSeconds, "Wait between doses",
kCfgSet, 0, m_cfg_vg),
m_state("watering_state", kStateWaitForNextCycle, "", "watering state", 0, m_vg),
m_sec_since_dose("sec_since_pump", 0, units::kSeconds, "seconds since pump dose", 0, 0, m_vg),
m_max_doses_per_cycle("max_doses_per_cycle", kMaxDosesPerCycle, "", "maximum doses per cycle",
kCfgSet, m_cfg_vg),
m_doses_this_cycle("doses_this_cycle", 0, "", "doses this cycle", 0, m_vg),
m_watering_enabled("watering_enabled", false, "watering enabled", kCfgSet, m_cfg_vg),
m_reservoir_check_enabled("res_check_enabled", false, "reservior check enabled", kCfgSet,
m_cfg_vg),
m_sec_since_dose("sec_since_pump", 0, units::kSeconds, "seconds since pump dose", 0, 0,
m_vg) {
m_cfg_vg) {
setDependencies(&m_dependencies);
// 10 seconds after boot, start the plant state machine.
m_next_update_msec = millis() + (10 + 15 * index) * kMsecInSec;
Expand Down Expand Up @@ -136,6 +140,9 @@ Watering::Watering(unsigned index, const char* name, uint8_t moisture_pin, uint8
return had->addMeas(json, m_sec_since_dose, ha::device_type::kSensor,
ha::device_class::sensor::kDuration);
});
ha_discovery->addDiscoveryCallback([this](HADiscovery* had, JsonDocument* json) {
return had->addMeas(json, m_doses_this_cycle, ha::device_type::kSensor, nullptr);
});
}
});
add_update_fn([this]() { loop(); });
Expand Down Expand Up @@ -248,10 +255,12 @@ void Watering::loop() {
// Check whether to turn on the pump.
const float val = m_moisture.filteredValue();
if (val > m_max_moisture_target.value()) {
// Moisure level is above the maximum threshold, so switch the a state
// Moisure level is above the maximum threshold, so switch to the state
// where we wait for it to fall back below the minimum to start the
// watering cycle again.
setState(kStateWaitForNextCycle, kWaitForNextCycleMsec, "moisture past maximum range");
} else if (m_doses_this_cycle.value() >= m_max_doses_per_cycle.value()) {
setState(kStateWateringPaused, kWaitForNextCycleMsec, "too many doses in cycle");
} else {
setState(kStateDose, 1, "start pump");
}
Expand All @@ -263,6 +272,7 @@ void Watering::loop() {
case kStateDose:
// Start the pump, and run for kPumpOnMsec.
m_pump.turnOn();
m_doses_this_cycle = m_doses_this_cycle.value() + 1;
setState(kStateEndOfDose, m_pump_dose_msec.value(), "end watering dose");
break;

Expand All @@ -276,10 +286,29 @@ void Watering::loop() {
setState(kStateEval, kWaitForNextCycleMsec, "continue watering");
break;

case kStateWateringPaused: {
m_doses_this_cycle = 0;
const float val = m_moisture.filteredValue();
if (val > m_max_moisture_target.value()) {
// Moisure level is above the maximum threshold, so switch to the state
// where we wait for it to fall back below the minimum to start the
// watering cycle again.
setState(kStateWaitForNextCycle, kWaitForNextCycleMsec, "moisture past maximum range");
} else if (msecSincePump >= kWateringPauseMsec) {
// If the pause time expires, go back to eval state.
setState(kStateEval, 1, "re-enable watering after pause");
} else {
// Otherwise stay in the paused state.
setState(kStateWateringPaused, kWaitForNextCycleMsec, "");
}
break;
}

case kStateWaitForNextCycle: {
// After moisture level reaches maximum level, wait for it to reach minimum
// moisture level and also wait for minimum time between watering cycles, then
// go back to kStateEval.
m_doses_this_cycle = 0;
const float val = m_moisture.filteredValue();
if (val < m_min_moisture_target.value()) {
setState(kStateEval, 1, "start watering");
Expand All @@ -292,18 +321,21 @@ void Watering::loop() {
case kStateDisabled:
// State for when pump is disabled.
m_pump.turnOff();
m_doses_this_cycle = 0;
// Update every 10 seconds to get latest readings.
setState(kStateDisabled, 10 * kMsecInSec, "");
break;

case kStatePumpTest:
// State for when testing a single pump cycle, transitions to disabled mode.
m_pump.turnOn();
m_doses_this_cycle = 0;
setState(kStateDisabled, m_pump_dose_msec.value(), "end of pump test");
break;

case kStateTest:
_fullTest();
m_doses_this_cycle = 0;
setState(kStateDisabled, kMsecInSec, "end of test");
break;
}
Expand Down
7 changes: 6 additions & 1 deletion lib/watering/watering.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class Watering : public Module {
// go back to kStateEval.
kStateWaitForNextCycle,

// After too many does in a cycle, pause for 12 hours.
kStateWateringPaused,

// State for when pump is disabled.
kStateDisabled,

Expand Down Expand Up @@ -120,9 +123,11 @@ class Watering : public Module {
FloatVariable m_pump_dose_msec;
FloatVariable m_between_doses_sec;
StateVariable m_state;
FloatVariable m_sec_since_dose;
Variable<unsigned> m_max_doses_per_cycle;
Variable<unsigned> m_doses_this_cycle;
BoolVariable m_watering_enabled;
BoolVariable m_reservoir_check_enabled;
FloatVariable m_sec_since_dose;
};

} // namespace og3
4 changes: 4 additions & 0 deletions lib/watering/watering_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ constexpr unsigned kFullMoistureCounts = 1470;
// Default ADC reading at which to consider soil moisture to be 0%.
constexpr unsigned kNoMoistureCounts = 2900;

constexpr unsigned kMaxDosesPerCycle = 5;

constexpr unsigned kWateringPauseMsec = 12 * kMsecInHour;

} // namespace og3

0 comments on commit 6e554e7

Please sign in to comment.