diff --git a/esp32/host/config-example.json b/esp32/host/config-example.json index b383013..433ae42 100644 --- a/esp32/host/config-example.json +++ b/esp32/host/config-example.json @@ -20,7 +20,6 @@ "settings": { "deepsleep" : 60000, "nightsleep" : 60000, - "pumpdeepsleep": 1000, "watermaxlevel": 50, "watermin" : 5, "plants" : 3, @@ -44,6 +43,13 @@ "plant3MinPumpIdle": 10000, "plant4MinPumpIdle": 10000, "plant5MinPumpIdle": 10000, - "plant6MinPumpIdle": 10000 + "plant6MinPumpIdle": 10000, + "plant0PumpDuration": 5, + "plant1PumpDuration": 5, + "plant2PumpDuration": 5, + "plant3PumpDuration": 5, + "plant4PumpDuration": 5, + "plant5PumpDuration": 5, + "plant6PumpDuration": 5 } } diff --git a/esp32/include/ControllerConfiguration.h b/esp32/include/ControllerConfiguration.h index 3465ba3..e23af28 100644 --- a/esp32/include/ControllerConfiguration.h +++ b/esp32/include/ControllerConfiguration.h @@ -94,7 +94,7 @@ #define BATTSENSOR_INDEX_BATTERY 1 #define MQTT_TIMEOUT (1000 * 60) /**< After 10 seconds, MQTT is expected to be connected */ -#define ESP_STALE_TIMEOUT (MQTT_TIMEOUT+(30*1000)) +#define ESP_STALE_TIMEOUT (MQTT_TIMEOUT+(120*1000)) #define MAX_PLANTS 7 #define SOLAR_CHARGE_MIN_VOLTAGE 7 /**< Sun is rising (morning detected) */ diff --git a/esp32/include/HomieConfiguration.h b/esp32/include/HomieConfiguration.h index 5794f28..107c77c 100644 --- a/esp32/include/HomieConfiguration.h +++ b/esp32/include/HomieConfiguration.h @@ -73,7 +73,6 @@ HomieNode stayAlive("stay", "alive", "alive"); /**< Necessary for Mqtt Active C */ HomieSetting deepSleepTime("sleep", "time in seconds to sleep"); HomieSetting deepSleepNightTime("nightsleep", "time in seconds to sleep (0 uses same setting: deepsleep at night, too)"); -HomieSetting wateringDeepSleep("pumpsleep", "time seconds to sleep, while a pump is running"); HomieSetting pumpIneffectiveWarning("pumpConsecutiveWarn", "if the pump was triggered this amount directly after each cooldown, without resolving dryness, warn"); HomieSetting waterLevelMax("tankmax", "distance (mm) at maximum water level"); HomieSetting waterLevelMin("tankmin", "distance (mm) at minimum water level (pumps still covered)"); @@ -107,7 +106,8 @@ HomieSetting ntpServer("ntpServer", "NTP server (pool.ntp.org as d HomieSetting mPumpAllowedHourRangeEnd##plant = HomieSetting("hourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \ HomieSetting mPumpOnlyWhenLowLight##plant = HomieSetting("lowLight" strplant, "Plant" strplant " - Enable the Pump only, when there is no sunlight"); \ HomieSetting mPumpCooldownInHours##plant = HomieSetting("delay" strplant, "Plant" strplant " - How long to wait until the pump is activated again (minutes)"); \ - PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInHours##plant}; \ + HomieSetting pPumpDuration##plant = HomieSetting("pumpDuration" strplant, "Plant" strplant " - time seconds or ml (if using flowmeter) to water when pump is active"); \ + PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInHours##plant, &pPumpDuration##plant}; \ /**< Generate all settings for one plant \ * \ * Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE} \ diff --git a/esp32/include/HomieTypes.h b/esp32/include/HomieTypes.h index 5e58d13..9f08fbb 100644 --- a/esp32/include/HomieTypes.h +++ b/esp32/include/HomieTypes.h @@ -23,6 +23,7 @@ typedef struct PlantSettings_t HomieSetting *pPumpAllowedHourRangeEnd; HomieSetting *pPumpOnlyWhenLowLight; HomieSetting *pPumpCooldownInHours; + HomieSetting *pPumpDuration; } PlantSettings_t; #endif \ No newline at end of file diff --git a/esp32/include/PlantCtrl.h b/esp32/include/PlantCtrl.h index 0fda26d..9f0fa58 100644 --- a/esp32/include/PlantCtrl.h +++ b/esp32/include/PlantCtrl.h @@ -141,6 +141,10 @@ public: bool switchHandler(const HomieRange& range, const String& value); void setSwitchHandler(HomieInternals::PropertyInputHandler f); + + long getPumpDuration() { + return this->mSetting->pPumpDuration->get(); + } }; #endif diff --git a/esp32/src/PlantCtrl.cpp b/esp32/src/PlantCtrl.cpp index e0cbd4d..a2bd4f3 100644 --- a/esp32/src/PlantCtrl.cpp +++ b/esp32/src/PlantCtrl.cpp @@ -51,6 +51,12 @@ void Plant::init(void) return ((candidate >= 0) && (candidate <= 1024)); }); + this->mSetting->pPumpDuration->setDefaultValue(5); + this->mSetting->pPumpDuration->setValidator([](long candidate) { + return ((candidate >= 0) && (candidate <= 1000)); + }); + + /* Initialize Hardware */ Serial.println("Set GPIO mode " + String(mPinPump) + "=" + String(OUTPUT)); Serial.flush(); diff --git a/esp32/src/main.cpp b/esp32/src/main.cpp index 42171b9..78a5a23 100644 --- a/esp32/src/main.cpp +++ b/esp32/src/main.cpp @@ -66,13 +66,11 @@ bool determineTimedLightState(bool lowLight); * NON VOLATILE VARIABLES in DEEP SLEEP ******************************************************************************/ -RTC_DATA_ATTR int lastPumpRunning = -1; /**< store last successfully waterd plant */ -RTC_DATA_ATTR long lastWaterValue = 0; /**< to calculate the used water per plant */ +RTC_DATA_ATTR long lastWaterValue = 0; /**< to calculate the used water per plant */ #if defined(TIMED_LIGHT_PIN) - RTC_DATA_ATTR bool timedLightOn = false; /**< allow fast recovery after poweron */ - RTC_DATA_ATTR bool timedLightLowVoltageTriggered = false; /**remember if it was shut down due to voltage level */ -#endif // TIMED_LIGHT_PIN - +RTC_DATA_ATTR bool timedLightOn = false; /**< allow fast recovery after poweron */ +RTC_DATA_ATTR bool timedLightLowVoltageTriggered = false; /**remember if it was shut down due to voltage level */ +#endif // TIMED_LIGHT_PIN RTC_DATA_ATTR long rtcLastWateringPlant[MAX_PLANTS] = {0}; RTC_DATA_ATTR long consecutiveWateringPlant[MAX_PLANTS] = {0}; @@ -84,6 +82,7 @@ bool volatile mDownloadMode = false; /**< Controller must not sleep */ bool volatile mSensorsRead = false; /**< Sensors are read without Wifi or MQTT */ bool volatile mAliveWasRead = false; bool volatile mMQTTReady = false; +int volatile pumpToRun = -1; /** pump to run at the end of the cycle */ bool mConfigured = false; long nextBlink = 0; /**< Time needed in main loop to support expected blink code */ @@ -112,7 +111,7 @@ Plant mPlants[MAX_PLANTS] = { * LOCAL FUNCTIONS ******************************************************************************/ -void espDeepSleepFor(long seconds, bool activatePump) +void espDeepSleep() { if (mDownloadMode) { @@ -138,42 +137,33 @@ void espDeepSleepFor(long seconds, bool activatePump) log(LOG_LEVEL_DEBUG, "NTP timeout before deepsleep", LOG_DEBUG_CODE); } } - + //allo hold for all digital pins gpio_deep_sleep_hold_en(); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_ON); - if (activatePump) + esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_ON); + +#if defined(TIMED_LIGHT_PIN) + gpio_hold_en(TIMED_LIGHT_PIN); +#endif // TIMED_LIGHT_PIN + + long secondsToSleep = -1; + + if (mSolarVoltage < SOLAR_CHARGE_MIN_VOLTAGE) { - esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_ON); - - gpio_hold_en(OUTPUT_ENABLE_PUMP); //pump pwr + log(LOG_LEVEL_INFO, String(String(mSolarVoltage) + "V! No pumps to activate and low light, deepSleepNight"), LOG_NOPUMP_LOWLIGHT); + secondsToSleep = deepSleepTime.get(); } else { - gpio_hold_dis(OUTPUT_ENABLE_PUMP); //pump pwr - digitalWrite(OUTPUT_ENABLE_PUMP, LOW); - digitalWrite(OUTPUT_ENABLE_SENSOR, LOW); - for (int i = 0; i < MAX_PLANTS; i++) - { - mPlants[i].deactivatePump(); - } + log(LOG_LEVEL_INFO, "No pumps to activate, deepSleep", LOG_NOPUMPS); + secondsToSleep = deepSleepTime.get(); } - gpio_hold_en(OUTPUT_PUMP0); - gpio_hold_en(OUTPUT_PUMP1); - gpio_hold_en(OUTPUT_PUMP2); - gpio_hold_en(OUTPUT_PUMP3); - gpio_hold_en(OUTPUT_PUMP4); - gpio_hold_en(OUTPUT_PUMP5); - gpio_hold_en(OUTPUT_PUMP6); - #if defined(TIMED_LIGHT_PIN) - gpio_hold_en(TIMED_LIGHT_PIN); - #endif // TIMED_LIGHT_PIN - - esp_sleep_enable_timer_wakeup((seconds * 1000U * 1000U)); + esp_sleep_enable_timer_wakeup((secondsToSleep * 1000U * 1000U)); if (mAliveWasRead) { delay(1000); @@ -384,7 +374,7 @@ void onHomieEvent(const HomieEvent &event) } mSensorsRead = true; // MQTT is working, deactivate timeout logic - configTime(0, 0, ntpServer.get()); + configTime(3600, 3600, ntpServer.get()); { getTopic(TEST_TOPIC, testopic) @@ -596,6 +586,61 @@ bool switch7(const HomieRange &range, const String &value) return mPlants[6].switchHandler(range, value); } +bool pumpStarted = false; +long pumpTarget = -1; +#ifdef FLOWMETER +long pumpTargetMl = -1; +#endif + +void pumpActiveLoop() +{ + + if (!pumpStarted) + { + //enable power + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); + digitalWrite(OUTPUT_ENABLE_PUMP, HIGH); + delay(100); + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); + + mPlants[pumpToRun].activatePump(); + //set targets + pumpTarget = millis() + (mPlants[pumpToRun].getPumpDuration() * 1000); +#ifdef FLOWMETER + pumpTargetMl = mPlants[pumpToRun].getPumpDuration(); +#endif + pumpStarted = true; + } + + bool targetReached = false; +#ifdef FLOWMETER + long pumped = //readFlowMeterCounter * ratio; + if(pumped >= pumpTargetMl)) + { + targetReached = true; + } + mPlants[pumpToRun].setProperty("waterusage").send(String(pumped)); +#endif + + if (millis() > pumpTarget) + { + targetReached = true; + } + + if (targetReached) + { + //disable all + digitalWrite(OUTPUT_ENABLE_PUMP, LOW); + for (int i = 0; i < MAX_PLANTS; i++) + { + mPlants[i].deactivatePump(); + } + //disable loop, to prevent multi processing + pumpStarted = false; + espDeepSleep(); + } +} + /** * @brief Startup function * Is called once, the controller is started @@ -611,21 +656,12 @@ void setup() WiFi.mode(WIFI_OFF); Serial.flush(); - //restore state before releasing pin, to prevent flickering - #if defined(TIMED_LIGHT_PIN) - pinMode(TIMED_LIGHT_PIN, OUTPUT); - digitalWrite(TIMED_LIGHT_PIN, timedLightOn); - gpio_hold_dis(TIMED_LIGHT_PIN); - #endif // TIMED_LIGHT_PIN - - gpio_hold_dis(OUTPUT_PUMP0); - gpio_hold_dis(OUTPUT_PUMP1); - gpio_hold_dis(OUTPUT_PUMP2); - gpio_hold_dis(OUTPUT_PUMP3); - gpio_hold_dis(OUTPUT_PUMP4); - gpio_hold_dis(OUTPUT_PUMP5); - gpio_hold_dis(OUTPUT_PUMP6); - gpio_hold_dis(OUTPUT_ENABLE_PUMP); +//restore state before releasing pin, to prevent flickering +#if defined(TIMED_LIGHT_PIN) + pinMode(TIMED_LIGHT_PIN, OUTPUT); + digitalWrite(TIMED_LIGHT_PIN, timedLightOn); + gpio_hold_dis(TIMED_LIGHT_PIN); +#endif // TIMED_LIGHT_PIN /* Intialize Plant */ for (int i = 0; i < MAX_PLANTS; i++) @@ -691,7 +727,6 @@ void setup() deepSleepTime.setDefaultValue(600).setValidator([](long candidate) { return (candidate > 0) && (candidate < (60 * 60 * 2) /** 2h max sleep */); }); deepSleepNightTime.setDefaultValue(600); - wateringDeepSleep.setDefaultValue(5); ntpServer.setDefaultValue("pool.ntp.org"); /* waterLevelMax 1000 */ /* 100cm in mm */ @@ -701,18 +736,17 @@ void setup() lipoSensorAddr.setDefaultValue(""); waterSensorAddr.setDefaultValue(""); pumpIneffectiveWarning.setDefaultValue(5).setValidator([](long candidate) - { return (candidate > 0) && (candidate < (20)); }); + { return (candidate > 0) && (candidate < (20)); }); - #if defined(TIMED_LIGHT_PIN) - timedLightStart.setDefaultValue(18).setValidator([](long candidate) - { return (candidate > 0) && (candidate < (25)); }); - timedLightEnd.setDefaultValue(23).setValidator([](long candidate) - { return (candidate > 0) && (candidate < (24)); }); - timedLightOnlyWhenDark.setDefaultValue(true); - timedLightVoltageCutoff.setDefaultValue(3.8).setValidator([](double candidate) - { return (candidate > 3.3) && (candidate < (4.2)); }); - #endif // TIMED_LIGHT_PIN - +#if defined(TIMED_LIGHT_PIN) + timedLightStart.setDefaultValue(18).setValidator([](long candidate) + { return (candidate > 0) && (candidate < (25)); }); + timedLightEnd.setDefaultValue(23).setValidator([](long candidate) + { return (candidate > 0) && (candidate < (24)); }); + timedLightOnlyWhenDark.setDefaultValue(true); + timedLightVoltageCutoff.setDefaultValue(3.8).setValidator([](double candidate) + { return (candidate > 3.3) && (candidate < (4.2)); }); +#endif // TIMED_LIGHT_PIN Homie.setLoopFunction(homieLoop); Homie.onEvent(onHomieEvent); @@ -779,7 +813,7 @@ void setup() if (restoredConfig) { deleteFile(CONFIG_FILE_BACKUP); - espDeepSleepFor(1, false); + espDeepSleep(); return; } } @@ -797,6 +831,28 @@ void setup() setupFinishedTimestamp = millis(); } +void selfTest() +{ + if (pumpToRun >= 0 && pumpToRun < MAX_PLANTS) + { + mPlants[pumpToRun].deactivatePump(); + } + if (pumpToRun >= MAX_PLANTS) + { + digitalWrite(OUTPUT_ENABLE_PUMP, LOW); + nextBlink = millis() + 500; + } + else + { + pumpToRun++; + nextBlink = millis() + 5000; + } + if (pumpToRun < MAX_PLANTS) + { + mPlants[pumpToRun].activatePump(); + } +} + /** * @brief Cyclic call * Executs the Homie base functionallity or triggers sleeping, if requested. @@ -816,24 +872,7 @@ void loop() } else { - if (lastPumpRunning >= 0 && lastPumpRunning < MAX_PLANTS) - { - mPlants[lastPumpRunning].deactivatePump(); - } - if (lastPumpRunning >= MAX_PLANTS) - { - digitalWrite(OUTPUT_ENABLE_PUMP, LOW); - nextBlink = millis() + 500; - } - else - { - lastPumpRunning++; - nextBlink = millis() + 5000; - } - if (lastPumpRunning < MAX_PLANTS) - { - mPlants[lastPumpRunning].activatePump(); - } + selfTest(); } } } @@ -858,6 +897,11 @@ void loop() Serial.flush(); esp_restart(); } + + if (pumpToRun != -1) + { + pumpActiveLoop(); + } } /*** @@ -866,14 +910,6 @@ void loop() */ void plantcontrol() { - if (lastPumpRunning != -1) - { - long waterDiff = waterRawSensor.getAverage() - lastWaterValue; - mPlants[lastPumpRunning].setProperty("waterusage").send(String(waterDiff)); - /* TODO convert diff into volume (milli liter) */ - Serial << "Plant" << lastPumpRunning << ": Water diff " << waterDiff << " mm" << endl; - } - if (mAliveWasRead) { for (int i = 0; i < MAX_PLANTS; i++) @@ -922,95 +958,82 @@ void plantcontrol() bool isLowLight = (mSolarVoltage < SOLAR_CHARGE_MIN_VOLTAGE); bool hasWater = true; //FIXMEmWaterGone > waterLevelMin.get(); //FIXME no water warning message - lastPumpRunning = determineNextPump(isLowLight); - if (lastPumpRunning != -1 && !hasWater) + pumpToRun = determineNextPump(isLowLight); + //early aborts + if (pumpToRun != -1) { - log(LOG_LEVEL_ERROR, LOG_PUMP_BUTNOTANK_MESSAGE, LOG_PUMP_BUTNOTANK_CODE); - } - else if (lastPumpRunning != -1 && hasWater) - { - if (mDownloadMode) + if (hasWater) { - log(LOG_LEVEL_INFO, LOG_PUMP_AND_DOWNLOADMODE, LOG_PUMP_AND_DOWNLOADMODE_CODE); + if (mDownloadMode) + { + log(LOG_LEVEL_INFO, LOG_PUMP_AND_DOWNLOADMODE, LOG_PUMP_AND_DOWNLOADMODE_CODE); + pumpToRun = -1; + } } else { - //prevent BOD to be paranoid - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); - digitalWrite(OUTPUT_ENABLE_PUMP, HIGH); - delay(100); - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); - - rtcLastWateringPlant[lastPumpRunning] = getCurrentTime(); - mPlants[lastPumpRunning].activatePump(); + log(LOG_LEVEL_ERROR, LOG_PUMP_BUTNOTANK_MESSAGE, LOG_PUMP_BUTNOTANK_CODE); + pumpToRun = -1; } } - #if defined(TIMED_LIGHT_PIN) - bool shouldLight = determineTimedLightState(isLowLight); - timedLightOn = shouldLight; - digitalWrite(TIMED_LIGHT_PIN, shouldLight); - #endif // TIMED_LIGHT_PIN - - - /* Always handle one of the deep sleep duration */ - if (lastPumpRunning == -1 || !hasWater) + // go directly to sleep, skipping the pump loop + if (pumpToRun == -1) { - if (mSolarVoltage < SOLAR_CHARGE_MIN_VOLTAGE) - { - log(LOG_LEVEL_INFO, String(String(mSolarVoltage) + "V! No pumps to activate and low light, deepSleepNight"), LOG_NOPUMP_LOWLIGHT); - espDeepSleepFor(deepSleepNightTime.get(), false); - } - else - { - log(LOG_LEVEL_INFO, "No pumps to activate, deepSleep", LOG_NOPUMPS); - espDeepSleepFor(deepSleepTime.get(), false); - } - } - else - { - espDeepSleepFor(wateringDeepSleep.get(), true); + espDeepSleep(); } + +#if defined(TIMED_LIGHT_PIN) + bool shouldLight = determineTimedLightState(isLowLight); + timedLightOn = shouldLight; + digitalWrite(TIMED_LIGHT_PIN, shouldLight); +#endif // TIMED_LIGHT_PIN } /** @}*/ - -bool determineTimedLightState(bool lowLight){ +bool determineTimedLightState(bool lowLight) +{ bool onlyAllowedWhenDark = timedLightOnlyWhenDark.get(); long hoursStart = timedLightStart.get(); long hoursEnd = timedLightEnd.get(); //ntp missing - if(getCurrentTime() < 10000){ + if (getCurrentTime() < 10000) + { timedLightNode.setProperty("state").send(String("Off, missing ntp")); return false; } - if(onlyAllowedWhenDark && !lowLight){ + if (onlyAllowedWhenDark && !lowLight) + { timedLightNode.setProperty("state").send(String("Off, not dark")); timedLightLowVoltageTriggered = false; return false; } if (((hoursStart > hoursEnd) && - (getCurrentHour() >= hoursStart || getCurrentHour() <= hoursEnd)) || - /* Handle e.g. start = 8, end = 21 */ - ((hoursStart < hoursEnd) && - (getCurrentHour() >= hoursStart && getCurrentHour() <= hoursEnd))) - { - if(!timedLightLowVoltageTriggered && battery.getVoltage(BATTSENSOR_INDEX_BATTERY) >= timedLightVoltageCutoff.get() ){ - timedLightNode.setProperty("state").send(String("On")); - return true; - }else { - timedLightNode.setProperty("state").send(String("Off, due to missing voltage")); - timedLightLowVoltageTriggered = true; - return false; - } - - } else { - timedLightNode.setProperty("state").send(String("Off, outside worktime")); - return false; - } + (getCurrentHour() >= hoursStart || getCurrentHour() <= hoursEnd)) || + /* Handle e.g. start = 8, end = 21 */ + ((hoursStart < hoursEnd) && + (getCurrentHour() >= hoursStart && getCurrentHour() <= hoursEnd))) + { + if (!timedLightLowVoltageTriggered && battery.getVoltage(BATTSENSOR_INDEX_BATTERY) >= timedLightVoltageCutoff.get()) + { + timedLightNode.setProperty("state").send(String("On")); + return true; + } + else + { + timedLightNode.setProperty("state").send(String("Off, due to missing voltage")); + timedLightLowVoltageTriggered = true; + return false; + } + } + else + { + timedLightNode.setProperty("state").send(String("Off, outside worktime")); + return false; + } } void log(int level, String message, int statusCode)