2020-11-06 22:19:16 +01:00
/** \addtogroup Controller
* @ {
2022-04-29 10:47:54 +02:00
*
2020-09-07 18:18:46 +02:00
* @ file main . cpp
* @ author Ollo
* @ brief PlantControl
* @ version 0.1
* @ date 2020 - 05 - 01
2022-04-29 10:47:54 +02:00
*
2020-09-07 18:18:46 +02:00
* @ copyright Copyright ( c ) 2020
*/
2020-12-21 16:12:46 +01:00
/******************************************************************************
* INCLUDES
2022-04-29 10:47:54 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-07-01 21:19:51 +02:00
# include "LogDefines.h"
2021-06-29 22:09:30 +02:00
# include "FileUtils.h"
2021-07-01 21:19:51 +02:00
# include "TimeUtils.h"
2020-09-07 18:18:46 +02:00
# include "PlantCtrl.h"
# include "ControllerConfiguration.h"
2020-10-16 16:22:48 +02:00
# include "HomieConfiguration.h"
2020-12-13 17:17:54 +01:00
# include "DallasTemperature.h"
2020-09-07 18:18:46 +02:00
# include <Homie.h>
2020-10-19 01:39:56 +02:00
# include "time.h"
2020-09-20 19:18:47 +02:00
# include "esp_sleep.h"
2020-10-16 19:26:05 +02:00
# include "RunningMedian.h"
2020-11-28 01:45:57 +01:00
# include "WakeReason.h"
2020-10-21 16:43:26 +02:00
# include <stdint.h>
2020-11-06 21:00:11 +01:00
# include <math.h>
2020-12-13 17:17:54 +01:00
# include <OneWire.h>
2021-02-26 09:33:16 +01:00
# include "DS2438.h"
2021-05-24 20:07:22 +02:00
# include "soc/soc.h"
# include "soc/rtc_cntl_reg.h"
2021-06-06 21:23:21 +02:00
# include <Wire.h>
# include <VL53L0X.h>
2021-10-06 21:24:34 +02:00
# include "driver/pcnt.h"
2021-10-06 22:00:17 +02:00
# include "MQTTUtils.h"
2021-12-05 02:36:03 +01:00
# include "esp_ota_ops.h"
# if defined(TIMED_LIGHT_PIN)
2022-04-29 10:47:54 +02:00
# include "ulp-pwm.h"
2021-12-05 02:36:03 +01:00
# endif
2020-12-21 16:12:46 +01:00
/******************************************************************************
* DEFINES
2022-04-29 10:47:54 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-11-04 21:57:40 +01:00
# define AMOUNT_SENOR_QUERYS 8
2021-07-01 20:39:51 +02:00
# define MAX_TANK_DEPTH 2000
2021-12-05 02:36:03 +01:00
# define REBOOT_LOOP_DETECTION_ERROR 5
2021-10-06 22:00:17 +02:00
2020-12-21 16:12:46 +01:00
/******************************************************************************
* FUNCTION PROTOTYPES
2022-04-29 10:47:54 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-12-21 16:12:46 +01:00
2021-08-29 20:45:50 +02:00
int determineNextPump ( bool lowLight ) ;
2021-07-01 20:50:47 +02:00
void plantcontrol ( ) ;
2021-05-24 14:58:35 +02:00
void readPowerSwitchedSensors ( ) ;
2021-08-29 20:45:50 +02:00
bool determineTimedLightState ( bool lowLight ) ;
2022-04-29 10:47:16 +02:00
bool otaRunning = false ;
2020-12-21 16:12:46 +01:00
/******************************************************************************
* NON VOLATILE VARIABLES in DEEP SLEEP
2022-04-29 10:47:54 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-08-29 20:45:50 +02:00
# if defined(TIMED_LIGHT_PIN)
2021-10-01 23:46:37 +02:00
RTC_DATA_ATTR bool timedLightLowVoltageTriggered = false ; /**remember if it was shut down due to voltage level */
# endif // TIMED_LIGHT_PIN
2021-02-26 09:33:16 +01:00
2021-07-01 23:09:02 +02:00
RTC_DATA_ATTR long rtcLastWateringPlant [ MAX_PLANTS ] = { 0 } ;
2021-08-23 00:58:37 +02:00
RTC_DATA_ATTR long consecutiveWateringPlant [ MAX_PLANTS ] = { 0 } ;
2021-02-26 09:33:16 +01:00
2020-12-21 16:12:46 +01:00
/******************************************************************************
* LOCAL VARIABLES
2022-04-29 10:47:54 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-04-07 22:42:31 +02:00
bool volatile mDownloadMode = false ; /**< Controller must not sleep */
2021-05-26 21:46:33 +02:00
bool volatile mSensorsRead = false ; /**< Sensors are read without Wifi or MQTT */
2021-10-22 19:39:42 +02:00
int volatile pumpToRun = - 1 ; /** pump to run at the end of the cycle */
2021-11-19 19:48:14 +01:00
int volatile selfTestPumpRun = - 1 ; /** pump to run at the end of the cycle */
2020-10-16 19:26:05 +02:00
2020-09-21 20:42:24 +02:00
bool mConfigured = false ;
2021-02-26 09:33:16 +01:00
long nextBlink = 0 ; /**< Time needed in main loop to support expected blink code */
2020-09-07 18:18:46 +02:00
2020-10-16 19:26:05 +02:00
RunningMedian waterRawSensor = RunningMedian ( 5 ) ;
2021-05-26 21:46:33 +02:00
float mSolarVoltage = 0.0f ; /**< Voltage from solar panels */
2021-05-24 14:58:35 +02:00
unsigned long setupFinishedTimestamp ;
2021-10-06 20:05:09 +02:00
bool pumpStarted = false ;
long pumpTarget = - 1 ;
2022-04-27 21:19:26 +02:00
long pumpStartTime = 0 ;
2021-11-19 19:48:14 +01:00
long lastSendPumpUpdate = 0 ;
2021-10-06 21:24:34 +02:00
# ifdef FLOWMETER_PIN
2021-10-06 20:05:09 +02:00
long pumpTargetMl = - 1 ;
# endif
2022-12-20 00:04:57 +01:00
float waterTemp = 30 ;
2021-02-16 22:25:12 +01:00
/*************************** Hardware abstraction *****************************/
2020-10-16 19:26:05 +02:00
2021-04-07 18:49:59 +02:00
OneWire oneWire ( SENSOR_ONEWIRE ) ;
2020-12-13 17:17:54 +01:00
DallasTemperature sensors ( & oneWire ) ;
2021-05-27 21:43:15 +02:00
DS2438 battery ( & oneWire , 0.0333333f , AMOUNT_SENOR_QUERYS ) ;
2021-06-06 21:23:21 +02:00
VL53L0X tankSensor ;
2020-09-07 18:18:46 +02:00
2022-03-06 14:25:14 +01:00
# ifndef PLANT0_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
# ifndef PLANT1_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
# ifndef PLANT2_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
# ifndef PLANT3_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
# ifndef PLANT4_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
# ifndef PLANT5_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
# ifndef PLANT6_SENSORTYPE
# error "Sensor type must be specified, see HomieTypes.h - Sensor types"
# endif
2020-11-04 21:57:40 +01:00
Plant mPlants [ MAX_PLANTS ] = {
2022-03-06 14:25:14 +01:00
Plant ( SENSOR_PLANT0 , OUTPUT_PUMP0 , 0 , & plant0 , & mSetting0 , PLANT0_SENSORTYPE ) ,
Plant ( SENSOR_PLANT1 , OUTPUT_PUMP1 , 1 , & plant1 , & mSetting1 , PLANT1_SENSORTYPE ) ,
Plant ( SENSOR_PLANT2 , OUTPUT_PUMP2 , 2 , & plant2 , & mSetting2 , PLANT2_SENSORTYPE ) ,
Plant ( SENSOR_PLANT3 , OUTPUT_PUMP3 , 3 , & plant3 , & mSetting3 , PLANT3_SENSORTYPE ) ,
Plant ( SENSOR_PLANT4 , OUTPUT_PUMP4 , 4 , & plant4 , & mSetting4 , PLANT4_SENSORTYPE ) ,
Plant ( SENSOR_PLANT5 , OUTPUT_PUMP5 , 5 , & plant5 , & mSetting5 , PLANT5_SENSORTYPE ) ,
Plant ( SENSOR_PLANT6 , OUTPUT_PUMP6 , 6 , & plant6 , & mSetting6 , PLANT6_SENSORTYPE ) } ;
2020-11-04 21:57:40 +01:00
2020-12-21 16:12:46 +01:00
/******************************************************************************
* LOCAL FUNCTIONS
2022-04-29 10:47:54 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-10-20 18:06:37 +02:00
2021-12-05 02:36:03 +01:00
void finsihedCycleSucessfully ( )
{
const esp_partition_t * running = esp_ota_get_running_partition ( ) ;
esp_ota_img_states_t ota_state ;
if ( esp_ota_get_state_partition ( running , & ota_state ) = = ESP_OK )
{
log ( LOG_LEVEL_INFO , " Get State Partition was Successfull " , LOG_BOOT_ERROR_DETECTION ) ;
if ( ota_state = = ESP_OTA_IMG_PENDING_VERIFY )
{
log ( LOG_LEVEL_INFO , " Diagnostics completed successfully! Marking as valid " , LOG_BOOT_ERROR_DETECTION ) ;
esp_ota_mark_app_valid_cancel_rollback ( ) ;
}
}
}
2021-10-27 01:42:01 +02:00
void espDeepSleep ( bool afterPump = false )
2020-11-04 21:57:40 +01:00
{
2021-04-07 22:42:31 +02:00
if ( mDownloadMode )
2020-11-04 21:57:40 +01:00
{
2021-07-01 22:06:50 +02:00
log ( LOG_LEVEL_DEBUG , " abort deepsleep, DownloadMode active " , LOG_DEBUG_CODE ) ;
2022-04-29 10:47:54 +02:00
// if we manage to get to the download mode, the device can be restored
2021-12-05 02:36:03 +01:00
finsihedCycleSucessfully ( ) ;
2020-10-31 03:51:35 +01:00
return ;
}
2021-10-06 22:00:17 +02:00
if ( aliveWasRead ( ) )
2021-05-26 21:46:33 +02:00
{
2021-05-24 19:37:27 +02:00
for ( int i = 0 ; i < 10 ; i + + )
2020-11-04 21:57:40 +01:00
{
2021-05-24 19:37:27 +02:00
long cTime = getCurrentTime ( ) ;
if ( cTime < 100000 )
{
delay ( 100 ) ;
}
else
{
break ;
}
2020-10-31 19:25:13 +01:00
}
2021-07-01 23:09:02 +02:00
if ( getCurrentTime ( ) < 100000 )
{
log ( LOG_LEVEL_DEBUG , " NTP timeout before deepsleep " , LOG_DEBUG_CODE ) ;
}
2020-10-31 19:25:13 +01:00
}
2021-10-01 23:46:37 +02:00
2021-12-05 16:13:02 +01:00
esp_sleep_pd_config ( ESP_PD_DOMAIN_RTC_PERIPH , ESP_PD_OPTION_ON ) ;
2021-10-01 23:46:37 +02:00
long secondsToSleep = - 1 ;
2021-10-27 01:42:01 +02:00
if ( afterPump )
2020-11-04 21:57:40 +01:00
{
2021-10-27 01:42:01 +02:00
log ( LOG_LEVEL_INFO , " AfterPump Cycle Resume " , LOG_SLEEP_CYCLE ) ;
secondsToSleep = 1 ;
2020-11-04 21:57:40 +01:00
}
else
{
2021-10-27 01:42:01 +02:00
if ( mSolarVoltage < SOLAR_CHARGE_MIN_VOLTAGE )
{
log ( LOG_LEVEL_INFO , String ( String ( mSolarVoltage ) + " V! Low light -> deepSleepNight " ) , LOG_SLEEP_NIGHT ) ;
secondsToSleep = deepSleepNightTime . get ( ) ;
}
else
{
log ( LOG_LEVEL_INFO , " Sunny -> deepSleep " , LOG_SLEEP_DAY ) ;
secondsToSleep = deepSleepTime . get ( ) ;
}
2020-10-23 16:47:40 +02:00
}
2021-10-01 23:46:37 +02:00
2021-12-05 02:36:03 +01:00
finsihedCycleSucessfully ( ) ;
2021-10-01 23:46:37 +02:00
esp_sleep_enable_timer_wakeup ( ( secondsToSleep * 1000U * 1000U ) ) ;
2021-10-06 22:00:17 +02:00
if ( aliveWasRead ( ) )
2021-05-26 21:46:33 +02:00
{
2021-07-01 23:09:02 +02:00
delay ( 1000 ) ;
2021-05-24 15:50:04 +02:00
Homie . prepareToSleep ( ) ;
2021-05-26 21:46:33 +02:00
}
else
{
2021-05-24 15:50:04 +02:00
esp_deep_sleep_start ( ) ;
}
2020-10-21 19:50:05 +02:00
}
2022-04-29 10:47:54 +02:00
// requires homie being started
2021-07-01 20:50:47 +02:00
void readOneWireSensors ( )
2021-05-26 21:46:33 +02:00
{
2021-05-29 21:37:27 +02:00
for ( uint8_t i = 0 ; i < sensors . getDeviceCount ( ) ; i + + )
2021-04-07 20:27:42 +02:00
{
2021-05-29 21:37:27 +02:00
uint8_t ds18b20Address [ 8 ] ;
2021-05-27 21:43:15 +02:00
bool valid = false ;
float temp = - 127 ;
for ( int retry = 0 ; retry < AMOUNT_SENOR_QUERYS & & ! valid ; retry + + )
{
2021-05-29 21:37:27 +02:00
bool validAddress = sensors . getAddress ( ds18b20Address , i ) ;
if ( validAddress & & sensors . validFamily ( ds18b20Address ) )
2021-05-27 21:43:15 +02:00
{
2021-05-29 21:37:27 +02:00
temp = sensors . getTempC ( ds18b20Address ) ;
if ( temp ! = - 127 )
{
valid = true ;
}
else
{
delay ( 10 ) ;
}
2021-05-27 21:43:15 +02:00
}
}
2021-06-06 21:23:21 +02:00
if ( ! valid )
{
2022-04-29 10:47:54 +02:00
// wrong family or crc errors on each retry
2021-05-29 21:37:27 +02:00
continue ;
2023-03-03 19:58:55 +01:00
}
char buf [ ( sizeof ( ds18b20Address ) * 2 ) + 1 ] ; /* additional byte for trailing terminator */
2021-04-07 20:27:42 +02:00
snprintf ( buf , sizeof ( buf ) , " %.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X " ,
ds18b20Address [ 0 ] ,
ds18b20Address [ 1 ] ,
ds18b20Address [ 2 ] ,
ds18b20Address [ 3 ] ,
ds18b20Address [ 4 ] ,
ds18b20Address [ 5 ] ,
ds18b20Address [ 6 ] ,
ds18b20Address [ 7 ] ) ;
2023-03-03 19:58:55 +01:00
if ( valid )
2021-05-26 21:46:33 +02:00
{
2021-05-29 21:37:27 +02:00
Serial < < " DS18S20 Temperatur " < < String ( buf ) < < " : " < < temp < < " °C " < < endl ;
2021-06-06 21:23:21 +02:00
if ( strcmp ( lipoSensorAddr . get ( ) , buf ) = = 0 )
2021-05-27 21:43:15 +02:00
{
2021-10-22 19:39:42 +02:00
mqttWrite ( & sensorTemp , TEMPERATUR_SENSOR_LIPO , String ( temp ) ) ;
2021-05-27 21:43:15 +02:00
Serial < < " Lipo Temperatur " < < temp < < " °C " < < endl ;
}
2021-06-06 21:23:21 +02:00
if ( strcmp ( waterSensorAddr . get ( ) , buf ) = = 0 )
2021-05-26 21:46:33 +02:00
{
2021-10-22 19:39:42 +02:00
mqttWrite ( & sensorTemp , TEMPERATUR_SENSOR_WATER , String ( temp ) ) ;
2021-05-27 21:43:15 +02:00
Serial < < " Water Temperatur " < < temp < < " °C " < < endl ;
2022-12-20 00:04:57 +01:00
waterTemp = temp ;
2021-04-07 20:27:42 +02:00
}
2021-05-27 21:43:15 +02:00
/* Always send the sensor address with the temperatur value */
2021-10-22 19:39:42 +02:00
mqttWrite ( & sensorTemp , String ( buf ) , String ( temp ) ) ;
2021-05-27 22:53:49 +02:00
}
else
{
2021-05-29 21:37:27 +02:00
Serial < < " DS18S20 sensor " < < String ( buf ) < < " could not be read " < < temp < < endl ;
2021-05-26 21:46:33 +02:00
}
2020-12-13 17:17:54 +01:00
}
2021-02-26 09:33:16 +01:00
2021-10-22 15:52:19 +02:00
battery . updateMultiple ( ) ;
2021-02-26 09:33:16 +01:00
mSolarVoltage = battery . getVoltage ( BATTSENSOR_INDEX_SOLAR ) * SOLAR_VOLT_FACTOR ;
2021-05-24 14:58:35 +02:00
Serial . flush ( ) ;
}
/**
* @ brief Sensors , that are connected to GPIOs , mandatory for WIFI .
* These sensors ( ADC2 ) can only be read when no Wifi is used .
*/
void readPowerSwitchedSensors ( )
{
2022-02-19 02:24:19 +01:00
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
Serial < < " Sensor " < < i < < " mode: " < < mPlants [ i ] . getSensorModeString ( ) < < endl ;
}
2021-05-24 14:58:35 +02:00
digitalWrite ( OUTPUT_ENABLE_SENSOR , HIGH ) ;
2021-07-21 21:23:58 +02:00
delay ( 50 ) ;
2021-07-01 20:39:51 +02:00
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
2021-07-21 21:23:58 +02:00
mPlants [ i ] . startMoistureMeasurement ( ) ;
2021-07-01 20:39:51 +02:00
}
2021-07-21 21:23:58 +02:00
delay ( MOISTURE_MEASUREMENT_DURATION ) ;
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
2020-12-13 17:17:54 +01:00
{
2021-07-21 21:23:58 +02:00
mPlants [ i ] . stopMoistureMeasurement ( ) ;
2020-12-13 17:17:54 +01:00
}
2021-07-21 21:34:14 +02:00
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
2022-02-12 05:26:54 +01:00
mPlants [ i ] . blockingMoistureMeasurement ( ) ;
}
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
2022-02-19 02:24:19 +01:00
Plant plant = mPlants [ i ] ;
switch ( plant . getSensorMode ( ) )
2022-02-12 05:26:54 +01:00
{
2023-02-12 12:51:52 +01:00
case FREQUENCY_MOD_RESISTANCE_PROBE : {
2022-02-19 02:24:19 +01:00
Serial < < " Plant " < < i < < " measurement: " < < mPlants [ i ] . getCurrentMoistureRaw ( ) < < " hz " < < mPlants [ i ] . getCurrentMoisturePCT ( ) < < " % " < < endl ;
break ;
}
case ANALOG_RESISTANCE_PROBE : {
2022-03-05 10:31:43 +01:00
Serial < < " Plant " < < i < < " measurement: " < < mPlants [ i ] . getCurrentMoistureRaw ( ) < < " mV " < < mPlants [ i ] . getCurrentMoisturePCT ( ) < < " % " < < endl ;
break ;
}
2022-02-19 02:24:19 +01:00
case NONE : {
}
2022-02-12 05:26:54 +01:00
}
2021-07-21 21:34:14 +02:00
}
2022-03-05 10:31:43 +01:00
waterRawSensor . clear ( ) ;
2021-06-06 21:23:21 +02:00
tankSensor . setTimeout ( 500 ) ;
long start = millis ( ) ;
bool distanceReady = false ;
while ( start + 500 > millis ( ) )
2021-05-24 14:58:35 +02:00
{
2021-06-06 21:23:21 +02:00
if ( tankSensor . init ( ) )
2021-05-24 14:58:35 +02:00
{
2021-06-06 21:23:21 +02:00
distanceReady = true ;
break ;
}
else
{
delay ( 20 ) ;
2021-05-24 14:58:35 +02:00
}
}
2021-06-06 21:23:21 +02:00
if ( distanceReady )
{
tankSensor . setSignalRateLimit ( 0.1 ) ;
// increase laser pulse periods (defaults are 14 and 10 PCLKs)
tankSensor . setVcselPulsePeriod ( VL53L0X : : VcselPeriodPreRange , 18 ) ;
tankSensor . setVcselPulsePeriod ( VL53L0X : : VcselPeriodFinalRange , 14 ) ;
tankSensor . setMeasurementTimingBudget ( 200000 ) ;
2023-03-03 19:58:55 +01:00
for ( int readCnt = 0 ; readCnt < 5 ; readCnt + + )
2021-06-06 21:23:21 +02:00
{
2021-07-01 21:19:51 +02:00
if ( ! tankSensor . timeoutOccurred ( ) )
{
2021-07-01 20:39:51 +02:00
uint16_t distance = tankSensor . readRangeSingleMillimeters ( ) ;
2021-07-01 21:19:51 +02:00
if ( distance < MAX_TANK_DEPTH )
{
waterRawSensor . add ( distance ) ;
2021-07-01 20:39:51 +02:00
}
2021-06-06 21:23:21 +02:00
}
delay ( 10 ) ;
}
Serial < < " Distance sensor " < < waterRawSensor . getMedian ( ) < < " mm " < < endl ;
}
else
{
2021-07-01 23:09:02 +02:00
log ( LOG_LEVEL_WARN , LOG_TANKSENSOR_FAIL_DETECT , LOG_TANKSENSOR_FAIL_DETECT_CODE ) ;
2021-06-06 21:23:21 +02:00
}
2020-11-06 21:00:11 +01:00
2020-10-16 19:26:05 +02:00
/* deactivate the sensors */
2021-04-07 18:49:59 +02:00
digitalWrite ( OUTPUT_ENABLE_SENSOR , LOW ) ;
2020-09-07 18:18:46 +02:00
}
2020-11-04 21:57:40 +01:00
void onHomieEvent ( const HomieEvent & event )
{
switch ( event . type )
{
2021-05-24 15:50:04 +02:00
case HomieEventType : : READY_TO_SLEEP :
esp_deep_sleep_start ( ) ;
break ;
2020-11-04 21:57:40 +01:00
case HomieEventType : : SENDING_STATISTICS :
break ;
case HomieEventType : : MQTT_READY :
2021-05-26 21:46:33 +02:00
if ( mSensorsRead )
{
2021-04-07 21:54:53 +02:00
Serial . printf ( " Timeout occured... too late! \r \n " ) ;
return ;
}
mSensorsRead = true ; // MQTT is working, deactivate timeout logic
2021-10-06 20:05:09 +02:00
configTime ( UTC_OFFSET_DE , UTF_OFFSET_DE_DST , ntpServer . get ( ) ) ;
2021-10-06 22:00:17 +02:00
startMQTTRoundtripTest ( ) ;
2020-11-04 21:57:40 +01:00
break ;
case HomieEventType : : OTA_STARTED :
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
mPlants [ i ] . deactivatePump ( ) ;
}
2022-04-29 10:47:16 +02:00
otaRunning = true ;
2021-04-07 22:42:31 +02:00
mDownloadMode = true ;
2020-11-04 21:57:40 +01:00
break ;
case HomieEventType : : OTA_SUCCESSFUL :
2021-04-07 18:49:59 +02:00
digitalWrite ( OUTPUT_ENABLE_SENSOR , LOW ) ;
2021-05-24 20:07:22 +02:00
digitalWrite ( OUTPUT_ENABLE_PUMP , LOW ) ;
2020-11-04 21:57:40 +01:00
ESP . restart ( ) ;
break ;
default :
break ;
2020-09-07 18:18:46 +02:00
}
2020-10-19 01:39:56 +02:00
}
2020-09-07 18:18:46 +02:00
2021-08-29 20:45:50 +02:00
int determineNextPump ( bool isLowLight )
2020-11-04 21:57:40 +01:00
{
2020-11-04 21:10:22 +01:00
int pumpToUse = - 1 ;
2020-11-04 21:57:40 +01:00
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
2021-08-23 00:58:37 +02:00
bool wateralarm = consecutiveWateringPlant [ i ] > = pumpIneffectiveWarning . get ( ) ;
if ( wateralarm )
{
log ( LOG_LEVEL_ERROR , String ( String ( i ) + " Plant still dry after " + String ( consecutiveWateringPlant [ i ] ) + " watering attempts " ) , LOG_PUMP_INEFFECTIVE ) ;
}
2020-11-04 21:10:22 +01:00
Plant plant = mPlants [ i ] ;
2021-04-07 21:26:11 +02:00
if ( ! plant . isPumpTriggerActive ( ) )
{
2021-07-01 22:06:50 +02:00
plant . publishState ( " deactivated " ) ;
2021-07-01 22:15:55 +02:00
log ( LOG_LEVEL_DEBUG , String ( String ( i ) + " Skip deactivated pump " ) , LOG_DEBUG_CODE ) ;
2021-04-07 21:26:11 +02:00
continue ;
}
2021-07-01 20:39:51 +02:00
if ( ( rtcLastWateringPlant [ i ] + plant . getCooldownInSeconds ( ) ) > getCurrentTime ( ) )
2021-05-26 21:46:33 +02:00
{
2021-08-23 00:58:37 +02:00
if ( wateralarm )
{
plant . publishState ( " cooldown+alarm " ) ;
}
else
{
plant . publishState ( " cooldown " ) ;
}
2021-07-01 22:15:55 +02:00
log ( LOG_LEVEL_DEBUG , String ( String ( i ) + " Skipping due to cooldown " + String ( rtcLastWateringPlant [ i ] + plant . getCooldownInSeconds ( ) ) ) , LOG_DEBUG_CODE ) ;
2021-04-07 21:26:11 +02:00
continue ;
}
2020-11-04 21:57:40 +01:00
if ( ! isLowLight & & plant . isAllowedOnlyAtLowLight ( ) )
{
2021-08-23 00:58:37 +02:00
if ( wateralarm )
{
plant . publishState ( " sunny+alarm " ) ;
}
else
{
plant . publishState ( " sunny " ) ;
}
2021-07-01 22:15:55 +02:00
log ( LOG_LEVEL_DEBUG , String ( String ( i ) + " No pump required: due to light " ) , LOG_DEBUG_CODE ) ;
2020-10-19 01:39:56 +02:00
continue ;
}
2022-05-20 01:00:04 +02:00
if ( ! ( plant . isHydroponic ( ) | | plant . isTimerOnly ( ) ) )
2020-11-06 21:00:11 +01:00
{
2022-02-12 05:26:54 +01:00
if ( equalish ( plant . getCurrentMoistureRaw ( ) , MISSING_SENSOR ) )
2021-10-22 19:39:42 +02:00
{
plant . publishState ( " nosensor " ) ;
log ( LOG_LEVEL_ERROR , String ( String ( i ) + " No pump possible: missing sensor " ) , LOG_MISSING_PUMP ) ;
continue ;
}
2020-11-04 23:28:08 +01:00
}
2021-10-22 19:39:42 +02:00
2020-11-04 21:57:40 +01:00
if ( plant . isPumpRequired ( ) )
{
2021-05-26 21:46:33 +02:00
/* Handle e.g. start = 21, end = 8 */
2021-10-27 01:42:01 +02:00
if ( plant . isHydroponic ( ) | | ( ( ( plant . getHoursStart ( ) > plant . getHoursEnd ( ) ) & &
( getCurrentHour ( ) > = plant . getHoursStart ( ) | | getCurrentHour ( ) < = plant . getHoursEnd ( ) ) ) | |
/* Handle e.g. start = 8, end = 21 */
( ( plant . getHoursStart ( ) < plant . getHoursEnd ( ) ) & &
( getCurrentHour ( ) > = plant . getHoursStart ( ) & & getCurrentHour ( ) < = plant . getHoursEnd ( ) ) ) | |
/* no time from NTP received */
( getCurrentTime ( ) < 10000 ) ) )
2021-05-26 20:19:27 +02:00
{
2021-08-23 00:58:37 +02:00
if ( wateralarm )
{
plant . publishState ( " active+alarm " ) ;
}
else
{
2022-04-27 21:19:26 +02:00
if ( mDownloadMode ) {
plant . publishState ( " active+supressed " ) ;
} else {
plant . publishState ( " active " ) ;
}
2021-08-23 00:58:37 +02:00
}
2022-05-20 01:00:04 +02:00
if ( ! ( plant . isHydroponic ( ) | | plant . isTimerOnly ( ) ) )
2021-10-27 01:42:01 +02:00
{
2021-10-22 19:39:42 +02:00
consecutiveWateringPlant [ i ] + + ;
}
2021-07-01 22:15:55 +02:00
log ( LOG_LEVEL_DEBUG , String ( String ( i ) + " Requested pumping " ) , LOG_DEBUG_CODE ) ;
2021-04-07 21:26:11 +02:00
pumpToUse = i ;
2021-10-27 01:42:01 +02:00
return pumpToUse ;
2021-05-26 21:46:33 +02:00
}
else
{
2021-08-23 00:58:37 +02:00
if ( wateralarm )
{
plant . publishState ( " after-work+alarm " ) ;
}
else
{
plant . publishState ( " after-work " ) ;
}
2021-07-01 22:15:55 +02:00
log ( LOG_LEVEL_DEBUG , String ( String ( i ) + " ignored due to time boundary: " + String ( plant . getHoursStart ( ) ) + " to " + String ( plant . getHoursEnd ( ) ) + " ( current " + String ( getCurrentHour ( ) ) + " ) " ) , LOG_DEBUG_CODE ) ;
2021-04-07 21:26:11 +02:00
}
continue ;
2020-11-04 21:57:40 +01:00
}
else
{
2021-07-01 22:06:50 +02:00
plant . publishState ( " wet " ) ;
2022-04-29 10:47:54 +02:00
// plant was detected as wet, remove consecutive count
2021-08-23 00:58:37 +02:00
consecutiveWateringPlant [ i ] = 0 ;
2020-09-07 18:18:46 +02:00
}
}
2021-10-26 20:46:40 +02:00
return - 1 ;
2020-10-19 01:39:56 +02:00
}
2020-09-07 18:18:46 +02:00
2020-10-13 20:16:28 +02:00
/**
* @ brief Handle Mqtt commands to keep controller alive
2022-04-29 10:47:54 +02:00
*
2020-10-13 20:16:28 +02:00
* @ param range multiple transmitted values ( not used for this function )
* @ param value single value
* @ return true when the command was parsed and executed succuessfully
* @ return false on errors when parsing the request
*/
2020-11-04 21:57:40 +01:00
bool aliveHandler ( const HomieRange & range , const String & value )
{
2021-07-01 23:09:02 +02:00
if ( range . isRange )
{
2020-11-04 21:57:40 +01:00
return false ; // only one controller is present
2021-07-01 22:06:50 +02:00
}
2021-07-01 23:09:02 +02:00
2020-11-04 21:57:40 +01:00
if ( value . equals ( " ON " ) | | value . equals ( " On " ) | | value . equals ( " 1 " ) )
{
2021-04-07 22:42:31 +02:00
mDownloadMode = true ;
2020-11-04 21:57:40 +01:00
}
else
{
2021-05-26 21:46:33 +02:00
if ( mDownloadMode )
{
2021-05-24 19:37:27 +02:00
esp_restart ( ) ;
}
2021-04-07 22:42:31 +02:00
mDownloadMode = false ;
2020-11-04 21:57:40 +01:00
}
2020-10-13 20:16:28 +02:00
return true ;
}
2021-05-24 19:37:27 +02:00
bool notStarted = true ;
2020-11-04 21:57:40 +01:00
void homieLoop ( )
{
2021-10-06 22:00:17 +02:00
if ( aliveWasRead ( ) & & notStarted )
2021-05-26 21:46:33 +02:00
{
2021-05-24 19:37:27 +02:00
Serial . println ( " received alive & mqtt is ready " ) ;
notStarted = false ;
2021-07-01 20:50:47 +02:00
plantcontrol ( ) ;
2021-05-24 19:37:27 +02:00
}
2020-10-20 20:12:27 +02:00
}
2021-08-23 00:58:37 +02:00
bool switch1 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 0 ] . switchHandler ( range , value ) ;
}
2021-08-23 00:58:37 +02:00
bool switch2 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 1 ] . switchHandler ( range , value ) ;
}
2021-08-23 00:58:37 +02:00
bool switch3 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 2 ] . switchHandler ( range , value ) ;
}
2021-08-23 00:58:37 +02:00
bool switch4 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 3 ] . switchHandler ( range , value ) ;
}
2021-08-23 00:58:37 +02:00
bool switch5 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 4 ] . switchHandler ( range , value ) ;
}
2021-08-23 00:58:37 +02:00
bool switch6 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 5 ] . switchHandler ( range , value ) ;
}
2021-08-23 00:58:37 +02:00
bool switch7 ( const HomieRange & range , const String & value )
{
2021-07-09 22:51:50 +02:00
return mPlants [ 6 ] . switchHandler ( range , value ) ;
}
2021-10-06 21:24:34 +02:00
void initPumpLogic ( )
{
2022-04-29 10:47:54 +02:00
// set targets
2021-10-27 01:42:01 +02:00
2021-10-06 21:24:34 +02:00
# ifdef FLOWMETER_PIN
2022-04-27 21:19:26 +02:00
pumpTargetMl = mPlants [ pumpToRun ] . getPumpMl ( ) ;
2021-10-06 21:24:34 +02:00
2022-04-29 10:47:54 +02:00
// 0-6 are used for moisture measurment
2021-10-06 21:24:34 +02:00
pcnt_unit_t unit = ( pcnt_unit_t ) ( PCNT_UNIT_7 ) ;
pcnt_config_t pcnt_config = { } ; // Instancia PCNT config
pcnt_config . pulse_gpio_num = FLOWMETER_PIN ; // Configura GPIO para entrada dos pulsos
pcnt_config . ctrl_gpio_num = PCNT_PIN_NOT_USED ; // Configura GPIO para controle da contagem
pcnt_config . unit = unit ; // Unidade de contagem PCNT - 0
pcnt_config . channel = PCNT_CHANNEL_0 ; // Canal de contagem PCNT - 0
pcnt_config . counter_h_lim = INT16_MAX ; // Limite maximo de contagem - 20000
2022-04-27 21:19:26 +02:00
pcnt_config . pos_mode = PCNT_COUNT_INC ; // Incrementa contagem na subida do pulso
pcnt_config . neg_mode = PCNT_COUNT_DIS ; // Incrementa contagem na descida do pulso
2021-10-06 21:24:34 +02:00
pcnt_config . lctrl_mode = PCNT_MODE_KEEP ; // PCNT - modo lctrl desabilitado
pcnt_config . hctrl_mode = PCNT_MODE_KEEP ; // PCNT - modo hctrl - se HIGH conta incrementando
pcnt_unit_config ( & pcnt_config ) ; // Configura o contador PCNT
pcnt_counter_clear ( unit ) ; // Zera o contador PCNT
pcnt_counter_resume ( unit ) ;
2021-11-19 19:48:14 +01:00
# endif
2022-04-27 21:19:26 +02:00
pumpStartTime = millis ( ) ;
2021-10-27 01:42:01 +02:00
pumpTarget = millis ( ) + ( mPlants [ pumpToRun ] . getPumpDuration ( ) * 1000 ) ;
2022-04-27 21:19:26 +02:00
# ifdef FLOWMETER_PIN
log ( LOG_LEVEL_INFO , " Starting pump " + String ( pumpToRun ) + " for " + String ( mPlants [ pumpToRun ] . getPumpDuration ( ) ) + " s or " + String ( pumpTargetMl ) + " ml " , LOG_PUMP_STARTED_CODE ) ;
# else
log ( LOG_LEVEL_INFO , " Starting pump " + String ( pumpToRun ) + " for " + String ( mPlants [ pumpToRun ] . getPumpDuration ( ) ) + " s " , LOG_PUMP_STARTED_CODE ) ;
# endif
2021-11-19 19:48:14 +01:00
2022-04-29 10:47:54 +02:00
// enable power
2021-10-06 21:24:34 +02:00
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 ( ) ;
}
2021-10-01 23:46:37 +02:00
void pumpActiveLoop ( )
{
2021-10-06 20:05:09 +02:00
bool targetReached = false ;
2021-10-01 23:46:37 +02:00
if ( ! pumpStarted )
{
2021-10-06 21:24:34 +02:00
initPumpLogic ( ) ;
2021-10-01 23:46:37 +02:00
pumpStarted = true ;
2021-10-27 00:40:46 +02:00
rtcLastWateringPlant [ pumpToRun ] = getCurrentTime ( ) ;
2021-10-01 23:46:37 +02:00
}
2021-11-19 19:48:14 +01:00
bool mqttUpdateTick = false ;
if ( lastSendPumpUpdate + 1000 < millis ( ) )
{
lastSendPumpUpdate = millis ( ) ;
mqttUpdateTick = true ;
}
2022-04-29 10:47:54 +02:00
long duration = millis ( ) - pumpStartTime ;
2021-10-06 21:24:34 +02:00
# ifdef FLOWMETER_PIN
int16_t pulses ;
2021-10-22 19:39:42 +02:00
pcnt_unit_t unit = ( pcnt_unit_t ) ( PCNT_UNIT_7 ) ;
2021-10-06 21:24:34 +02:00
esp_err_t result = pcnt_get_counter_value ( unit , & pulses ) ;
2021-10-22 19:39:42 +02:00
if ( result ! = ESP_OK )
{
2021-10-06 21:24:34 +02:00
log ( LOG_LEVEL_ERROR , LOG_HARDWARECOUNTER_ERROR_MESSAGE , LOG_HARDWARECOUNTER_ERROR_CODE ) ;
2021-10-01 23:46:37 +02:00
targetReached = true ;
2022-04-27 21:19:26 +02:00
log ( LOG_LEVEL_INFO , " Reached pump target ml " + String ( pumpToRun ) , LOG_PUMP_STARTED_CODE ) ;
2021-10-22 19:39:42 +02:00
}
else
{
2022-04-29 10:47:54 +02:00
float mLPumped = pulses / FLOWMETER_PULSES_PER_ML ; // mLperMs*duration;
2022-04-27 21:19:26 +02:00
if ( mLPumped > = pumpTargetMl )
2021-10-06 21:24:34 +02:00
{
targetReached = true ;
2021-10-22 19:39:42 +02:00
pcnt_counter_pause ( unit ) ;
2022-04-27 21:19:26 +02:00
mPlants [ pumpToRun ] . setProperty ( " pulses " ) . send ( String ( pulses ) ) ;
mPlants [ pumpToRun ] . setProperty ( " waterusage " ) . send ( String ( mLPumped ) ) ;
2021-11-19 19:48:14 +01:00
}
2022-04-29 10:47:54 +02:00
2021-11-19 19:48:14 +01:00
else if ( mqttUpdateTick )
{
2022-04-27 21:19:26 +02:00
mPlants [ pumpToRun ] . setProperty ( " pulses " ) . send ( String ( pulses ) ) ;
mPlants [ pumpToRun ] . setProperty ( " waterusage " ) . send ( String ( mLPumped ) ) ;
2021-10-06 21:24:34 +02:00
}
2021-10-01 23:46:37 +02:00
}
2021-12-03 19:36:10 +01:00
# endif
2021-11-19 19:48:14 +01:00
2021-10-27 01:42:01 +02:00
if ( millis ( ) > pumpTarget )
2021-10-01 23:46:37 +02:00
{
2021-11-19 19:48:14 +01:00
mPlants [ pumpToRun ] . setProperty ( " watertime " ) . send ( String ( duration ) ) ;
2021-10-01 23:46:37 +02:00
targetReached = true ;
}
2021-11-19 19:48:14 +01:00
else if ( mqttUpdateTick )
{
mPlants [ pumpToRun ] . setProperty ( " watertime " ) . send ( String ( duration ) ) ;
}
2021-10-01 23:46:37 +02:00
if ( targetReached )
{
2021-10-06 21:24:34 +02:00
2022-04-29 10:47:54 +02:00
// disable all
2021-10-01 23:46:37 +02:00
digitalWrite ( OUTPUT_ENABLE_PUMP , LOW ) ;
2022-04-27 21:19:26 +02:00
mPlants [ pumpToRun ] . deactivatePump ( ) ;
2022-04-29 10:47:54 +02:00
// disable loop, to prevent multi processing
2021-10-01 23:46:37 +02:00
pumpStarted = false ;
2022-04-29 10:47:54 +02:00
// if runtime is larger than cooldown, else it would run continously
2021-10-27 00:40:46 +02:00
rtcLastWateringPlant [ pumpToRun ] = getCurrentTime ( ) ;
2021-10-27 01:42:01 +02:00
espDeepSleep ( true ) ;
2021-10-01 23:46:37 +02:00
}
}
2021-11-13 17:26:39 +01:00
void safeSetup ( )
2020-11-04 21:57:40 +01:00
{
2021-04-07 21:54:53 +02:00
Serial . begin ( 115200 ) ;
2021-05-24 14:58:35 +02:00
2021-05-26 21:46:33 +02:00
Serial < < " Wifi mode set to " < < WIFI_OFF < < " to allow analog2 useage " < < endl ;
2021-05-24 14:58:35 +02:00
WiFi . mode ( WIFI_OFF ) ;
Serial . flush ( ) ;
2022-04-29 10:47:54 +02:00
// restore state before releasing pin, to prevent flickering
2021-10-01 23:46:37 +02:00
# if defined(TIMED_LIGHT_PIN)
2021-12-05 02:36:03 +01:00
ulp_pwm_init ( ) ;
2021-10-01 23:46:37 +02:00
# endif // TIMED_LIGHT_PIN
2021-07-01 20:39:51 +02:00
2021-04-07 21:54:53 +02:00
/* Intialize Plant */
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
mPlants [ i ] . init ( ) ;
}
// read button
pinMode ( BUTTON , INPUT ) ;
// Power pins
pinMode ( OUTPUT_ENABLE_PUMP , OUTPUT ) ;
2021-05-24 20:07:22 +02:00
2021-05-24 14:58:35 +02:00
digitalWrite ( OUTPUT_ENABLE_PUMP , LOW ) ;
2021-05-24 20:07:22 +02:00
2021-04-07 21:54:53 +02:00
pinMode ( OUTPUT_ENABLE_SENSOR , OUTPUT ) ;
2022-04-29 10:47:46 +02:00
static_assert ( HomieInternals : : MAX_CONFIG_SETTING_SIZE > = MAX_CONFIG_SETTING_ITEMS , " Limits.hpp not adjusted MAX_CONFIG_SETTING_ITEMS " ) ;
2021-04-07 21:54:53 +02:00
if ( HomieInternals : : MAX_CONFIG_SETTING_SIZE < MAX_CONFIG_SETTING_ITEMS )
{
2022-04-29 10:47:54 +02:00
// increase the config settings
2021-05-24 14:58:35 +02:00
Serial < < " Limits.hpp is not adjusted, please search for this string and increase " < < endl ;
return ;
2021-04-07 21:54:53 +02:00
}
2022-04-29 10:47:46 +02:00
static_assert ( HomieInternals : : MAX_JSON_CONFIG_FILE_SIZE > = MAX_JSON_CONFIG_FILE_SIZE_CUSTOM , " Limits.hpp not adjusted MAX_JSON_CONFIG_FILE_SIZE " ) ;
2021-06-29 23:49:48 +02:00
if ( HomieInternals : : MAX_JSON_CONFIG_FILE_SIZE < MAX_JSON_CONFIG_FILE_SIZE_CUSTOM )
{
2022-04-29 10:47:54 +02:00
// increase the config settings
2021-06-29 23:49:48 +02:00
Serial < < " Limits.hpp is not adjusted, please search for this string and increase " < < endl ;
return ;
}
2021-04-07 21:54:53 +02:00
2022-04-29 10:47:54 +02:00
/************************* Start Homie Framework ***************/
2020-10-16 20:36:07 +02:00
Homie_setFirmware ( " PlantControl " , FIRMWARE_VERSION ) ;
2021-06-29 22:09:30 +02:00
Homie . disableLedFeedback ( ) ;
Homie_setBrand ( " PlantControl " ) ;
2020-10-16 20:36:07 +02:00
// Set default values
2020-11-04 21:57:40 +01:00
2022-04-29 10:47:54 +02:00
// in seconds
2021-10-22 19:39:42 +02:00
deepSleepTime . setDefaultValue ( 600 ) . setValidator ( [ ] ( long candidate )
{ return ( candidate > 0 ) & & ( candidate < ( 60 * 60 * 2 ) /** 2h max sleep */ ) ; } ) ;
2020-10-31 12:44:49 +01:00
deepSleepNightTime . setDefaultValue ( 600 ) ;
2020-11-01 20:17:21 +01:00
ntpServer . setDefaultValue ( " pool.ntp.org " ) ;
2020-10-21 19:50:05 +02:00
2020-11-04 21:57:40 +01:00
/* waterLevelMax 1000 */ /* 100cm in mm */
waterLevelMin . setDefaultValue ( 50 ) ; /* 5cm in mm */
waterLevelWarn . setDefaultValue ( 500 ) ; /* 50cm in mm */
waterLevelVol . setDefaultValue ( 5000 ) ; /* 5l in ml */
2021-04-07 19:40:31 +02:00
lipoSensorAddr . setDefaultValue ( " " ) ;
waterSensorAddr . setDefaultValue ( " " ) ;
2021-10-22 19:39:42 +02:00
pumpIneffectiveWarning . setDefaultValue ( 5 ) . setValidator ( [ ] ( long candidate )
{ return ( candidate > 0 ) & & ( candidate < ( 20 ) ) ; } ) ;
2021-10-01 23:46:37 +02:00
# if defined(TIMED_LIGHT_PIN)
2021-12-05 02:36:03 +01:00
timedLightPowerLevel . setDefaultValue ( 25 ) . setValidator ( [ ] ( long candidate )
2022-04-29 10:47:54 +02:00
{ return ( candidate > 0 ) & & ( candidate < = ( 255 ) ) ; } ) ;
2021-10-22 19:39:42 +02:00
timedLightStart . setDefaultValue ( 18 ) . setValidator ( [ ] ( long candidate )
{ return ( candidate > 0 ) & & ( candidate < ( 25 ) ) ; } ) ;
timedLightEnd . setDefaultValue ( 23 ) . setValidator ( [ ] ( long candidate )
{ return ( candidate > 0 ) & & ( candidate < ( 24 ) ) ; } ) ;
2021-10-01 23:46:37 +02:00
timedLightOnlyWhenDark . setDefaultValue ( true ) ;
2021-10-22 19:39:42 +02:00
timedLightVoltageCutoff . setDefaultValue ( 3.8 ) . setValidator ( [ ] ( double candidate )
2021-12-02 14:40:06 +01:00
{ return ( ( candidate > 3.3 | | candidate = = - 1 ) & & ( candidate < ( 50 ) ) ) ; } ) ;
2021-10-01 23:46:37 +02:00
# endif // TIMED_LIGHT_PIN
2021-08-29 20:45:50 +02:00
2020-10-20 20:12:27 +02:00
Homie . setLoopFunction ( homieLoop ) ;
2020-10-21 18:14:51 +02:00
Homie . onEvent ( onHomieEvent ) ;
2021-05-26 21:46:33 +02:00
2022-03-05 10:31:43 +01:00
/* Intialize Plant */
2022-02-19 02:24:19 +01:00
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
mPlants [ i ] . initSensors ( ) ;
}
2023-03-03 19:58:55 +01:00
Wire . begin ( SENSOR_TANK_SDA , SENSOR_TANK_SCL ) ;
2022-03-05 10:31:43 +01:00
readPowerSwitchedSensors ( ) ;
Homie . setup ( ) ;
2022-04-29 10:47:54 +02:00
/************************* Start One-Wire bus ***************/
2022-02-12 05:26:54 +01:00
int tempInitStartTime = millis ( ) ;
uint8_t sensorCount = 0U ;
/* Required to read the temperature at least once */
while ( ( sensorCount = = 0 | | ! battery . isFound ( ) ) & & millis ( ) < tempInitStartTime + TEMPERATUR_TIMEOUT )
{
sensors . begin ( ) ;
battery . begin ( ) ;
sensorCount = sensors . getDS18Count ( ) ;
delay ( 50 ) ;
}
Serial < < " DS18S20 count: " < < sensorCount < < " found in " < < ( millis ( ) - tempInitStartTime ) < < " ms " < < endl ;
Serial . flush ( ) ;
/* Measure temperature TODO idea: move this into setup */
if ( sensorCount > 0 )
{
2023-03-03 19:58:55 +01:00
sensors . setResolution ( DS18B20_RESOLUTION ) ;
2022-02-12 05:26:54 +01:00
sensors . requestTemperatures ( ) ;
}
2020-10-16 21:50:42 +02:00
mConfigured = Homie . isConfigured ( ) ;
2020-11-04 21:57:40 +01:00
if ( mConfigured )
{
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
{
2020-10-23 16:20:34 +02:00
mPlants [ i ] . advertise ( ) ;
}
2021-07-09 22:51:50 +02:00
mPlants [ 0 ] . setSwitchHandler ( switch1 ) ;
mPlants [ 1 ] . setSwitchHandler ( switch2 ) ;
mPlants [ 2 ] . setSwitchHandler ( switch3 ) ;
mPlants [ 3 ] . setSwitchHandler ( switch4 ) ;
mPlants [ 4 ] . setSwitchHandler ( switch5 ) ;
mPlants [ 5 ] . setSwitchHandler ( switch6 ) ;
mPlants [ 6 ] . setSwitchHandler ( switch7 ) ;
2020-12-21 17:07:15 +01:00
sensorTemp . advertise ( TEMPERATUR_SENSOR_LIPO )
. setName ( TEMPERATURE_NAME )
. setDatatype ( NUMBER_TYPE )
. setUnit ( TEMPERATURE_UNIT ) ;
sensorTemp . advertise ( TEMPERATUR_SENSOR_WATER )
. setName ( TEMPERATURE_NAME )
. setDatatype ( NUMBER_TYPE )
. setUnit ( TEMPERATURE_UNIT ) ;
2021-02-16 22:29:07 +01:00
sensorTemp . advertise ( TEMPERATUR_SENSOR_CHIP )
. setName ( TEMPERATURE_NAME )
. setDatatype ( NUMBER_TYPE )
. setUnit ( TEMPERATURE_UNIT ) ;
2020-09-21 20:42:24 +02:00
sensorLipo . advertise ( " percent " )
2020-11-04 21:57:40 +01:00
. setName ( " Percent " )
2020-12-21 17:07:15 +01:00
. setDatatype ( NUMBER_TYPE )
2020-11-04 21:57:40 +01:00
. setUnit ( " % " ) ;
2020-09-21 20:42:24 +02:00
sensorLipo . advertise ( " volt " )
2020-11-04 21:57:40 +01:00
. setName ( " Volt " )
2020-12-21 17:07:15 +01:00
. setDatatype ( NUMBER_TYPE )
2020-11-04 21:57:40 +01:00
. setUnit ( " V " ) ;
2020-09-21 20:42:24 +02:00
sensorSolar . advertise ( " percent " )
2020-11-04 21:57:40 +01:00
. setName ( " Percent " )
2020-12-21 17:07:15 +01:00
. setDatatype ( NUMBER_TYPE )
2020-11-04 21:57:40 +01:00
. setUnit ( " % " ) ;
2020-09-21 20:42:24 +02:00
sensorSolar . advertise ( " volt " )
2020-11-04 21:57:40 +01:00
. setName ( " Volt " )
2020-12-21 17:07:15 +01:00
. setDatatype ( NUMBER_TYPE )
2020-11-04 21:57:40 +01:00
. setUnit ( " V " ) ;
2020-12-21 17:07:15 +01:00
sensorWater . advertise ( " remaining " ) . setDatatype ( NUMBER_TYPE ) . setUnit ( " % " ) ;
2021-05-26 21:46:33 +02:00
}
else
{
2021-07-01 21:19:51 +02:00
if ( doesFileExist ( CONFIG_FILE ) )
{
2021-06-29 22:09:30 +02:00
printFile ( CONFIG_FILE ) ;
}
2021-07-01 21:19:51 +02:00
if ( doesFileExist ( CONFIG_FILE_BACKUP ) )
{
2021-06-29 22:09:30 +02:00
printFile ( CONFIG_FILE_BACKUP ) ;
bool restoredConfig = copyFile ( CONFIG_FILE_BACKUP , CONFIG_FILE ) ;
2021-07-01 21:19:51 +02:00
if ( restoredConfig )
{
deleteFile ( CONFIG_FILE_BACKUP ) ;
2021-10-01 23:46:37 +02:00
espDeepSleep ( ) ;
2021-07-01 21:19:51 +02:00
return ;
2021-06-29 22:09:30 +02:00
}
}
2021-07-01 20:50:47 +02:00
readOneWireSensors ( ) ;
2022-04-29 10:47:54 +02:00
// prevent BOD to be paranoid
2021-06-06 21:23:21 +02:00
WRITE_PERI_REG ( RTC_CNTL_BROWN_OUT_REG , 0 ) ;
2021-05-26 21:46:33 +02:00
digitalWrite ( OUTPUT_ENABLE_PUMP , HIGH ) ;
delay ( 100 ) ;
2021-06-06 21:23:21 +02:00
WRITE_PERI_REG ( RTC_CNTL_BROWN_OUT_REG , 1 ) ;
2021-04-07 21:54:53 +02:00
Serial . println ( " Initial Setup. Start Accesspoint... " ) ;
2021-04-07 22:42:31 +02:00
mDownloadMode = true ;
2020-10-16 20:36:07 +02:00
}
2021-04-07 21:54:53 +02:00
stayAlive . advertise ( " alive " ) . setName ( " Alive " ) . setDatatype ( NUMBER_TYPE ) . settable ( aliveHandler ) ;
2021-05-24 14:58:35 +02:00
setupFinishedTimestamp = millis ( ) ;
2020-09-07 18:18:46 +02:00
}
2021-11-13 17:26:39 +01:00
/**
* @ brief Startup function
* Is called once , the controller is started
*/
void setup ( )
{
2023-03-03 19:58:55 +01:00
Serial . begin ( 115200 ) ;
Serial < < " First init " < < endl ;
Serial . flush ( ) ;
2021-11-13 17:26:39 +01:00
try
{
safeSetup ( ) ;
}
catch ( const std : : exception & e )
{
Serial . printf ( " Exception thrown: \" %s \" " , e . what ( ) ) ;
}
catch ( . . . )
{
Serial . println ( " Other exception thrown. " ) ;
}
}
2021-10-01 23:46:37 +02:00
void selfTest ( )
{
2021-11-19 19:48:14 +01:00
2021-10-27 02:05:48 +02:00
if ( selfTestPumpRun > = 0 & & selfTestPumpRun < MAX_PLANTS )
2021-10-01 23:46:37 +02:00
{
2021-10-27 01:42:01 +02:00
Serial < < " self test mode pump deactivate " < < pumpToRun < < endl ;
Serial . flush ( ) ;
2021-10-27 02:05:48 +02:00
mPlants [ selfTestPumpRun ] . deactivatePump ( ) ;
2021-10-01 23:46:37 +02:00
}
2021-10-27 02:05:48 +02:00
if ( selfTestPumpRun > = MAX_PLANTS )
2021-10-01 23:46:37 +02:00
{
2021-10-27 02:05:48 +02:00
Serial < < " self test finished all pumps, proceed to initial wait mode " < < selfTestPumpRun < < endl ;
2021-10-27 01:42:01 +02:00
Serial . flush ( ) ;
2021-10-01 23:46:37 +02:00
digitalWrite ( OUTPUT_ENABLE_PUMP , LOW ) ;
nextBlink = millis ( ) + 500 ;
}
else
{
2021-10-27 02:05:48 +02:00
selfTestPumpRun + + ;
2021-10-01 23:46:37 +02:00
nextBlink = millis ( ) + 5000 ;
}
2021-10-27 02:05:48 +02:00
if ( selfTestPumpRun < MAX_PLANTS )
2021-10-01 23:46:37 +02:00
{
2021-10-27 02:05:48 +02:00
Serial < < " self test activating pump " < < selfTestPumpRun < < endl ;
2021-10-27 01:42:01 +02:00
Serial . flush ( ) ;
2021-10-27 02:05:48 +02:00
mPlants [ selfTestPumpRun ] . activatePump ( ) ;
2021-10-01 23:46:37 +02:00
}
}
2020-09-07 18:18:46 +02:00
/**
* @ brief Cyclic call
* Executs the Homie base functionallity or triggers sleeping , if requested .
*/
2020-11-04 21:57:40 +01:00
void loop ( )
{
2021-05-24 14:58:35 +02:00
Homie . loop ( ) ;
2021-02-26 09:33:16 +01:00
/* Toggel Senor LED to visualize mode 3 */
2021-04-07 22:42:31 +02:00
if ( mDownloadMode )
2021-02-26 09:33:16 +01:00
{
if ( nextBlink < millis ( ) )
{
2021-04-07 18:49:59 +02:00
digitalWrite ( OUTPUT_ENABLE_SENSOR , ! digitalRead ( OUTPUT_ENABLE_SENSOR ) ) ;
2021-05-26 21:46:33 +02:00
if ( mConfigured )
{
2022-04-29 10:47:16 +02:00
if ( otaRunning )
{
nextBlink = millis ( ) + 100 ;
}
else
{
nextBlink = millis ( ) + 501 ;
}
2021-05-26 21:46:33 +02:00
}
else
{
2021-10-01 23:46:37 +02:00
selfTest ( ) ;
2021-05-26 21:46:33 +02:00
}
2021-02-26 09:33:16 +01:00
}
}
2021-05-26 21:46:33 +02:00
else
{
2021-05-24 14:58:35 +02:00
unsigned long timeSinceSetup = millis ( ) - setupFinishedTimestamp ;
2021-05-26 21:46:33 +02:00
if ( ( timeSinceSetup > MQTT_TIMEOUT ) & & ( ! mSensorsRead ) )
{
2021-04-07 21:54:53 +02:00
mSensorsRead = true ;
/* Disable Wifi and put modem into sleep mode */
WiFi . mode ( WIFI_OFF ) ;
2021-05-26 21:46:33 +02:00
Serial < < " Wifi mode set to " < < WIFI_OFF < < " mqqt was no reached within " < < timeSinceSetup < < " ms , fallback to offline mode " < < endl ;
2021-05-24 14:58:35 +02:00
Serial . flush ( ) ;
2021-07-01 20:50:47 +02:00
plantcontrol ( ) ;
2021-04-07 21:54:53 +02:00
}
2020-11-04 21:57:40 +01:00
}
2020-10-16 20:36:07 +02:00
2021-04-07 21:54:53 +02:00
/** Timeout always stopping the ESP -> no endless power consumption */
2021-07-09 19:40:51 +02:00
if ( millis ( ) > ESP_STALE_TIMEOUT & & ! mDownloadMode )
2020-11-04 21:57:40 +01:00
{
2021-02-26 09:33:16 +01:00
Serial < < ( millis ( ) / 1000 ) < < " not terminated watchdog reset " < < endl ;
2020-10-16 20:36:07 +02:00
Serial . flush ( ) ;
2021-02-26 09:33:16 +01:00
esp_restart ( ) ;
2020-11-01 20:42:45 +01:00
}
2021-10-01 23:46:37 +02:00
if ( pumpToRun ! = - 1 )
{
pumpActiveLoop ( ) ;
}
2020-09-07 18:18:46 +02:00
}
2020-11-06 22:19:16 +01:00
2021-04-07 21:54:53 +02:00
/***
* @ fn plantcontrol
* Main function , doing the logic
*/
2021-07-01 20:50:47 +02:00
void plantcontrol ( )
2021-04-07 21:54:53 +02:00
{
2021-10-06 22:00:17 +02:00
if ( aliveWasRead ( ) )
2021-04-07 21:54:53 +02:00
{
2021-07-09 21:50:51 +02:00
for ( int i = 0 ; i < MAX_PLANTS ; i + + )
2021-04-07 21:54:53 +02:00
{
2021-07-09 21:50:51 +02:00
mPlants [ i ] . postMQTTconnection ( ) ;
2021-08-23 00:58:37 +02:00
mPlants [ i ] . setProperty ( " consecutivePumps " ) . send ( String ( consecutiveWateringPlant [ i ] ) ) ;
2021-04-07 21:54:53 +02:00
}
}
2021-05-24 15:50:04 +02:00
2021-07-09 21:50:51 +02:00
readOneWireSensors ( ) ;
2021-10-22 19:39:42 +02:00
Serial < < " W : " < < waterRawSensor . getAverage ( ) < < " cm ( " < < String ( waterLevelMax . get ( ) - waterRawSensor . getAverage ( ) ) < < " %) " < < endl ;
2021-10-06 21:24:34 +02:00
2021-04-07 22:42:31 +02:00
float batteryVoltage = battery . getVoltage ( BATTSENSOR_INDEX_BATTERY ) ;
float chipTemp = battery . getTemperature ( ) ;
2021-05-24 15:50:04 +02:00
Serial < < " Chip Temperatur " < < chipTemp < < " °C " < < endl ;
2021-04-07 22:42:31 +02:00
2021-10-06 22:00:17 +02:00
if ( aliveWasRead ( ) )
2021-05-26 21:46:33 +02:00
{
2021-10-22 19:39:42 +02:00
float remaining = waterLevelMax . get ( ) - waterRawSensor . getAverage ( ) ;
2021-08-23 00:58:37 +02:00
if ( ! isnan ( remaining ) )
{
2021-07-14 21:14:03 +02:00
sensorWater . setProperty ( " remaining " ) . send ( String ( remaining ) ) ;
}
2021-10-22 19:39:42 +02:00
if ( ! isnan ( waterRawSensor . getAverage ( ) ) )
2021-08-23 00:58:37 +02:00
{
2021-10-22 19:39:42 +02:00
sensorWater . setProperty ( " distance " ) . send ( String ( waterRawSensor . getAverage ( ) ) ) ;
2021-07-14 21:14:03 +02:00
}
2021-05-24 15:50:04 +02:00
sensorLipo . setProperty ( " percent " ) . send ( String ( 100 * batteryVoltage / VOLT_MAX_BATT ) ) ;
sensorLipo . setProperty ( " volt " ) . send ( String ( batteryVoltage ) ) ;
sensorLipo . setProperty ( " current " ) . send ( String ( battery . getCurrent ( ) ) ) ;
sensorLipo . setProperty ( " Ah " ) . send ( String ( battery . getAh ( ) ) ) ;
sensorLipo . setProperty ( " ICA " ) . send ( String ( battery . getICA ( ) ) ) ;
sensorLipo . setProperty ( " DCA " ) . send ( String ( battery . getDCA ( ) ) ) ;
sensorLipo . setProperty ( " CCA " ) . send ( String ( battery . getCCA ( ) ) ) ;
sensorSolar . setProperty ( " volt " ) . send ( String ( mSolarVoltage ) ) ;
sensorTemp . setProperty ( TEMPERATUR_SENSOR_CHIP ) . send ( String ( chipTemp ) ) ;
2021-05-26 21:46:33 +02:00
}
else
{
2021-05-24 15:50:04 +02:00
Serial . println ( " Skipping MQTT, offline mode " ) ;
Serial . flush ( ) ;
}
2021-04-07 21:54:53 +02:00
2023-03-03 19:58:55 +01:00
bool isLowLight = mSolarVoltage < = 9 ;
2021-12-05 02:36:03 +01:00
# if defined(TIMED_LIGHT_PIN)
2023-03-03 19:58:55 +01:00
2021-12-05 02:36:03 +01:00
bool shouldLight = determineTimedLightState ( isLowLight ) ;
if ( shouldLight ) {
ulp_pwm_set_level ( timedLightPowerLevel . get ( ) ) ;
} else {
ulp_pwm_set_level ( 0 ) ;
}
2022-04-29 10:47:54 +02:00
2021-12-05 02:36:03 +01:00
# endif // TIMED_LIGHT_PIN
2022-12-20 00:04:57 +01:00
bool isLiquid = waterTemp > 5 ;
2022-04-29 10:47:54 +02:00
bool hasWater = true ; // FIXME remaining > waterLevelMin.get();
// FIXME no water warning message
2021-10-01 23:46:37 +02:00
pumpToRun = determineNextPump ( isLowLight ) ;
2022-04-29 10:47:54 +02:00
// early aborts
2021-10-01 23:46:37 +02:00
if ( pumpToRun ! = - 1 )
2021-04-07 21:54:53 +02:00
{
2022-12-20 00:04:57 +01:00
if ( isLiquid ) {
if ( hasWater )
2021-10-01 23:46:37 +02:00
{
2022-12-20 00:04:57 +01:00
if ( mDownloadMode )
{
log ( LOG_LEVEL_INFO , LOG_PUMP_AND_DOWNLOADMODE , LOG_PUMP_AND_DOWNLOADMODE_CODE ) ;
pumpToRun = - 1 ;
}
}
else
2021-10-01 23:46:37 +02:00
{
2022-12-20 00:04:57 +01:00
log ( LOG_LEVEL_ERROR , LOG_PUMP_BUTNOTANK_MESSAGE , LOG_PUMP_BUTNOTANK_CODE ) ;
2021-10-01 23:46:37 +02:00
pumpToRun = - 1 ;
}
2021-04-07 21:54:53 +02:00
}
2022-12-20 00:04:57 +01:00
else {
2023-02-10 22:23:04 +01:00
log ( LOG_LEVEL_ERROR , LOG_VERY_COLD_WATER , LOG_VERY_COLD_WATER_CODE ) ;
2022-12-20 00:04:57 +01:00
pumpToRun = - 1 ;
2021-04-07 21:54:53 +02:00
}
}
2021-10-01 23:46:37 +02:00
// go directly to sleep, skipping the pump loop
if ( pumpToRun = = - 1 )
2021-04-07 21:54:53 +02:00
{
2021-10-01 23:46:37 +02:00
espDeepSleep ( ) ;
2021-04-07 21:54:53 +02:00
}
}
2020-11-29 05:04:46 +01:00
/** @}*/
2021-07-01 21:19:51 +02:00
2021-10-01 22:53:40 +02:00
# ifdef TIMED_LIGHT_PIN
2021-10-01 23:46:37 +02:00
bool determineTimedLightState ( bool lowLight )
{
2021-08-29 20:45:50 +02:00
bool onlyAllowedWhenDark = timedLightOnlyWhenDark . get ( ) ;
long hoursStart = timedLightStart . get ( ) ;
long hoursEnd = timedLightEnd . get ( ) ;
2022-04-29 10:47:54 +02:00
// ntp missing
2021-10-01 23:46:37 +02:00
if ( getCurrentTime ( ) < 10000 )
{
2021-08-29 20:45:50 +02:00
timedLightNode . setProperty ( " state " ) . send ( String ( " Off, missing ntp " ) ) ;
return false ;
}
2021-10-01 23:46:37 +02:00
if ( onlyAllowedWhenDark & & ! lowLight )
{
2021-08-29 20:45:50 +02:00
timedLightNode . setProperty ( " state " ) . send ( String ( " Off, not dark " ) ) ;
2021-09-14 18:54:14 +02:00
timedLightLowVoltageTriggered = false ;
2021-08-29 20:45:50 +02:00
return false ;
}
2021-12-05 02:36:03 +01:00
int curHour = getCurrentHour ( ) ;
2022-05-20 01:00:04 +02:00
2021-12-05 02:36:03 +01:00
bool condition1 = ( ( hoursStart > hoursEnd ) & &
( curHour > = hoursStart | | curHour < = hoursEnd ) ) ;
bool condition2 = /* Handle e.g. start = 8, end = 21 */
2022-05-20 01:00:04 +02:00
2021-10-01 23:46:37 +02:00
( ( hoursStart < hoursEnd ) & &
2021-12-05 02:36:03 +01:00
( curHour > = hoursStart & & curHour < = hoursEnd ) ) ;
timedLightNode . setProperty ( " debug " ) . send ( String ( curHour ) + " " + String ( hoursStart ) + " " + String ( hoursEnd ) + " " + String ( condition1 ) + " " + String ( condition2 ) ) ;
if ( condition1 | | condition2 )
2021-10-01 23:46:37 +02:00
{
2021-12-02 16:50:24 +01:00
bool voltageOk = ! timedLightLowVoltageTriggered & & battery . getVoltage ( BATTSENSOR_INDEX_BATTERY ) > = timedLightVoltageCutoff . get ( ) ;
if ( voltageOk | | equalish ( timedLightVoltageCutoff . get ( ) , - 1 ) )
2021-10-01 23:46:37 +02:00
{
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 ;
}
2021-08-29 20:45:50 +02:00
}
2021-10-01 22:53:40 +02:00
# endif