added initial support for hydroponic systems, changed cooldown to use minutes

This commit is contained in:
Empire 2021-10-22 17:39:42 +00:00
parent 0bcef25528
commit 49664ba6f7
7 changed files with 70 additions and 96 deletions

View File

@ -1,55 +0,0 @@
{
"name": "PlantControl",
"device_id": "PlantCtrl1",
"device_stats_interval": 60,
"wifi": {
"ssid": "SSID",
"bssid" : "BSSID",
"password": "mysecretPassword",
"channel": 1
},
"mqtt": {
"host": "[0-255].[0-255].[0-255].[0-255]",
"port": 1883,
"base_topic": "mqtt/topic/",
"auth": false
},
"ota": {
"enabled": true
},
"settings": {
"deepsleep" : 60000,
"nightsleep" : 60000,
"watermaxlevel": 50,
"watermin" : 5,
"plants" : 3,
"moist0" : 2000,
"moist1" : 2000,
"moist2" : 2000,
"moist3" : 2000,
"moist4" : 2000,
"moist5" : 2000,
"moist6" : 2000,
"plant0MaxPumpTime": 1000,
"plant1MaxPumpTime": 1000,
"plant2MaxPumpTime": 1000,
"plant3MaxPumpTime": 1000,
"plant4MaxPumpTime": 1000,
"plant5MaxPumpTime": 1000,
"plant6MaxPumpTime": 1000,
"plant0MinPumpIdle": 10000,
"plant1MinPumpIdle": 10000,
"plant2MinPumpIdle": 10000,
"plant3MinPumpIdle": 10000,
"plant4MinPumpIdle": 10000,
"plant5MinPumpIdle": 10000,
"plant6MinPumpIdle": 10000,
"plant0PumpDuration": 5,
"plant1PumpDuration": 5,
"plant2PumpDuration": 5,
"plant3PumpDuration": 5,
"plant4PumpDuration": 5,
"plant5PumpDuration": 5,
"plant6PumpDuration": 5
}
}

View File

@ -14,36 +14,36 @@
"hourstart0":6, "hourstart0":6,
"hourend0":20, "hourend0":20,
"lowLight0": false, "lowLight0": false,
"delay0": 10, "delay0": 30,
"dry1":-1, "dry1":-1,
"hourstart1":6, "hourstart1":6,
"hourend1":20, "hourend1":20,
"lowLight1": false, "lowLight1": false,
"delay1": 10, "delay1": 30,
"dry2":-1, "dry2":-1,
"hourstart2":6, "hourstart2":6,
"hourend2":20, "hourend2":20,
"lowLight2": false, "lowLight2": false,
"delay2": 10, "delay2": 30,
"dry3":-1, "dry3":-1,
"hourstart3":6, "hourstart3":6,
"hourend3":20, "hourend3":20,
"lowLight3": false, "lowLight3": false,
"delay3": 10, "delay3": 30,
"dry4":-1, "dry4":-1,
"hourstart4":6, "hourstart4":6,
"hourend4":20, "hourend4":20,
"lowLight4": false, "lowLight4": false,
"delay4": 10, "delay4": 30,
"dry5":-1, "dry5":-1,
"hourstart5":6, "hourstart5":6,
"hourend5":20, "hourend5":20,
"lowLight5": false, "lowLight5": false,
"delay5": 10, "delay5": 30,
"dry6":-1, "dry6":-1,
"hourstart6":6, "hourstart6":6,
"hourend6":20, "hourend6":20,
"lowLight6": false, "lowLight6": false,
"delay6": 10 "delay6": 30
} }
} }

View File

@ -104,9 +104,9 @@ HomieSetting<const char *> ntpServer("ntpServer", "NTP server (pool.ntp.org as d
HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("hourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \ HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("hourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \
HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("hourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \ HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("hourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \
HomieSetting<bool> mPumpOnlyWhenLowLight##plant = HomieSetting<bool>("lowLight" strplant, "Plant" strplant " - Enable the Pump only, when there is no sunlight"); \ HomieSetting<bool> mPumpOnlyWhenLowLight##plant = HomieSetting<bool>("lowLight" strplant, "Plant" strplant " - Enable the Pump only, when there is no sunlight"); \
HomieSetting<long> mPumpCooldownInHours##plant = HomieSetting<long>("delay" strplant, "Plant" strplant " - How long to wait until the pump is activated again (minutes)"); \ HomieSetting<long> mPumpCooldownInMinutes##plant = HomieSetting<long>("delay" strplant, "Plant" strplant " - How long to wait until the pump is activated again (minutes)"); \
HomieSetting<long> pPumpDuration##plant = HomieSetting<long>("pumpDuration" strplant, "Plant" strplant " - time seconds or ml (if using flowmeter) to water when pump is active"); \ HomieSetting<long> pPumpDuration##plant = HomieSetting<long>("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}; \ PlantSettings_t mSetting##plant = {&mSensorDry##plant, &mPumpAllowedHourRangeStart##plant, &mPumpAllowedHourRangeEnd##plant, &mPumpOnlyWhenLowLight##plant, &mPumpCooldownInMinutes##plant, &pPumpDuration##plant}; \
/**< Generate all settings for one 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} \ * Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE} \

View File

@ -13,8 +13,12 @@
#include <Homie.h> #include <Homie.h>
//plant pump is deactivated, but sensor values are still recorded and published
#define DEACTIVATED_PLANT -1 #define DEACTIVATED_PLANT -1
//special value to indicate a missing sensor when the plant is not deactivated but no valid sensor value was read
#define MISSING_SENSOR -2 #define MISSING_SENSOR -2
//plant uses only cooldown and duration, moisture is measured but ignored, allowedHours is ignored (eg. make a 30min on 30min off cycle)
#define HYDROPONIC_MODE -3
typedef struct PlantSettings_t typedef struct PlantSettings_t
{ {
@ -22,7 +26,7 @@ typedef struct PlantSettings_t
HomieSetting<long> *pPumpAllowedHourRangeStart; HomieSetting<long> *pPumpAllowedHourRangeStart;
HomieSetting<long> *pPumpAllowedHourRangeEnd; HomieSetting<long> *pPumpAllowedHourRangeEnd;
HomieSetting<bool> *pPumpOnlyWhenLowLight; HomieSetting<bool> *pPumpOnlyWhenLowLight;
HomieSetting<long> *pPumpCooldownInHours; HomieSetting<long> *pPumpCooldownInMinutes;
HomieSetting<long> *pPumpDuration; HomieSetting<long> *pPumpDuration;
} PlantSettings_t; } PlantSettings_t;

View File

@ -61,6 +61,12 @@ public:
void activatePump(void); void activatePump(void);
bool isHydroponic(){
long current = this->mSetting->pSensorDry->get();
return !equalish(current,HYDROPONIC_MODE);
}
/** /**
* @brief Check if a plant is too dry and needs some water. * @brief Check if a plant is too dry and needs some water.
* *
@ -69,11 +75,17 @@ public:
*/ */
bool isPumpRequired() bool isPumpRequired()
{ {
if(isHydroponic()){
//hydroponic only uses timer based controll
return true;
}
bool isDry = getCurrentMoisture() > getSetting2Moisture(); bool isDry = getCurrentMoisture() > getSetting2Moisture();
bool isActive = isPumpTriggerActive(); bool isActive = isPumpTriggerActive();
return isDry && isActive; return isDry && isActive;
} }
bool isPumpTriggerActive() bool isPumpTriggerActive()
{ {
long current = this->mSetting->pSensorDry->get(); long current = this->mSetting->pSensorDry->get();
@ -110,7 +122,7 @@ public:
void init(void); void init(void);
long getCooldownInSeconds() { long getCooldownInSeconds() {
return this->mSetting->pPumpCooldownInHours->get()*60*60; return this->mSetting->pPumpCooldownInMinutes->get()*60;
} }
/** /**

View File

@ -46,9 +46,9 @@ void Plant::init(void)
return ((candidate >= 0) && (candidate <= 23)); return ((candidate >= 0) && (candidate <= 23));
}); });
this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true); this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true);
this->mSetting->pPumpCooldownInHours->setDefaultValue(20); // minutes this->mSetting->pPumpCooldownInMinutes->setDefaultValue(20); // minutes
this->mSetting->pPumpCooldownInHours->setValidator([](long candidate) { this->mSetting->pPumpCooldownInMinutes->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 1024)); return ((candidate >= 0) && (candidate <= 60*24*7));
}); });
this->mSetting->pPumpDuration->setDefaultValue(5); this->mSetting->pPumpDuration->setDefaultValue(5);

View File

@ -42,7 +42,6 @@
#define AMOUNT_SENOR_QUERYS 8 #define AMOUNT_SENOR_QUERYS 8
#define MAX_TANK_DEPTH 2000 #define MAX_TANK_DEPTH 2000
/****************************************************************************** /******************************************************************************
* FUNCTION PROTOTYPES * FUNCTION PROTOTYPES
******************************************************************************/ ******************************************************************************/
@ -69,7 +68,7 @@ RTC_DATA_ATTR long consecutiveWateringPlant[MAX_PLANTS] = {0};
******************************************************************************/ ******************************************************************************/
bool volatile mDownloadMode = false; /**< Controller must not sleep */ bool volatile mDownloadMode = false; /**< Controller must not sleep */
bool volatile mSensorsRead = false; /**< Sensors are read without Wifi or MQTT */ bool volatile mSensorsRead = false; /**< Sensors are read without Wifi or MQTT */
int volatile pumpToRun = -1; /** pump to run at the end of the cycle */ int volatile pumpToRun = -1; /** pump to run at the end of the cycle */
bool mConfigured = false; bool mConfigured = false;
long nextBlink = 0; /**< Time needed in main loop to support expected blink code */ long nextBlink = 0; /**< Time needed in main loop to support expected blink code */
@ -216,16 +215,16 @@ void readOneWireSensors()
Serial << "DS18S20 Temperatur " << String(buf) << " : " << temp << " °C " << endl; Serial << "DS18S20 Temperatur " << String(buf) << " : " << temp << " °C " << endl;
if (strcmp(lipoSensorAddr.get(), buf) == 0) if (strcmp(lipoSensorAddr.get(), buf) == 0)
{ {
mqttWrite(&sensorTemp,TEMPERATUR_SENSOR_LIPO,String(temp)); mqttWrite(&sensorTemp, TEMPERATUR_SENSOR_LIPO, String(temp));
Serial << "Lipo Temperatur " << temp << " °C " << endl; Serial << "Lipo Temperatur " << temp << " °C " << endl;
} }
if (strcmp(waterSensorAddr.get(), buf) == 0) if (strcmp(waterSensorAddr.get(), buf) == 0)
{ {
mqttWrite(&sensorTemp,TEMPERATUR_SENSOR_WATER,String(temp)); mqttWrite(&sensorTemp, TEMPERATUR_SENSOR_WATER, String(temp));
Serial << "Water Temperatur " << temp << " °C " << endl; Serial << "Water Temperatur " << temp << " °C " << endl;
} }
/* Always send the sensor address with the temperatur value */ /* Always send the sensor address with the temperatur value */
mqttWrite(&sensorTemp,String(buf),String(temp)); mqttWrite(&sensorTemp, String(buf), String(temp));
} }
else else
{ {
@ -397,22 +396,26 @@ int determineNextPump(bool isLowLight)
log(LOG_LEVEL_DEBUG, String(String(i) + " No pump required: due to light"), LOG_DEBUG_CODE); log(LOG_LEVEL_DEBUG, String(String(i) + " No pump required: due to light"), LOG_DEBUG_CODE);
continue; continue;
} }
if (equalish(plant.getCurrentMoisture(), MISSING_SENSOR)) if (!plant.isHydroponic())
{ {
plant.publishState("nosensor"); if (equalish(plant.getCurrentMoisture(), MISSING_SENSOR))
log(LOG_LEVEL_ERROR, String(String(i) + " No pump possible: missing sensor"), LOG_MISSING_PUMP); {
continue; plant.publishState("nosensor");
log(LOG_LEVEL_ERROR, String(String(i) + " No pump possible: missing sensor"), LOG_MISSING_PUMP);
continue;
}
} }
if (plant.isPumpRequired()) if (plant.isPumpRequired())
{ {
/* Handle e.g. start = 21, end = 8 */ /* Handle e.g. start = 21, end = 8 */
if (((plant.getHoursStart() > plant.getHoursEnd()) && if ( plant.isHydroponic() || (((plant.getHoursStart() > plant.getHoursEnd()) &&
(getCurrentHour() >= plant.getHoursStart() || getCurrentHour() <= plant.getHoursEnd())) || (getCurrentHour() >= plant.getHoursStart() || getCurrentHour() <= plant.getHoursEnd())) ||
/* Handle e.g. start = 8, end = 21 */ /* Handle e.g. start = 8, end = 21 */
((plant.getHoursStart() < plant.getHoursEnd()) && ((plant.getHoursStart() < plant.getHoursEnd()) &&
(getCurrentHour() >= plant.getHoursStart() && getCurrentHour() <= plant.getHoursEnd())) || (getCurrentHour() >= plant.getHoursStart() && getCurrentHour() <= plant.getHoursEnd())) ||
/* no time from NTP received */ /* no time from NTP received */
(getCurrentTime() < 10000)) (getCurrentTime() < 10000)))
{ {
if (wateralarm) if (wateralarm)
{ {
@ -423,7 +426,10 @@ int determineNextPump(bool isLowLight)
plant.publishState("active"); plant.publishState("active");
} }
consecutiveWateringPlant[i]++; if(!plant.isHydroponic()){
consecutiveWateringPlant[i]++;
}
log(LOG_LEVEL_DEBUG, String(String(i) + " Requested pumping"), LOG_DEBUG_CODE); log(LOG_LEVEL_DEBUG, String(String(i) + " Requested pumping"), LOG_DEBUG_CODE);
pumpToUse = i; pumpToUse = i;
} }
@ -573,21 +579,24 @@ void pumpActiveLoop()
#ifdef FLOWMETER_PIN #ifdef FLOWMETER_PIN
int16_t pulses; int16_t pulses;
pcnt_unit_t unit = (pcnt_unit_t) (PCNT_UNIT_7); pcnt_unit_t unit = (pcnt_unit_t)(PCNT_UNIT_7);
esp_err_t result = pcnt_get_counter_value(unit, &pulses); esp_err_t result = pcnt_get_counter_value(unit, &pulses);
if(result != ESP_OK){ if (result != ESP_OK)
{
log(LOG_LEVEL_ERROR, LOG_HARDWARECOUNTER_ERROR_MESSAGE, LOG_HARDWARECOUNTER_ERROR_CODE); log(LOG_LEVEL_ERROR, LOG_HARDWARECOUNTER_ERROR_MESSAGE, LOG_HARDWARECOUNTER_ERROR_CODE);
targetReached = true; targetReached = true;
} else { }
else
{
/**FLOWMETER_FLOWFACTOR * (L/Min) = F; /**FLOWMETER_FLOWFACTOR * (L/Min) = F;
given 1L/min -> FLOWMETER_FLOWFACTOR*60 pulses per liter given 1L/min -> FLOWMETER_FLOWFACTOR*60 pulses per liter
-> 1000/result -> ml pro pulse; -> 1000/result -> ml pro pulse;
-> result * pulses ->*/ -> result * pulses ->*/
long pumped = (FLOWMETER_FLOWFACTOR * 60) * pulses / 1000; long pumped = (FLOWMETER_FLOWFACTOR * 60) * pulses / 1000;
if(pumped >= pumpTargetMl) if (pumped >= pumpTargetMl)
{ {
targetReached = true; targetReached = true;
pcnt_counter_pause(unit); pcnt_counter_pause(unit);
} }
mPlants[pumpToRun].setProperty("waterusage").send(String(pumped)); mPlants[pumpToRun].setProperty("waterusage").send(String(pumped));
} }
@ -696,7 +705,8 @@ void setup()
// Set default values // Set default values
//in seconds //in seconds
deepSleepTime.setDefaultValue(600).setValidator([](long candidate) { return (candidate > 0) && (candidate < (60 * 60 * 2) /** 2h max sleep */); }); deepSleepTime.setDefaultValue(600).setValidator([](long candidate)
{ return (candidate > 0) && (candidate < (60 * 60 * 2) /** 2h max sleep */); });
deepSleepNightTime.setDefaultValue(600); deepSleepNightTime.setDefaultValue(600);
ntpServer.setDefaultValue("pool.ntp.org"); ntpServer.setDefaultValue("pool.ntp.org");
@ -706,13 +716,17 @@ void setup()
waterLevelVol.setDefaultValue(5000); /* 5l in ml */ waterLevelVol.setDefaultValue(5000); /* 5l in ml */
lipoSensorAddr.setDefaultValue(""); lipoSensorAddr.setDefaultValue("");
waterSensorAddr.setDefaultValue(""); waterSensorAddr.setDefaultValue("");
pumpIneffectiveWarning.setDefaultValue(5).setValidator([](long candidate) { return (candidate > 0) && (candidate < (20)); }); pumpIneffectiveWarning.setDefaultValue(5).setValidator([](long candidate)
{ return (candidate > 0) && (candidate < (20)); });
#if defined(TIMED_LIGHT_PIN) #if defined(TIMED_LIGHT_PIN)
timedLightStart.setDefaultValue(18).setValidator([](long candidate) { return (candidate > 0) && (candidate < (25)); }); timedLightStart.setDefaultValue(18).setValidator([](long candidate)
timedLightEnd.setDefaultValue(23).setValidator([](long candidate) { return (candidate > 0) && (candidate < (24)); }); { return (candidate > 0) && (candidate < (25)); });
timedLightEnd.setDefaultValue(23).setValidator([](long candidate)
{ return (candidate > 0) && (candidate < (24)); });
timedLightOnlyWhenDark.setDefaultValue(true); timedLightOnlyWhenDark.setDefaultValue(true);
timedLightVoltageCutoff.setDefaultValue(3.8).setValidator([](double candidate) { return (candidate > 3.3) && (candidate < (4.2)); }); timedLightVoltageCutoff.setDefaultValue(3.8).setValidator([](double candidate)
{ return (candidate > 3.3) && (candidate < (4.2)); });
#endif // TIMED_LIGHT_PIN #endif // TIMED_LIGHT_PIN
Homie.setLoopFunction(homieLoop); Homie.setLoopFunction(homieLoop);
@ -888,7 +902,7 @@ void plantcontrol()
readOneWireSensors(); readOneWireSensors();
Serial << "W : " << waterRawSensor.getMedian() << " cm (" << String(waterLevelMax.get() - waterRawSensor.getMedian ()) << "%)" << endl; Serial << "W : " << waterRawSensor.getAverage() << " cm (" << String(waterLevelMax.get() - waterRawSensor.getAverage()) << "%)" << endl;
float batteryVoltage = battery.getVoltage(BATTSENSOR_INDEX_BATTERY); float batteryVoltage = battery.getVoltage(BATTSENSOR_INDEX_BATTERY);
float chipTemp = battery.getTemperature(); float chipTemp = battery.getTemperature();
@ -896,14 +910,14 @@ void plantcontrol()
if (aliveWasRead()) if (aliveWasRead())
{ {
float remaining = waterLevelMax.get() - waterRawSensor.getMedian(); float remaining = waterLevelMax.get() - waterRawSensor.getAverage();
if (!isnan(remaining)) if (!isnan(remaining))
{ {
sensorWater.setProperty("remaining").send(String(remaining)); sensorWater.setProperty("remaining").send(String(remaining));
} }
if (!isnan(waterRawSensor.getMedian())) if (!isnan(waterRawSensor.getAverage()))
{ {
sensorWater.setProperty("distance").send(String(waterRawSensor.getMedian())); sensorWater.setProperty("distance").send(String(waterRawSensor.getAverage()));
} }
sensorLipo.setProperty("percent").send(String(100 * batteryVoltage / VOLT_MAX_BATT)); sensorLipo.setProperty("percent").send(String(100 * batteryVoltage / VOLT_MAX_BATT));
sensorLipo.setProperty("volt").send(String(batteryVoltage)); sensorLipo.setProperty("volt").send(String(batteryVoltage));
@ -1004,4 +1018,3 @@ bool determineTimedLightState(bool lowLight)
} }
#endif #endif