diff --git a/esp32/include/ControllerConfiguration.h b/esp32/include/ControllerConfiguration.h index 45d7c0e..0085256 100644 --- a/esp32/include/ControllerConfiguration.h +++ b/esp32/include/ControllerConfiguration.h @@ -46,7 +46,6 @@ #define MIN_TIME_RUNNING 5UL /**< Amount of seconds the controller must stay awoken */ #define MAX_PLANTS 7 -#define EMPTY_LIPO_MULTIPL 3 /**< Multiplier to increase time for sleeping when lipo is empty */ #define MINIMUM_LIPO_VOLT 3.6f /**< Minimum voltage of the Lipo, that must be present */ #define NO_LIPO_VOLT 2.0f /**< No Lipo connected */ #define MINIMUM_SOLAR_VOLT 4.0f /**< Minimum voltage of the sun, to detect daylight */ @@ -59,4 +58,7 @@ #define MAX_CONFIG_SETTING_ITEMS 50 /**< Parameter, that can be configured in Homie */ +#define PANIK_MODE_DEEPSLEEP (60*60*5U) /**< 5 hours in usecond */ +#define PANIK_MODE_DEEPSLEEP_US (PANIK_MODE_DEEPSLEEP*1000*1000) + #endif diff --git a/esp32/include/HomieConfiguration.h b/esp32/include/HomieConfiguration.h index 510dd47..9146754 100644 --- a/esp32/include/HomieConfiguration.h +++ b/esp32/include/HomieConfiguration.h @@ -37,7 +37,6 @@ HomieNode stayAlive("stay", "alive", "alive"); /** *********************************** Settings ******************************* */ - HomieSetting deepSleepTime("deepsleep", "time in milliseconds to sleep (0 deactivats it)"); HomieSetting deepSleepNightTime("nightsleep", "time in milliseconds to sleep (0 uses same setting: deepsleep at night, too)"); HomieSetting wateringDeepSleep("pumpdeepsleep", "time seconds to sleep, while a pump is running"); diff --git a/esp32/include/PlantCtrl.h b/esp32/include/PlantCtrl.h index 41f735a..a955797 100644 --- a/esp32/include/PlantCtrl.h +++ b/esp32/include/PlantCtrl.h @@ -13,13 +13,12 @@ #define PLANT_CTRL_H #include "HomieTypes.h" +#include "RunningMedian.h" class Plant { private: - int mValue = 0; /**< Value of the moist sensor */ - - int mAnalogValue=0; /**< moist sensor values, used for a calculation */ + RunningMedian moistureRaw = RunningMedian(5); HomieNode* mPlant = NULL; public: @@ -45,14 +44,6 @@ public: */ void addSenseValue(int analogValue); - /** - * @brief Calculate the value based on the information - * @see amountMeasurePoints - * Internal memory, used by addSenseValue will be resetted - * @return int analog value - */ - void calculateSensorValue(int amountMeasurePoints); - /** * @brief Get the Sensor Pin of the analog measuring * @@ -67,7 +58,7 @@ public: */ int getPumpPin() { return mPinPump; } - int getSensorValue() { return mValue; } + int getSensorValue() { return moistureRaw.getMedian(); } /** * @brief Check if a plant is too dry and needs some water. @@ -76,7 +67,7 @@ public: * @return false */ bool isPumpRequired() { - return (this->mSetting->pSensorDry != NULL) && (this->mValue < this->mSetting->pSensorDry->get()); + return (this->mSetting->pSensorDry != NULL) && (this->moistureRaw.getMedian() < this->mSetting->pSensorDry->get()); } HomieInternals::SendingPromise& setProperty(const String& property) const { @@ -84,10 +75,6 @@ public: } void init(void); - - long getSettingSensorDry() { - return this->mSetting->pSensorDry->get(); - } }; #endif diff --git a/esp32/platformio.ini b/esp32/platformio.ini index e0e7544..ec166b1 100644 --- a/esp32/platformio.ini +++ b/esp32/platformio.ini @@ -19,3 +19,4 @@ board_build.partitions = defaultWithSmallerSpiffs.csv lib_deps = ArduinoJson@6.16.1 https://github.com/homieiot/homie-esp8266.git#v3.0 OneWire + DallasTemperature diff --git a/esp32/src/PlantCtrl.cpp b/esp32/src/PlantCtrl.cpp index 7290ae8..e7cf64d 100644 --- a/esp32/src/PlantCtrl.cpp +++ b/esp32/src/PlantCtrl.cpp @@ -20,7 +20,7 @@ Plant::Plant(int pinSensor, int pinPump,int plantId, HomieNode* plant, PlantSett } void Plant::init(void) { - this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT); + this->mSetting->pSensorDry->setDefaultValue(4095); this->mSetting->pSensorDry->setValidator([] (long candidate) { return (((candidate >= 0) && (candidate <= 4095) ) || candidate == DEACTIVATED_PLANT); }); @@ -41,10 +41,5 @@ void Plant::init(void) { } void Plant::addSenseValue(int analog) { - this->mAnalogValue += analog; -} - -void Plant::calculateSensorValue(int amountMeasurePoints) { - this->mValue = this->mAnalogValue / amountMeasurePoints; - this->mAnalogValue = 0; -} + this->moistureRaw.add(analog); +} \ No newline at end of file diff --git a/esp32/src/main.cpp b/esp32/src/main.cpp index 0c8d54d..1319b3f 100644 --- a/esp32/src/main.cpp +++ b/esp32/src/main.cpp @@ -17,6 +17,7 @@ #include "esp_sleep.h" #include "RunningMedian.h" #include +#include const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minutes */ @@ -28,6 +29,7 @@ const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minut /********************* non volatile enable after deepsleep *******************************/ +RTC_DATA_ATTR long gotoMode2AfterThisTimestamp = 0; RTC_DATA_ATTR long rtcDeepSleepTime = 0; /**< Time, when the microcontroller shall be up again */ RTC_DATA_ATTR long rtcLastActive0 = 0; RTC_DATA_ATTR long rtcMoistureTrigger0 = 0; /**= (MIN_TIME_RUNNING * MS_TO_S)) && (deepSleepTime.get() > 0)) { - Serial << "No W" << endl; - /* in 500 microseconds */ - wait4sleep.in(500, prepareSleep); - return; - } - } - sensorLipo.setProperty("percent").send( String(100 * lipoSenor / 4095) ); - sensorLipo.setProperty("volt").send( String(ADC_5V_TO_3V3(lipoSenor)) ); - sensorSolar.setProperty("percent").send(String((100 * solarSensor ) / 4095)); - sensorSolar.setProperty("volt").send( String(SOLAR_VOLT(solarSensor)) ); + sensorLipo.setProperty("percent").send( String(100 * lipoRawSensor.getAverage() / 4095) ); + sensorLipo.setProperty("volt").send( String(getBatteryVoltage()) ); + sensorSolar.setProperty("percent").send(String((100 * solarRawSensor.getAverage() ) / 4095)); + sensorSolar.setProperty("volt").send( String(getSolarVoltage()) ); float temp[2] = { TEMP_INIT_VALUE, TEMP_INIT_VALUE }; float* pFloat = temp; @@ -161,20 +186,39 @@ void mode2MQTT(){ bool lipoTempWarning = abs(temp[0] - temp[1]) > 5; if(lipoTempWarning){ - wait4sleep.in(500, prepareSleep); + Serial.println("Lipo temp incorrect, panic mode deepsleep"); + espDeepSleepFor(PANIK_MODE_DEEPSLEEP); return; } - digitalWrite(OUTPUT_PUMP, LOW); - for(int i=0; i < MAX_PLANTS; i++) { - digitalWrite(mPlants[i].mPinPump, LOW); - } - + bool hasWater = true;//FIXMEmWaterGone > waterLevelMin.get(); + //FIXME no water warning message lastPumpRunning = determineNextPump(); - if(lastPumpRunning != -1){ + if(lastPumpRunning != -1 && !hasWater){ + Serial.println("Want to pump but no water"); + } + if(lastPumpRunning != -1 && hasWater){ + digitalWrite(OUTPUT_PUMP, HIGH); setLastActivationForPump(lastPumpRunning, getCurrentTime()); digitalWrite(mPlants[lastPumpRunning].mPinPump, HIGH); } + if(lastPumpRunning == -1 || !hasWater){ + if(getSolarVoltage() < SOLAR_CHARGE_MIN_VOLTAGE){ + gotoMode2AfterThisTimestamp = getCurrentTime()+deepSleepNightTime.get(); + Serial.println("No pumps to activate and low light, deepSleepNight"); + espDeepSleepFor(deepSleepNightTime.get()); + }else { + gotoMode2AfterThisTimestamp = getCurrentTime()+deepSleepTime.get(); + Serial.println("No pumps to activate, deepSleep"); + espDeepSleepFor(deepSleepTime.get()); + } + + }else { + gotoMode2AfterThisTimestamp = 0; + Serial.println("Running pump, watering deepsleep"); + espDeepSleepFor(wateringDeepSleep.get()); + } + } void setMoistureTrigger(int plantId, long value){ @@ -255,7 +299,9 @@ long getLastActivationForPump(int plantId){ * These sensors (ADC2) can only be read when no Wifi is used. */ void readSensors() { - Serial << "rs" << endl; + Serial << "Read Sensors" << endl; + + readSystemSensors(); /* activate all sensors */ pinMode(OUTPUT_SENSOR, OUTPUT); @@ -305,15 +351,26 @@ void readSensors() { digitalWrite(OUTPUT_SENSOR, LOW); } - - - //Homie.getMqttClient().disconnect(); void onHomieEvent(const HomieEvent& event) { const String OFF = String("OFF"); switch(event.type) { + case HomieEventType::SENDING_STATISTICS: + mode2MQTT(); + Homie.getLogger() << "My statistics" << endl; + break; case HomieEventType::MQTT_READY: + //wait for rtc sync? + rtcDeepSleepTime = deepSleepTime.get(); + Serial << rtcDeepSleepTime << " ms ds" << endl; + + //saveguard, should be overriden in mode2MQTT normally + esp_sleep_enable_timer_wakeup( (rtcDeepSleepTime * 1000U) ); + + mode2MQTT(); + Homie.getLogger() << "MQTT 1" << endl; + plant0.setProperty("switch").send(OFF); plant1.setProperty("switch").send(OFF); plant2.setProperty("switch").send(OFF); @@ -322,46 +379,48 @@ void onHomieEvent(const HomieEvent& event) { plant5.setProperty("switch").send(OFF); plant6.setProperty("switch").send(OFF); - //wait for rtc sync? - rtcDeepSleepTime = deepSleepTime.get(); - if(!mode3Active){ - mode2MQTT(); - } - Homie.getLogger() << "MQTT 1" << endl; break; case HomieEventType::READY_TO_SLEEP: Homie.getLogger() << "rtsleep" << endl; esp_deep_sleep_start(); break; + case HomieEventType::OTA_STARTED: + digitalWrite(OUTPUT_SENSOR, HIGH); + mode3Active=true; + break; + case HomieEventType::OTA_SUCCESSFUL: + digitalWrite(OUTPUT_SENSOR, LOW); + break; + default: + break; } } int determineNextPump(){ - float solarValue = solarRawSensor.getMedian(); - bool isLowLight =(ADC_5V_TO_3V3(solarValue) > SOLAR_CHARGE_MIN_VOLTAGE || ADC_5V_TO_3V3(solarValue) < SOLAR_CHARGE_MAX_VOLTAGE); - - - + float solarValue = getSolarVoltage(); + bool isLowLight =(solarValue > SOLAR_CHARGE_MIN_VOLTAGE || solarValue < SOLAR_CHARGE_MAX_VOLTAGE); //FIXME instead of for, use sorted by last activation index to ensure equal runtime? for(int i=0; i < MAX_PLANTS; i++) { - mPlants[i].calculateSensorValue(AMOUNT_SENOR_QUERYS); - mPlants[i].setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095 )); long lastActivation = getLastActivationForPump(i); long sinceLastActivation = getCurrentTime()-lastActivation; //this pump is in cooldown skip it and disable low power mode trigger for it - if(mPlants[i].mSetting->pPumpCooldownInHours->get() > sinceLastActivation / 3600 / 1000){ - setMoistureTrigger(i, DEACTIVATED_PLANT); - continue; + if(mPlants[i].mSetting->pPumpCooldownInHours->get() > sinceLastActivation / 3600){ + Serial.println("Skipping due to cooldown"); + //setMoistureTrigger(i, DEACTIVATED_PLANT); + //continue; } //skip as it is not low light if(!isLowLight && mPlants[i].mSetting->pPumpOnlyWhenLowLight->get()){ + Serial.println("Skipping due to light"); continue; } if(mPlants->isPumpRequired()){ + Serial.println("Requested pumpin"); return i; } + Serial.println("No pump required"); } return -1; } @@ -415,13 +474,13 @@ bool switchGeneralPumpHandler(const int pump, const HomieRange& range, const Str */ bool aliveHandler(const HomieRange& range, const String& value) { if (range.isRange) return false; // only one controller is present + Serial << value << endl; if (value.equals("ON") || value.equals("On") || value.equals("1")) { mode3Active=true; } else { mode3Active=false; - esp_deep_sleep_start(); } - Serial << (mode3Active ? "stayalive" : "") << endl; + return true; } @@ -472,15 +531,19 @@ void systemInit(){ Homie_setFirmware("PlantControl", FIRMWARE_VERSION); // Set default values - deepSleepTime.setDefaultValue(300000); /* 5 minutes in milliseconds */ - deepSleepNightTime.setDefaultValue(0); - wateringDeepSleep.setDefaultValue(60000); /* 1 minute in milliseconds */ + + //in seconds + deepSleepTime.setDefaultValue(10); + deepSleepNightTime.setDefaultValue(30); + wateringDeepSleep.setDefaultValue(5); + waterLevelMax.setDefaultValue(1000); /* 100cm in mm */ waterLevelMin.setDefaultValue(50); /* 5cm in mm */ waterLevelWarn.setDefaultValue(500); /* 50cm in mm */ waterLevelVol.setDefaultValue(5000); /* 5l in ml */ Homie.setLoopFunction(homieLoop); + Homie.onEvent(onHomieEvent); Homie.setup(); mConfigured = Homie.isConfigured(); @@ -551,6 +614,12 @@ void systemInit(){ bool mode1(){ Serial.println("m1"); + Serial << getCurrentTime() << " curtime" << endl; + + if(rtcDeepSleepTime > 0){ + esp_sleep_enable_timer_wakeup( (rtcDeepSleepTime * 1000U) ); + } + readSensors(); //queue sensor values for @@ -598,7 +667,16 @@ bool mode1(){ } //check how long it was already in mode1 if to long goto mode2 - //TODO evaluate if something is to do + long cTime = getCurrentTime(); + if(cTime < 100000){ + Serial.println("Starting mode 2 due to missing ntp"); + //missing ntp time boot to mode3 + return true; + } + if(gotoMode2AfterThisTimestamp < cTime){ + Serial.println("Starting mode 2 after specified mode1 time"); + return true; + } return false; } @@ -636,6 +714,7 @@ void setup() { } /* read button */ pinMode(BUTTON, INPUT); + pinMode(OUTPUT_PUMP, OUTPUT); /* Disable Wifi and bluetooth */ WiFi.mode(WIFI_OFF); @@ -652,26 +731,12 @@ void setup() { // Big TODO use here the settings in RTC_Memory - // Configure Deep Sleep: - if (mConfigured && (deepSleepNightTime.get() > 0) && - ( SOLAR_VOLT(solarSensor) < MINIMUM_SOLAR_VOLT)) { - Serial << deepSleepNightTime.get() << "ms ds " << SOLAR_VOLT(solarSensor) << "V" << endl; - uint64_t usSleepTime = deepSleepNightTime.get() * 1000U; - esp_sleep_enable_timer_wakeup(usSleepTime); - }else if (mConfigured && deepSleepTime.get()) { - Serial << deepSleepTime.get() << " ms ds" << endl; - uint64_t usSleepTime = deepSleepTime.get() * 1000U; - esp_sleep_enable_timer_wakeup(usSleepTime); - } - - if (mConfigured && - (ADC_5V_TO_3V3(lipoSenor) < MINIMUM_LIPO_VOLT) && - (ADC_5V_TO_3V3(lipoSenor) > NO_LIPO_VOLT) && - (deepSleepTime.get()) ) { - long sleepEmptyLipo = (deepSleepTime.get() * EMPTY_LIPO_MULTIPL); - Serial << sleepEmptyLipo << " ms lipo " << ADC_5V_TO_3V3(lipoSenor) << "V" << endl; - esp_sleep_enable_timer_wakeup(sleepEmptyLipo * 1000U); - mDeepSleep = true; + //Panik mode, the Lipo is empty, sleep a long long time: + if ((getBatteryVoltage() < MINIMUM_LIPO_VOLT) && + (getBatteryVoltage() > NO_LIPO_VOLT)) { + Serial << PANIK_MODE_DEEPSLEEP << " s lipo " << getBatteryVoltage() << "V" << endl; + esp_sleep_enable_timer_wakeup(PANIK_MODE_DEEPSLEEP_US); + esp_deep_sleep_start(); } if(mode1()){ @@ -692,7 +757,7 @@ void loop() { Homie.loop(); if(millis() > 30000 && !mode3Active){ - Serial << (millis()/ 1000) << " ds watchdog" << endl; + Serial << (millis()/ 1000) << "s alive" << endl; Serial.flush(); esp_deep_sleep_start(); }