Merged documents into ultrasonic sensor stuff

This commit is contained in:
Ollo
2020-11-11 21:42:29 +01:00
21 changed files with 6752 additions and 1363 deletions

1
esp32/.gitignore vendored
View File

@@ -3,3 +3,4 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
doc/

2522
esp32/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,23 @@ Uses ESP32MiniKit
* Temperature
* Custom GPIO
## Documentation of Power-Modes
https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/#esp32-deep-sleep
gpio 17 only out no hold
gpio 16 only out no hold
## Additional hardware
solar charger 2A?
https://www.aliexpress.com/item/4000238259949.html?spm=a2g0o.productlist.0.0.7e50231cCWGu0Z&algo_pvid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56&algo_expid=9ab7b0d3-5026-438b-972b-1d4a81d4dc56-11&btsid=0b0a0ac215999246489888249e72a9&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
MT3608 boost für pumpe
https://www.aliexpress.com/item/32925951391.html?spm=a2g0o.productlist.0.0.39e21087nAzH9q&algo_pvid=7db0a849-62f7-4403-88e3-615ee4d99339&algo_expid=7db0a849-62f7-4403-88e3-615ee4d99339-0&btsid=0b0a0ac215999252934777876e7253&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_
DS18B20 one wire temp sensor
# Features
## Empires Wunschliste
* Pflanze

View File

@@ -12,7 +12,6 @@ For further details have a look at the Readme.md one level above.
# Remote Upload
This script will allow you to send an OTA update to your device.
## Installation
@@ -22,6 +21,12 @@ Requirements are:
## Usage
See ***upload-via-mqtt.sh***
# Remote Upload - Backend
## Usage
```text
usage: ota_updater.py [-h] -l BROKER_HOST -p BROKER_PORT [-u BROKER_USERNAME]
[-d BROKER_PASSWORD] [-t BASE_TOPIC] -i DEVICE_ID

27
esp32/host/upload-via-mqtt.sh Executable file
View File

@@ -0,0 +1,27 @@
#!//bin/bash
if [ $# -ne 3 ]; then
echo "Homie prefex and device index must be specified:"
echo "$0 <mqtt host> <prefix> <device index>"
echo "e.g."
echo "$0 192.168.0.2 test/ MyDeviceId"
exit 1
fi
mqttHost=$1
mqttPrefix=$2
homieId=$3
firmwareFile=../.pio/build/esp32doit-devkit-v1/firmware.bin
if [ ! -f $firmwareFile ]; then
echo "the script $0 must be started in host/ sub directory"
exit 2
fi
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/stay/alive/set" -m "1" -r
echo "Waiting ..."
mosquitto_sub -h $mqttHost -t "${mqttPrefix}${homieId}/#" -R -C 1
python ota_updater.py -l $mqttHost -t "$mqttPrefix" -i "$homieId" $firmwareFile
mosquitto_pub -h $mqttHost -t "${mqttPrefix}${homieId}/stay/alive/set" -m "0" -r
exit 0

View File

@@ -6,59 +6,102 @@
* @date 2020-05-30
*
* @copyright Copyright (c) 2020
* Describe the used PINs of the controller
*
* \mainpage Configuration of the controller
* @{
* Describe the used PINs of the controller
*
* @subpage Controller
*
* @subpage Homie
*
* @subpage Configuration
*
* There are several modes in the controller
* \dot
* digraph Operationmode {
* ranksep=.75;
* poweroff [ label="off" ];
* mode1 [ label="Mode 1 - Sensor only", shape=box, width=2 ];
* mode2 [ label="Mode 2 - Wifi enabled", shape=box ];
* mode3 [ label="Mode 3 - Stay alive", shape=box ];
* mode1 -> mode2 [ label="wakeup reason", fontsize=10 ];
* mode1 -> mode2 [ label="Time duration", fontsize=10 ];
* mode2 -> mode3 [ label="Over the Air Update", fontsize=10 ];
* mode3 -> mode2 [ label="Over the Air Finished", fontsize=10 ];
* mode3 -> mode2 [ label="Mqtt Command", fontsize=10 ];
* mode2 -> mode3 [ label="Mqtt Command", fontsize=10 ];
* poweroff -> mode1 [ label="deep sleep wakeup", fontsize=10 ];
* mode1 -> poweroff [ label="enter deep sleep", fontsize=10 ];
* mode2 -> poweroff [ label="Mqtt queue empty", fontsize=10 ];
* }
* \enddot
*
* Before entering Deep sleep the controller is configured with an wakeup time.
*
* @}
*/
#ifndef CONTROLLER_CONFIG_H
#define CONTROLLER_CONFIG_H
/** \addtogroup Configuration
* @{
*/
#define FIRMWARE_VERSION "1.0.9"
#define FIRMWARE_VERSION "1.0.6"
#define ADC_TO_VOLT(adc) ((adc) * 3.3 ) / 4095)
#define ADC_TO_VOLT_WITH_MULTI(adc, multi) (((adc)*3.3 * (multi)) / 4095)
#define MOIST_SENSOR_MAX_ADC (85 * 4095 / 100)
#define MOIST_SENSOR_MIN_ADC (25 * 4095 / 100)
#define ADC_TO_VOLT(adc) ((adc) * 3.3 ) / 4095)
#define ADC_TO_VOLT_WITH_MULTI(adc, multi) (((adc) * 3.3 * (multi)) / 4095)
#define SOLAR_VOLT(adc) ADC_TO_VOLT_WITH_MULTI(adc, 4.0306) /**< 100k and 33k voltage dividor */
#define ADC_5V_TO_3V3(adc) ADC_TO_VOLT_WITH_MULTI(adc, 1.69) /**< 33k and 47k8 voltage dividor */
#define MS_TO_S 1000
#define SOLAR_VOLT(adc) ADC_TO_VOLT_WITH_MULTI(adc, 4.0306) /**< 100k and 33k voltage dividor */
#define ADC_5V_TO_3V3(adc) ADC_TO_VOLT_WITH_MULTI(adc, 1.7) /**< 33k and 47k8 voltage dividor */
#define MS_TO_S 1000
#define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */
#define SENSOR_SOLAR 35 /**< GPIO 35 (ADC1) */
#define SENSOR_PLANT0 32 /**< GPIO 32 (ADC1) */
#define SENSOR_PLANT1 33 /**< GPIO 33 (ADC1) */
#define SENSOR_PLANT2 25 /**< GPIO 25 (ADC2) */
#define SENSOR_PLANT3 26 /**< GPIO 26 (ADC2) */
#define SENSOR_PLANT4 27 /**< GPIO 27 (ADC2) */
#define SENSOR_PLANT5 14 /**< GPIO 14 (ADC2) */
#define SENSOR_PLANT6 12 /**< GPIO 12 (ADC2) */
#define SENSOR_LIPO 34 /**< GPIO 34 (ADC1) */
#define SENSOR_SOLAR 35 /**< GPIO 35 (ADC1) */
#define SENSOR_PLANT0 32 /**< GPIO 32 (ADC1) */
#define SENSOR_PLANT1 33 /**< GPIO 33 (ADC1) */
#define SENSOR_PLANT2 25 /**< GPIO 25 (ADC2) */
#define SENSOR_PLANT3 26 /**< GPIO 26 (ADC2) */
#define SENSOR_PLANT4 27 /**< GPIO 27 (ADC2) */
#define SENSOR_PLANT5 14 /**< GPIO 14 (ADC2) */
#define SENSOR_PLANT6 12 /**< GPIO 12 (ADC2) */
#define OUTPUT_PUMP0 23 /**< GPIO 23 */
#define OUTPUT_PUMP1 22 /**< GPIO 22 */
#define OUTPUT_PUMP2 21 /**< GPIO 21 */
#define OUTPUT_PUMP3 19 /**< GPIO 19 */
#define OUTPUT_PUMP4 18 /**< GPIO 18 */
#define OUTPUT_PUMP5 5 /**< GPIO 5 */
#define OUTPUT_PUMP6 15 /**< GPIO 15 */
#define OUTPUT_PUMP0 23 /**< GPIO 23 */
#define OUTPUT_PUMP1 22 /**< GPIO 22 */
#define OUTPUT_PUMP2 21 /**< GPIO 21 */
#define OUTPUT_PUMP3 19 /**< GPIO 19 */
#define OUTPUT_PUMP4 18 /**< GPIO 18 */
#define OUTPUT_PUMP5 5 /**< GPIO 5 */
#define OUTPUT_PUMP6 15 /**< GPIO 15 */
#define OUTPUT_SENSOR 16 /**< GPIO 16 - Enable Sensors */
#define OUTPUT_PUMP 13 /**< GPIO 13 - Enable Pumps */
#define OUTPUT_SENSOR 16 /**< GPIO 16 - Enable Sensors */
#define OUTPUT_PUMP 13 /**< GPIO 13 - Enable Pumps */
#define SENSOR_DS18B20 2 /**< GPIO 2 */
#define BUTTON 0 /**< GPIO 0 */
#define SENSOR_DS18B20 2 /**< GPIO 2 - Temperatur sensor */
#define BUTTON 0 /**< GPIO 0 - Fix button of NodeMCU */
#define MIN_TIME_RUNNING 5UL /**< Amount of seconds the controller must stay awoken */
#define MAX_PLANTS 7
#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 */
#define SOLAR_CHARGE_MIN_VOLTAGE 7
#define SOLAR_CHARGE_MAX_VOLTAGE 9
#define MIN_TIME_RUNNING 5UL /**< Amount of seconds the controller must stay awoken */
#define MAX_PLANTS 7
#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 */
#define SOLAR_CHARGE_MIN_VOLTAGE 7 /**< Sun is rising (morning detected) */
#define SOLAR_CHARGE_MAX_VOLTAGE 9 /**< Sun is shining (noon) */
#define HC_SR04 /**< Ultrasonic distance sensor to measure water level */
#define SENSOR_SR04_ECHO 17 /**< GPIO 17 - Echo */
#define SENSOR_SR04_TRIG 23 /**< GPIO 23 - Trigger */
#define HC_SR04 /**< Ultrasonic distance sensor to measure water level */
#define SENSOR_SR04_ECHO 17 /**< GPIO 17 - Echo */
#define SENSOR_SR04_TRIG 23 /**< GPIO 23 - Trigger */
#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)
#define PANIK_MODE_DEEPSLEEP (60 * 60 * 5U) /**< 5 hours in usecond */
#define PANIK_MODE_DEEPSLEEP_US (PANIK_MODE_DEEPSLEEP * 1000 * 1000)
#endif
#define TEMPERATURE_DELTA_TRIGGER_IN_C 1.0f
#define MOIST_DELTA_TRIGGER_ADC 10
#define SOLAR_DELTA_VOLT_ADC 3
#define LIPO_DELTA_VOLT_ADC 0.2 /**< trigger for lipo voltage */
/* @} */
#endif

View File

@@ -21,32 +21,36 @@
#include <OneWire.h>
class Ds18B20 {
private:
OneWire* mDs;
int foundDevices;
public:
Ds18B20(int pin) {
this->mDs = new OneWire(pin);
}
class Ds18B20
{
private:
OneWire *mDs;
int foundDevices;
~Ds18B20() {
delete this->mDs;
}
/**
public:
Ds18B20(int pin)
{
this->mDs = new OneWire(pin);
}
~Ds18B20()
{
delete this->mDs;
}
/**
* @brief read amount sensots
* check for available of DS18B20 sensors
* @return amount of sensors
*/
int readDevices(void);
int readDevices(void);
/**
/**
* @brief Read all temperatures in celsius
*
* @param pTemperatures array of float valuies
* @param maxTemperatures size of the given array
* @return int amount of read temperature values
*/
int readAllTemperatures(float* pTemperatures, int maxTemperatures);
int readAllTemperatures(float *pTemperatures, int maxTemperatures);
};
#endif

View File

@@ -1,4 +1,6 @@
/**
/** \addtogroup Homie
* @{
*
* @file HomieConfiguration.h
* @author your name (you@domain.com)
* @brief
@@ -7,6 +9,7 @@
*
* @copyright Copyright (c) 2020
* All Settings, configurable in Homie
*
*/
#ifndef HOMIE_PLANT_CONFIG_H
#define HOMIE_PLANT_CONFIG_H
@@ -16,54 +19,75 @@
#define MAX_PLANTS 7
/**
*********************************** Attributes *******************************
*/
* @name Attributes
* generated Information
* @{
**/
HomieNode plant0("plant0", "Plant 0", "Plant");
HomieNode plant1("plant1", "Plant 1", "Plant");
HomieNode plant2("plant2", "Plant 2", "Plant");
HomieNode plant3("plant3", "Plant 3", "Plant");
HomieNode plant4("plant4", "Plant 4", "Plant");
HomieNode plant5("plant5", "Plant 5", "Plant");
HomieNode plant6("plant6", "Plant 6", "Plant");
HomieNode plant0("plant0", "Plant 0", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant1("plant1", "Plant 1", "Plant"); /**< dynamic Homie information for second plant */
HomieNode plant2("plant2", "Plant 2", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant3("plant3", "Plant 3", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant4("plant4", "Plant 4", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant5("plant5", "Plant 5", "Plant"); /**< dynamic Homie information for first plant */
HomieNode plant6("plant6", "Plant 6", "Plant"); /**< dynamic Homie information for first plant */
HomieNode sensorLipo("lipo", "Battery Status", "Lipo");
HomieNode sensorSolar("solar", "Solar Status", "Solarpanel");
HomieNode sensorWater("water", "WaterSensor", "Water");
HomieNode sensorTemp("temperature", "Temperature", "temperature");
HomieNode stayAlive("stay", "alive", "alive");
HomieNode stayAlive("stay", "alive", "alive"); /**< Necessary for Mqtt Active Command */
/* @} */
/**
*********************************** Settings *******************************
* @name Settings
* General settings for the controller
* @{
*/
HomieSetting<long> deepSleepTime("deepsleep", "time in milliseconds to sleep (0 deactivats it)");
HomieSetting<long> deepSleepNightTime("nightsleep", "time in milliseconds to sleep (0 uses same setting: deepsleep at night, too)");
HomieSetting<long> maxTimeBetweenMQTTUpdates("mqttSleep", "time in seconds to start into mode2");
HomieSetting<long> deepSleepTime("deepsleep", "time in seconds to sleep (0 deactivats it)");
HomieSetting<long> deepSleepNightTime("nightsleep", "time in seconds to sleep (0 uses same setting: deepsleep at night, too)");
HomieSetting<long> wateringDeepSleep("pumpdeepsleep", "time seconds to sleep, while a pump is running");
HomieSetting<long> waterLevelMax("watermaxlevel", "distance (mm) at maximum water level");
HomieSetting<long> waterLevelMin("waterminlevel", "distance (mm) at minimum water level (pumps still covered)");
HomieSetting<long> waterLevelWarn("waterlevelwarn", "warn (mm) if below this water level %");
HomieSetting<long> waterLevelVol("waterVolume", "(ml) between minimum and maximum");
HomieSetting<const char *> ntpServer("ntpServer", "NTP server (pool.ntp.org as default)");
/** Plant specific ones */
/**
*@}
*/
#define GENERATE_PLANT(plant, strplant) \
HomieSetting<long> mSensorDry##plant = HomieSetting<long>("moistdry" strplant, "Plant " strplant "- Moist sensor dry threshold"); \
HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("rangehourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \
HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("rangehourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \
/**
* @name Plant specific ones
* Setting for one plant
* @{
**/
#define GENERATE_PLANT(plant, strplant) \
HomieSetting<long> mSensorDry##plant = HomieSetting<long>("moistdry" strplant, "Plant " strplant "- Moist sensor dry threshold"); \
HomieSetting<long> mPumpAllowedHourRangeStart##plant = HomieSetting<long>("rangehourstart" strplant, "Plant" strplant " - Range pump allowed hour start (0-23)"); \
HomieSetting<long> mPumpAllowedHourRangeEnd##plant = HomieSetting<long>("rangehourend" strplant, "Plant" strplant " - Range pump allowed hour end (0-23)"); \
HomieSetting<bool> mPumpOnlyWhenLowLight##plant = HomieSetting<bool>("onlyWhenLowLightZ" strplant, "Plant" strplant " - Enable the Pump only, when there is light but not enought to charge battery"); \
HomieSetting<long> mPumpCooldownInHours##plant = HomieSetting<long>("cooldownpump" 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 };
GENERATE_PLANT(0, "0");
GENERATE_PLANT(1, "1");
GENERATE_PLANT(2, "2");
GENERATE_PLANT(3, "3");
GENERATE_PLANT(4, "4");
GENERATE_PLANT(5, "5");
GENERATE_PLANT(6, "6");
HomieSetting<long> mPumpCooldownInHours##plant = HomieSetting<long>("cooldownpump" 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}; \
/**< Generate all settings for one plant
*
* Feature to start pumping only at morning: @link{SOLAR_CHARGE_MIN_VOLTAGE} and @link{SOLAR_CHARGE_MAX_VOLTAGE}
*/
/**
* @}
*/
GENERATE_PLANT(0, "0"); /**< Homie settings for first plant */
GENERATE_PLANT(1, "1"); /**< Homie settings for second Plant */
GENERATE_PLANT(2, "2"); /**< Homie settings for third plant */
GENERATE_PLANT(3, "3"); /**< Homie settings for fourth plant */
GENERATE_PLANT(4, "4"); /**< Homie settings for fifth plant */
GENERATE_PLANT(5, "5"); /**< Homie settings for sixth plant */
GENERATE_PLANT(6, "6"); /**< Homie settings for seventh plant */
#endif /* HOMIE_PLANT_CONFIG_H */
#endif /* HOMIE_PLANT_CONFIG_H @} */

View File

@@ -13,14 +13,16 @@
#include <Homie.h>
#define DEACTIVATED_PLANT 5000
#define DEACTIVATED_PLANT 5000
#define MISSING_SENSOR 5001
typedef struct PlantSettings_t {
HomieSetting<long>* pSensorDry;
HomieSetting<long>* pPumpAllowedHourRangeStart;
HomieSetting<long>* pPumpAllowedHourRangeEnd;
HomieSetting<bool>* pPumpOnlyWhenLowLight;
HomieSetting<long>* pPumpCooldownInHours;
typedef struct PlantSettings_t
{
HomieSetting<long> *pSensorDry;
HomieSetting<long> *pPumpAllowedHourRangeStart;
HomieSetting<long> *pPumpAllowedHourRangeEnd;
HomieSetting<bool> *pPumpOnlyWhenLowLight;
HomieSetting<long> *pPumpCooldownInHours;
} PlantSettings_t;
#endif

View File

@@ -15,17 +15,18 @@
#include "HomieTypes.h"
#include "RunningMedian.h"
class Plant {
class Plant
{
private:
RunningMedian moistureRaw = RunningMedian(5);
HomieNode* mPlant = NULL;
int mPinSensor=0; /**< Pin of the moist sensor */
int mPinPump=0; /**< Pin of the pump */
PlantSettings_t* mSetting;
HomieNode *mPlant = NULL;
int mPinSensor = 0; /**< Pin of the moist sensor */
int mPinPump = 0; /**< Pin of the pump */
bool mConnected = false;
public:
PlantSettings_t *mSetting;
/**
* @brief Construct a new Plant object
*
@@ -33,9 +34,9 @@ public:
* @param pinPump Pin of the Pump to use
*/
Plant(int pinSensor, int pinPump,
int plantId,
HomieNode* plant,
PlantSettings_t* setting);
int plantId,
HomieNode *plant,
PlantSettings_t *setting);
void postMQTTconnection(void);
@@ -46,8 +47,6 @@ public:
*
*/
void addSenseValue(void);
int getSensorValue() { return moistureRaw.getMedian(); }
void deactivatePump(void);
@@ -59,16 +58,42 @@ public:
* @return true
* @return false
*/
bool isPumpRequired() {
return (this->mSetting->pSensorDry != NULL)
&& (this->moistureRaw.getMedian() > this->mSetting->pSensorDry->get())
&& (this->mSetting->pSensorDry->get() != DEACTIVATED_PLANT);
bool isPumpRequired()
{
bool isDry = getCurrentMoisture() > getSettingsMoisture();
bool isActive = isPumpTriggerActive();
return isDry && isActive;
}
HomieInternals::SendingPromise& setProperty(const String& property) const {
bool isPumpTriggerActive()
{
return this->mSetting->pSensorDry->get() != DEACTIVATED_PLANT;
}
float getCurrentMoisture()
{
if(moistureRaw.getCount()==0){
return MISSING_SENSOR;
}
return this->moistureRaw.getMedian();
}
long getSettingsMoisture()
{
if (this->mSetting->pSensorDry != NULL)
{
return this->mSetting->pSensorDry->get();
}
else
{
return DEACTIVATED_PLANT;
}
}
HomieInternals::SendingPromise &setProperty(const String &property) const
{
return mPlant->setProperty(property);
}
bool switchHandler(const HomieRange& range, const String& value);
bool switchHandler(const HomieRange &range, const String &value);
void init(void);
@@ -76,16 +101,23 @@ public:
* @brief determine, if the plant was recently casted
* @param sinceLastActivation timestamp of last time
*/
bool isInCooldown(long sinceLastActivation) {
bool isInCooldown(long sinceLastActivation)
{
/* if the time difference is greater than one month, we know these are initial values */
if (sinceLastActivation > (60 * 60 * 24 * 30)) {
if (sinceLastActivation > (60 * 60 * 24 * 30))
{
return false;
}
return (this->mSetting->pPumpCooldownInHours->get() > sinceLastActivation / 3600);
return (getCooldownInSeconds() > sinceLastActivation);
}
bool isAllowedOnlyAtLowLight(void) {
long getCooldownInSeconds(){
return this->mSetting->pPumpCooldownInHours->get()*60*60;
}
bool isAllowedOnlyAtLowLight(void)
{
return this->mSetting->pPumpOnlyWhenLowLight->get();
}
};

View File

@@ -17,13 +17,11 @@
// not tested ==> use at own risk :)
// #define RUNNING_MEDIAN_USE_MALLOC
// should at least be 5 to be practical,
// odd sizes results in a 'real' middle element and will be a bit faster.
// even sizes takes the average of the two middle elements as median
#define MEDIAN_MIN_SIZE 5
#define MEDIAN_MAX_SIZE 19
#define MEDIAN_MIN_SIZE 5
#define MEDIAN_MAX_SIZE 19
class RunningMedian
{
@@ -45,7 +43,7 @@ public:
float getAverage(uint8_t nMedian);
float getHighest() { return getSortedElement(_cnt - 1); };
float getLowest() { return getSortedElement(0); };
float getLowest() { return getSortedElement(0); };
// get n'th element from the values in time order
float getElement(const uint8_t n);
@@ -58,7 +56,6 @@ public:
// returns current used elements, getCount() <= getSize()
uint8_t getCount() { return _cnt; };
protected:
boolean _sorted;
uint8_t _size;
@@ -66,8 +63,8 @@ protected:
uint8_t _idx;
#ifdef RUNNING_MEDIAN_USE_MALLOC
float * _ar;
uint8_t * _p;
float *_ar;
uint8_t *_p;
#else
float _ar[MEDIAN_MAX_SIZE];
uint8_t _p[MEDIAN_MAX_SIZE];

View File

@@ -1,202 +0,0 @@
/**
arduino-timer - library for delaying function calls
Copyright (c) 2018, Michael Contreras
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _CM_ARDUINO_TIMER_H__
#define _CM_ARDUINO_TIMER_H__
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#ifndef TIMER_MAX_TASKS
#define TIMER_MAX_TASKS 0x10
#endif
template <
size_t max_tasks = TIMER_MAX_TASKS, /* max allocated tasks */
unsigned long (*time_func)() = millis, /* time function for timer */
typename T = void * /* handler argument type */
>
class Timer {
public:
typedef uintptr_t Task; /* public task handle */
typedef bool (*handler_t)(T opaque); /* task handler func signature */
/* Calls handler with opaque as argument in delay units of time */
Task
in(unsigned long delay, handler_t h, T opaque = T())
{
return task_id(add_task(time_func(), delay, h, opaque));
}
/* Calls handler with opaque as argument at time */
Task
at(unsigned long time, handler_t h, T opaque = T())
{
const unsigned long now = time_func();
return task_id(add_task(now, time - now, h, opaque));
}
/* Calls handler with opaque as argument every interval units of time */
Task
every(unsigned long interval, handler_t h, T opaque = T())
{
return task_id(add_task(time_func(), interval, h, opaque, interval));
}
/* Cancel the timer task */
void
cancel(Task &task)
{
if (!task) return;
for (size_t i = 0; i < max_tasks; ++i) {
struct task * const t = &tasks[i];
if (t->handler && (t->id ^ task) == (uintptr_t)t) {
remove(t);
break;
}
}
task = (Task)NULL;
}
/* Ticks the timer forward - call this function in loop() */
unsigned long
tick()
{
unsigned long ticks = (unsigned long)-1;
for (size_t i = 0; i < max_tasks; ++i) {
struct task * const task = &tasks[i];
if (task->handler) {
const unsigned long t = time_func();
const unsigned long duration = t - task->start;
if (duration >= task->expires) {
task->repeat = task->handler(task->opaque) && task->repeat;
if (task->repeat) task->start = t;
else remove(task);
} else {
const unsigned long remaining = task->expires - duration;
ticks = remaining < ticks ? remaining : ticks;
}
}
}
return ticks == (unsigned long)-1 ? 0 : ticks;
}
private:
size_t ctr;
struct task {
handler_t handler; /* task handler callback func */
T opaque; /* argument given to the callback handler */
unsigned long start,
expires; /* when the task expires */
size_t repeat, /* repeat task */
id;
} tasks[max_tasks];
inline
void
remove(struct task *task)
{
task->handler = NULL;
task->opaque = T();
task->start = 0;
task->expires = 0;
task->repeat = 0;
task->id = 0;
}
inline
Task
task_id(const struct task * const t)
{
const Task id = (Task)t;
return id ? id ^ t->id : id;
}
inline
struct task *
next_task_slot()
{
for (size_t i = 0; i < max_tasks; ++i) {
struct task * const slot = &tasks[i];
if (slot->handler == NULL) return slot;
}
return NULL;
}
inline
struct task *
add_task(unsigned long start, unsigned long expires,
handler_t h, T opaque, bool repeat = 0)
{
struct task * const slot = next_task_slot();
if (!slot) return NULL;
if (++ctr == 0) ++ctr; // overflow
slot->id = ctr;
slot->handler = h;
slot->opaque = opaque;
slot->start = start;
slot->expires = expires;
slot->repeat = repeat;
return slot;
}
};
/* create a timer with the default settings */
inline Timer<>
timer_create_default()
{
return Timer<>();
}
#endif

View File

@@ -15,6 +15,8 @@ framework = arduino
build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
board_build.partitions = defaultWithSmallerSpiffs.csv
upload_port=/dev/ttyUSB0
; the latest development brankitchen-lightch (convention V3.0.x)
lib_deps = ArduinoJson@6.16.1
https://github.com/homieiot/homie-esp8266.git#v3.0

View File

@@ -11,46 +11,45 @@
#include "DS18B20.h"
#define STARTCONV 0x44
#define READSCRATCH 0xBE // Read EEPROM
#define TEMP_LSB 0
#define TEMP_MSB 1
#define SCRATCHPADSIZE 9
#define OFFSET_CRC8 8 /**< 9th byte has the CRC of the complete data */
#define STARTCONV 0x44
#define READSCRATCH 0xBE // Read EEPROM
#define TEMP_LSB 0
#define TEMP_MSB 1
#define SCRATCHPADSIZE 9
#define OFFSET_CRC8 8 /**< 9th byte has the CRC of the complete data */
//Printf debugging
//#define DS_DEBUG
int Ds18B20::readDevices() {
int Ds18B20::readDevices()
{
byte addr[8];
int amount = -1;
while (this->mDs->search(addr)) {
while (this->mDs->search(addr))
{
amount++;
}
this->mDs->reset_search();
return amount;
}
int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) {
int Ds18B20::readAllTemperatures(float *pTemperatures, int maxTemperatures)
{
byte addr[8];
uint8_t scratchPad[SCRATCHPADSIZE];
int currentTemp = 0;
#ifdef DS_DEBUG
int i;
#endif
while (this->mDs->search(addr)) {
#ifdef DS_DEBUG
Serial.print(" ROM =");
for (i = 0; i < 8; i++) {
Serial.write(' ');
Serial.print(addr[i], HEX);
}
#endif
while (this->mDs->search(addr))
{
this->mDs->reset();
this->mDs->select(addr);
this->mDs->write(STARTCONV);
}
delay(750);
while (this->mDs->search(addr))
{
this->mDs->reset();
this->mDs->select(addr);
this->mDs->write(READSCRATCH);
@@ -68,36 +67,32 @@ int Ds18B20::readAllTemperatures(float* pTemperatures, int maxTemperatures) {
// byte 7: DS18S20: COUNT_PER_C
// DS18B20 & DS1822: store for crc
// byte 8: SCRATCHPAD_CRC
#ifdef DS_DEBUG
Serial.write("\r\nDATA:");
for (uint8_t i = 0; i < 9; i++) {
Serial.print(scratchPad[i], HEX);
}
#else
delay(50);
#endif
for (uint8_t i = 0; i < 9; i++) {
for (uint8_t i = 0; i < 9; i++)
{
scratchPad[i] = this->mDs->read();
}
uint8_t crc8 = this->mDs->crc8(scratchPad, 8);
/* Only work an valid data */
if (crc8 == scratchPad[OFFSET_CRC8]) {
int16_t fpTemperature = (((int16_t) scratchPad[TEMP_MSB]) << 11)
| (((int16_t) scratchPad[TEMP_LSB]) << 3);
float celsius = (float) fpTemperature * 0.0078125;
if (crc8 == scratchPad[OFFSET_CRC8])
{
int16_t fpTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 11) | (((int16_t)scratchPad[TEMP_LSB]) << 3);
float celsius = (float)fpTemperature * 0.0078125;
#ifdef DS_DEBUG
Serial.printf("\r\nTemp%d %f °C (Raw: %d, %x =? %x)\r\n", (currentTemp+1), celsius, fpTemperature, crc8, scratchPad[8]);
Serial.printf("\r\nTemp%d %f °C (Raw: %d, %x =? %x)\r\n", (currentTemp + 1), celsius, fpTemperature, crc8, scratchPad[8]);
#endif
/* check, if the buffer as some space for our data */
if (currentTemp < maxTemperatures) {
if (currentTemp < maxTemperatures)
{
pTemperatures[currentTemp] = celsius;
} else {
}
else
{
return -1;
}
}
currentTemp++;
}
}
this->mDs->reset();
#ifdef DS_DEBUG
Serial.println(" No more addresses.");

View File

@@ -11,77 +11,87 @@
*/
#include "PlantCtrl.h"
#include "ControllerConfiguration.h"
Plant::Plant(int pinSensor, int pinPump,int plantId, HomieNode* plant, PlantSettings_t* setting) {
Plant::Plant(int pinSensor, int pinPump, int plantId, HomieNode *plant, PlantSettings_t *setting)
{
this->mPinSensor = pinSensor;
this->mPinPump = pinPump;
this->mPlant = plant;
this->mSetting = setting;
this->mSetting = setting;
}
void Plant::init(void) {
void Plant::init(void)
{
/* Initialize Home Settings validator */
this->mSetting->pSensorDry->setDefaultValue(DEACTIVATED_PLANT);
this->mSetting->pSensorDry->setValidator([] (long candidate) {
return (((candidate >= 0) && (candidate <= 4095) ) || candidate == DEACTIVATED_PLANT);
this->mSetting->pSensorDry->setValidator([](long candidate) {
return (((candidate >= 0) && (candidate <= 4095)) || candidate == DEACTIVATED_PLANT);
});
this->mSetting->pPumpAllowedHourRangeStart->setDefaultValue(8); // start at 8:00
this->mSetting->pPumpAllowedHourRangeStart->setValidator([] (long candidate) {
return ((candidate >= 0) && (candidate <= 23) );
this->mSetting->pPumpAllowedHourRangeStart->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 23));
});
this->mSetting->pPumpAllowedHourRangeEnd->setDefaultValue(20); // stop pumps at 20:00
this->mSetting->pPumpAllowedHourRangeEnd->setValidator([] (long candidate) {
return ((candidate >= 0) && (candidate <= 23) );
this->mSetting->pPumpAllowedHourRangeEnd->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 23));
});
this->mSetting->pPumpOnlyWhenLowLight->setDefaultValue(true);
this->mSetting->pPumpCooldownInHours->setDefaultValue(20); // minutes
this->mSetting->pPumpCooldownInHours->setValidator([] (long candidate) {
return ((candidate >= 0) && (candidate <= 1024) );
this->mSetting->pPumpCooldownInHours->setValidator([](long candidate) {
return ((candidate >= 0) && (candidate <= 1024));
});
/* Initialize Hardware */
pinMode(this->mPinPump, OUTPUT);
pinMode(this->mPinSensor, ANALOG);
digitalWrite(this->mPinPump, LOW);
digitalWrite(this->mPinPump, LOW);
}
void Plant::addSenseValue(void) {
this->moistureRaw.add( analogRead(this->mPinSensor) );
void Plant::addSenseValue(void)
{
int raw = analogRead(this->mPinSensor);
if(raw < MOIST_SENSOR_MAX_ADC && raw > MOIST_SENSOR_MIN_ADC){
this->moistureRaw.add(raw);
}
}
void Plant::postMQTTconnection(void) {
void Plant::postMQTTconnection(void)
{
const String OFF = String("OFF");
this->mConnected=true;
this->mConnected = true;
this->mPlant->setProperty("switch").send(OFF);
}
void Plant::deactivatePump(void) {
void Plant::deactivatePump(void)
{
digitalWrite(this->mPinPump, LOW);
if (this->mConnected) {
if (this->mConnected)
{
const String OFF = String("OFF");
this->mPlant->setProperty("switch").send(OFF);
}
}
void Plant::activatePump(void) {
void Plant::activatePump(void)
{
digitalWrite(this->mPinPump, HIGH);
if (this->mConnected) {
if (this->mConnected)
{
const String OFF = String("ON");
this->mPlant->setProperty("switch").send(OFF);
}
}
void Plant::advertise(void) {
void Plant::advertise(void)
{
// Advertise topics
this->mPlant->advertise("switch").setName("Pump 1")
.setDatatype("boolean");
this->mPlant->advertise("switch").setName("Pump 1").setDatatype("boolean");
//FIXME add .settable(this->switchHandler)
this->mPlant->advertise("moist").setName("Percent")
.setDatatype("number")
.setUnit("%");
this->mPlant->advertise("moist").setName("Percent").setDatatype("number").setUnit("%");
this->mPlant->advertise("moistraw").setName("adc").setDatatype("number").setUnit("3.3/4096V");
}
/* FIXME
bool Plant::switchHandler(const HomieRange& range, const String& value) {
if (range.isRange) return false; // only one switch is present

View File

@@ -31,8 +31,8 @@ RunningMedian::RunningMedian(const uint8_t size)
_size = constrain(size, MEDIAN_MIN_SIZE, MEDIAN_MAX_SIZE);
#ifdef RUNNING_MEDIAN_USE_MALLOC
_ar = (float *) malloc(_size * sizeof(float));
_p = (uint8_t *) malloc(_size * sizeof(uint8_t));
_ar = (float *)malloc(_size * sizeof(float));
_p = (uint8_t *)malloc(_size * sizeof(uint8_t));
#endif
clear();
@@ -63,18 +63,22 @@ void RunningMedian::clear()
void RunningMedian::add(float value)
{
_ar[_idx++] = value;
if (_idx >= _size) _idx = 0; // wrap around
if (_cnt < _size) _cnt++;
if (_idx >= _size)
_idx = 0; // wrap around
if (_cnt < _size)
_cnt++;
_sorted = false;
}
float RunningMedian::getMedian()
{
if (_cnt == 0) return NAN;
if (_cnt == 0)
return NAN;
if (_sorted == false) sort();
if (_sorted == false)
sort();
if (_cnt & 0x01) // is it odd sized?
if (_cnt & 0x01) // is it odd sized?
{
return _ar[_p[_cnt / 2]];
}
@@ -83,7 +87,8 @@ float RunningMedian::getMedian()
float RunningMedian::getAverage()
{
if (_cnt == 0) return NAN;
if (_cnt == 0)
return NAN;
float sum = 0;
for (uint8_t i = 0; i < _cnt; i++)
@@ -95,13 +100,16 @@ float RunningMedian::getAverage()
float RunningMedian::getAverage(uint8_t nMedians)
{
if ((_cnt == 0) || (nMedians == 0)) return NAN;
if ((_cnt == 0) || (nMedians == 0))
return NAN;
if (_cnt < nMedians) nMedians = _cnt; // when filling the array for first time
if (_cnt < nMedians)
nMedians = _cnt; // when filling the array for first time
uint8_t start = ((_cnt - nMedians) / 2);
uint8_t stop = start + nMedians;
if (_sorted == false) sort();
if (_sorted == false)
sort();
float sum = 0;
for (uint8_t i = start; i < stop; i++)
@@ -113,7 +121,8 @@ float RunningMedian::getAverage(uint8_t nMedians)
float RunningMedian::getElement(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt)) return NAN;
if ((_cnt == 0) || (n >= _cnt))
return NAN;
uint8_t pos = _idx + n;
if (pos >= _cnt) // faster than %
@@ -125,18 +134,21 @@ float RunningMedian::getElement(const uint8_t n)
float RunningMedian::getSortedElement(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt)) return NAN;
if ((_cnt == 0) || (n >= _cnt))
return NAN;
if (_sorted == false) sort();
if (_sorted == false)
sort();
return _ar[_p[n]];
}
// n can be max <= half the (filled) size
float RunningMedian::predict(const uint8_t n)
{
if ((_cnt == 0) || (n >= _cnt / 2)) return NAN;
if ((_cnt == 0) || (n >= _cnt / 2))
return NAN;
float med = getMedian(); // takes care of sorting !
float med = getMedian(); // takes care of sorting !
if (_cnt & 0x01)
{
return max(med - _ar[_p[_cnt / 2 - n]], _ar[_p[_cnt / 2 + n]] - med);
@@ -162,7 +174,8 @@ void RunningMedian::sort()
flag = false;
}
}
if (flag) break;
if (flag)
break;
}
_sorted = true;
}

View File

@@ -1,4 +1,6 @@
/**
/** \addtogroup Controller
* @{
*
* @file main.cpp
* @author Ollo
* @brief PlantControl
@@ -6,7 +8,6 @@
* @date 2020-05-01
*
* @copyright Copyright (c) 2020
*
*/
#include "PlantCtrl.h"
#include "ControllerConfiguration.h"
@@ -16,8 +17,8 @@
#include "time.h"
#include "esp_sleep.h"
#include "RunningMedian.h"
#include <arduino-timer.h>
#include <stdint.h>
#include <math.h>
const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minutes */
@@ -26,44 +27,36 @@ const unsigned long TEMPREADCYCLE = 30000; /**< Check temperature all half minut
#define SOLAR4SENSORS 6.0f
#define TEMP_INIT_VALUE -999.0f
#define TEMP_MAX_VALUE 85.0f
#define HalfHour 60
typedef struct
{
long lastActive; /**< Timestamp, a pump was activated */
long moistTrigger; /**< Trigger value of the moist sensor */
long moisture; /**< last measured moist value */
} rtc_plant_t;
/********************* non volatile enable after deepsleep *******************************/
RTC_DATA_ATTR rtc_plant_t rtcPlant[MAX_PLANTS];
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; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive1 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger1 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive2 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger2 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive3 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger3 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive4 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger4 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive5 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger5 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR long rtcLastActive6 = 0;
RTC_DATA_ATTR long rtcMoistureTrigger6 = 0; /**<Level for the moisture sensor */
RTC_DATA_ATTR int lastPumpRunning = 0;
RTC_DATA_ATTR long lastWaterValue = 0;
const char *ntpServer = "pool.ntp.org";
RTC_DATA_ATTR float rtcLastTemp1 = 0.0f;
RTC_DATA_ATTR float rtcLastTemp2 = 0.0f;
RTC_DATA_ATTR int gBootCount = 0;
RTC_DATA_ATTR int gCurrentPlant = 0; /**< Value Range: 1 ... 7 (0: no plant needs water) */
bool warmBoot = true;
bool mode3Active = false; /**< Controller must not sleep */
bool mDeepsleep = false;
bool volatile mode3Active = false; /**< Controller must not sleep */
bool volatile mDeepsleep = false;
int plantSensor1 = 0;
int readCounter = 0;
bool mConfigured = false;
auto wait4sleep = timer_create_default(); // create a timer with default settings
RTC_DATA_ATTR int gBootCount = 0;
RTC_DATA_ATTR int gCurrentPlant = 0; /**< Value Range: 1 ... 7 (0: no plant needs water) */
RunningMedian lipoRawSensor = RunningMedian(5);
RunningMedian solarRawSensor = RunningMedian(5);
RunningMedian waterRawSensor = RunningMedian(5);
@@ -91,10 +84,41 @@ float getSolarVoltage()
return SOLAR_VOLT(solarRawSensor.getAverage());
}
void setMoistureTrigger(int plantId, long value)
{
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcPlant[plantId].moistTrigger = value;
}
}
void setLastMoisture(int plantId, long value)
{
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcPlant[plantId].moisture = value;
}
}
long getLastMoisture(int plantId)
{
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
return rtcPlant[plantId].moisture;
}
else
{
return -1;
}
}
void readSystemSensors()
{
lipoRawSensor.add(analogRead(SENSOR_LIPO));
solarRawSensor.add(analogRead(SENSOR_SOLAR));
for (int i=0; i < 5; i++) {
lipoRawSensor.add(analogRead(SENSOR_LIPO));
solarRawSensor.add(analogRead(SENSOR_SOLAR));
}
Serial << "Lipo " << lipoRawSensor.getAverage() << " -> " << getBatteryVoltage() << endl;
}
int determineNextPump();
@@ -107,39 +131,42 @@ long getCurrentTime()
return tv_now.tv_sec;
}
//wait till homie flushed mqtt ect.
bool prepareSleep(void *)
{
//FIXME wait till pending mqtt is done, then start sleep via event or whatever
//Homie.disableResetTrigger();
bool queueIsEmpty = true;
if (queueIsEmpty)
{
mDeepsleep = true;
}
return false; // repeat? true there is something in the queue to be done
}
void espDeepSleepFor(long seconds, bool activatePump = false)
{
delay(1500);
if (mode3Active)
{
Serial << "abort deepsleep, mode3Active" << endl;
return;
}
for (int i = 0; i < 10; i++)
{
long cTime = getCurrentTime();
if (cTime < 100000)
{
Serial << "Wait for ntp" << endl;
delay(100);
}
else
{
break;
}
}
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_ON);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
if (activatePump)
{
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
gpio_deep_sleep_hold_en();
gpio_hold_en(GPIO_NUM_13); //pump pwr
}
else
{
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
gpio_hold_dis(GPIO_NUM_13); //pump pwr
gpio_deep_sleep_hold_dis();
digitalWrite(OUTPUT_PUMP, LOW);
digitalWrite(OUTPUT_SENSOR, LOW);
for (int i = 0; i < MAX_PLANTS; i++)
{
mPlants[i].deactivatePump();
@@ -148,19 +175,17 @@ void espDeepSleepFor(long seconds, bool activatePump = false)
//gpio_hold_en(GPIO_NUM_23); //p0
//FIXME fix for outher outputs
Serial.print("Going to sleep for ");
Serial.print("Trying to sleep for ");
Serial.print(seconds);
Serial.println(" seconds");
esp_sleep_enable_timer_wakeup((seconds * 1000U * 1000U));
wait4sleep.in(500, prepareSleep);
mDeepsleep = true;
}
void mode2MQTT()
{
readSystemSensors();
configTime(0, 0, ntpServer);
digitalWrite(OUTPUT_PUMP, LOW);
for (int i = 0; i < MAX_PLANTS; i++)
{
@@ -169,7 +194,7 @@ void mode2MQTT()
if (deepSleepTime.get())
{
Serial << "sleeping for " << deepSleepTime.get() << endl;
Serial << "deepsleep time is configured to " << deepSleepTime.get() << endl;
}
/* Publish default values */
@@ -180,7 +205,23 @@ void mode2MQTT()
}
for (int i = 0; i < MAX_PLANTS; i++)
{
mPlants[i].setProperty("moist").send(String(100 * mPlants[i].getSensorValue() / 4095));
long raw = mPlants[i].getCurrentMoisture();
long pct = 100 - map(raw, MOIST_SENSOR_MIN_ADC, MOIST_SENSOR_MAX_ADC, 0, 100);
if (raw == MISSING_SENSOR)
{
pct = 0;
}
if (pct < 0)
{
pct = 0;
}
if (pct > 100)
{
pct = 100;
}
mPlants[i].setProperty("moist").send(String(pct));
mPlants[i].setProperty("moistraw").send(String(raw));
}
sensorWater.setProperty("remaining").send(String(waterLevelMax.get() - waterRawSensor.getAverage()));
Serial << "W : " << waterRawSensor.getAverage() << " cm (" << String(waterLevelMax.get() - waterRawSensor.getAverage()) << "%)" << endl;
@@ -191,34 +232,31 @@ void mode2MQTT()
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;
int devices = dallas.readAllTemperatures(pFloat, 2);
if (devices < 2)
float t1 = temp1.getMedian();
if (t1 != NAN)
{
if ((pFloat[0] > TEMP_INIT_VALUE) && (pFloat[0] < TEMP_MAX_VALUE))
{
sensorTemp.setProperty("control").send(String(pFloat[0]));
}
sensorTemp.setProperty("control").send(String(t1));
}
else if (devices >= 2)
float t2 = temp2.getMedian();
if (t2 != NAN)
{
if ((pFloat[0] > TEMP_INIT_VALUE) && (pFloat[0] < TEMP_MAX_VALUE))
{
sensorTemp.setProperty("temp").send(String(pFloat[0]));
}
if ((pFloat[1] > TEMP_INIT_VALUE) && (pFloat[1] < TEMP_MAX_VALUE))
{
sensorTemp.setProperty("control").send(String(pFloat[1]));
}
sensorTemp.setProperty("temp").send(String(t2));
}
bool lipoTempWarning = abs(temp[0] - temp[1]) > 5;
//give mqtt time, use via publish callback instead?
delay(100);
bool lipoTempWarning = t1 != 85 && t2 != 85 && abs(t1 - t2) > 10;
if (lipoTempWarning)
{
Serial.println("Lipo temp incorrect, panic mode deepsleep");
espDeepSleepFor(PANIK_MODE_DEEPSLEEP);
return;
Serial.println("Lipo temp incorrect, panic mode deepsleep TODO");
//espDeepSleepFor(PANIK_MODE_DEEPSLEEP);
//return;
}
for (int i = 0; i < MAX_PLANTS; i++)
{
setMoistureTrigger(i, mPlants[i].mSetting->pSensorDry->get());
}
bool hasWater = true; //FIXMEmWaterGone > waterLevelMin.get();
@@ -230,23 +268,32 @@ void mode2MQTT()
}
if (lastPumpRunning != -1 && hasWater)
{
digitalWrite(OUTPUT_PUMP, HIGH);
setLastActivationForPump(lastPumpRunning, getCurrentTime());
mPlants[lastPumpRunning].activatePump();
if (mode3Active)
{
Serial.println("Mode 3 active, ignoring pump request");
}
else
{
digitalWrite(OUTPUT_PUMP, HIGH);
setLastActivationForPump(lastPumpRunning, getCurrentTime());
mPlants[lastPumpRunning].activatePump();
}
}
if (lastPumpRunning == -1 || !hasWater)
{
if (getSolarVoltage() < SOLAR_CHARGE_MIN_VOLTAGE)
{
gotoMode2AfterThisTimestamp = getCurrentTime() + deepSleepNightTime.get();
gotoMode2AfterThisTimestamp = getCurrentTime() + maxTimeBetweenMQTTUpdates.get();
Serial.println("No pumps to activate and low light, deepSleepNight");
espDeepSleepFor(deepSleepNightTime.get());
rtcDeepSleepTime = deepSleepNightTime.get();
}
else
{
gotoMode2AfterThisTimestamp = getCurrentTime() + deepSleepTime.get();
gotoMode2AfterThisTimestamp = getCurrentTime() + maxTimeBetweenMQTTUpdates.get();
Serial.println("No pumps to activate, deepSleep");
espDeepSleepFor(deepSleepTime.get());
rtcDeepSleepTime = deepSleepTime.get();
}
}
else
@@ -257,109 +304,47 @@ void mode2MQTT()
}
}
void setMoistureTrigger(int plantId, long value)
long getMoistureTrigger(int plantId)
{
if (plantId == 0)
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcMoistureTrigger0 = value;
return rtcPlant[plantId].moistTrigger;
}
if (plantId == 1)
else
{
rtcMoistureTrigger1 = value;
}
if (plantId == 2)
{
rtcMoistureTrigger2 = value;
}
if (plantId == 3)
{
rtcMoistureTrigger3 = value;
}
if (plantId == 4)
{
rtcMoistureTrigger4 = value;
}
if (plantId == 5)
{
rtcMoistureTrigger5 = value;
}
if (plantId == 6)
{
rtcMoistureTrigger6 = value;
return -1;
}
}
void setLastActivationForPump(int plantId, long value)
{
if (plantId == 0)
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
rtcLastActive0 = value;
}
if (plantId == 1)
{
rtcLastActive1 = value;
}
if (plantId == 2)
{
rtcLastActive2 = value;
}
if (plantId == 3)
{
rtcLastActive3 = value;
}
if (plantId == 4)
{
rtcLastActive4 = value;
}
if (plantId == 5)
{
rtcLastActive5 = value;
}
if (plantId == 6)
{
rtcLastActive6 = value;
rtcPlant[plantId].lastActive = value;
}
}
long getLastActivationForPump(int plantId)
{
if (plantId == 0)
if ((plantId >= 0) && (plantId < MAX_PLANTS))
{
return rtcLastActive0;
return rtcPlant[plantId].lastActive;
}
if (plantId == 1)
else
{
return rtcLastActive1;
return -1;
}
if (plantId == 2)
{
return rtcLastActive2;
}
if (plantId == 3)
{
return rtcLastActive3;
}
if (plantId == 4)
{
return rtcLastActive4;
}
if (plantId == 5)
{
return rtcLastActive5;
}
if (plantId == 6)
{
return rtcLastActive6;
}
return -1;
}
/**
* @brief Sensors, that are connected to GPIOs, mandatory for WIFI.
* These sensors (ADC2) can only be read when no Wifi is used.
*/
void readSensors()
bool readSensors()
{
float temp[2] = {TEMP_MAX_VALUE, TEMP_MAX_VALUE};
float *pFloat = temp;
bool leaveMode1 = false;
Serial << "Read Sensors" << endl;
readSystemSensors();
@@ -368,7 +353,7 @@ void readSensors()
pinMode(OUTPUT_SENSOR, OUTPUT);
digitalWrite(OUTPUT_SENSOR, HIGH);
delay(100);
delay(20);
/* wait before reading something */
for (int readCnt = 0; readCnt < AMOUNT_SENOR_QUERYS; readCnt++)
{
@@ -376,6 +361,19 @@ void readSensors()
{
mPlants[i].addSenseValue();
}
delay(10);
}
for (int i = 0; i < MAX_PLANTS; i++)
{
long current = mPlants[i].getCurrentMoisture();
long delta = abs(getLastMoisture(i) - current);
bool tmp = (delta > MOIST_DELTA_TRIGGER_ADC);
setLastMoisture(i, current);
if (tmp)
{
leaveMode1 = true;
Serial.printf("Mode2 start due to moist delta in plant %d with %ld \r\n", i, delta);
}
}
Serial << "DS18B20" << endl;
@@ -384,24 +382,34 @@ void readSensors()
delay(200);
/* Required to read the temperature once */
float temp[2] = {0, 0};
float *pFloat = temp;
for (int i = 0; i < 10; i++)
for (int i = 0; i < 5; i++)
{
if (dallas.readAllTemperatures(pFloat, 2) > 0)
int sensors = dallas.readAllTemperatures(pFloat, 2);
if (sensors > 0)
{
Serial << "t1: " << String(temp[0]) << endl;
Serial << "t2: " << String(temp[1]) << endl;
// first read returns crap, ignore result and read again
if (i <= 2)
{
temp1.add(temp[0]);
temp2.add(temp[1]);
}
temp1.add(temp[0]);
}
delay(200);
if (sensors > 1)
{
Serial << "t2: " << String(temp[1]) << endl;
temp2.add(temp[1]);
}
delay(50);
}
if ((temp1.getAverage() - rtcLastTemp1 > TEMPERATURE_DELTA_TRIGGER_IN_C) ||
(rtcLastTemp1 - temp1.getAverage() > TEMPERATURE_DELTA_TRIGGER_IN_C)) {
leaveMode1 = true;
}
if ((temp2.getAverage() - rtcLastTemp2 > TEMPERATURE_DELTA_TRIGGER_IN_C) ||
(rtcLastTemp2 - temp2.getAverage() > TEMPERATURE_DELTA_TRIGGER_IN_C)) {
leaveMode1 = true;
}
rtcLastTemp1 = temp1.getAverage();
rtcLastTemp2 = temp2.getAverage();
/* Use the Ultrasonic sensor to measure waterLevel */
for (int i = 0; i < 5; i++)
{
@@ -417,6 +425,7 @@ void readSensors()
}
/* deactivate the sensors */
digitalWrite(OUTPUT_SENSOR, LOW);
return leaveMode1;
}
//Homie.getMqttClient().disconnect();
@@ -429,9 +438,11 @@ void onHomieEvent(const HomieEvent &event)
Homie.getLogger() << "My statistics" << endl;
break;
case HomieEventType::MQTT_READY:
Serial.printf("NTP Setup with server %s\r\n", ntpServer.get());
configTime(0, 0, ntpServer.get());
//wait for rtc sync?
rtcDeepSleepTime = deepSleepTime.get();
Serial << "MQTT ready " << rtcDeepSleepTime << " ms ds" << endl;
Serial << "Setup plants" << endl;
for (int i = 0; i < MAX_PLANTS; i++)
{
mPlants[i].postMQTTconnection();
@@ -444,8 +455,9 @@ void onHomieEvent(const HomieEvent &event)
esp_deep_sleep_start();
break;
case HomieEventType::OTA_STARTED:
Homie.getLogger() << "OTA started" << endl;
digitalWrite(OUTPUT_SENSOR, HIGH);
digitalWrite(OUTPUT_PUMP, LOW);
digitalWrite(OUTPUT_PUMP, HIGH);
gpio_hold_dis(GPIO_NUM_13); //pump pwr
gpio_deep_sleep_hold_dis();
for (int i = 0; i < MAX_PLANTS; i++)
@@ -455,6 +467,7 @@ void onHomieEvent(const HomieEvent &event)
mode3Active = true;
break;
case HomieEventType::OTA_SUCCESSFUL:
Homie.getLogger() << "OTA successfull" << endl;
digitalWrite(OUTPUT_SENSOR, LOW);
digitalWrite(OUTPUT_PUMP, LOW);
ESP.restart();
@@ -470,32 +483,46 @@ int determineNextPump()
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?
int pumpToUse = -1;
for (int i = 0; i < MAX_PLANTS; i++)
{
Plant plant = mPlants[i];
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].isInCooldown(sinceLastActivation))
if (plant.isInCooldown(sinceLastActivation))
{
Serial.printf("%d Skipping due to cooldown\r\n", i);
Serial.printf("%d Skipping due to cooldown %ld / %ld \r\n", i, sinceLastActivation, plant.getCooldownInSeconds());
setMoistureTrigger(i, DEACTIVATED_PLANT);
continue;
}
//skip as it is not low light
if (!isLowLight && mPlants[i].isAllowedOnlyAtLowLight())
if (!isLowLight && plant.isAllowedOnlyAtLowLight())
{
Serial.println("Skipping due to light");
Serial.printf("%d No pump required: due to light\r\n", i);
continue;
}
if (mPlants->isPumpRequired())
if (plant.getCurrentMoisture() == MISSING_SENSOR && plant.isPumpTriggerActive())
{
Serial.printf("%d No pump possible: missing sensor \r\n", i);
continue;
}
if (plant.isPumpRequired())
{
Serial.printf("%d Requested pumping\r\n", i);
return i;
pumpToUse = i;
}
else if (plant.isPumpTriggerActive())
{
Serial.printf("%d No pump required: moisture acceptable %f / %ld\r\n", i, plant.getCurrentMoisture(), plant.getSettingsMoisture());
}
else
{
Serial.printf("%d No pump required: disabled pump trigger \r\n", i);
}
Serial.printf("%d No pump required\r\n", i);
}
return -1;
return pumpToUse;
}
/**
@@ -510,7 +537,6 @@ 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;
@@ -536,9 +562,11 @@ void systemInit()
// Set default values
//in seconds
deepSleepTime.setDefaultValue(10);
deepSleepNightTime.setDefaultValue(30);
maxTimeBetweenMQTTUpdates.setDefaultValue(120);
deepSleepTime.setDefaultValue(60);
deepSleepNightTime.setDefaultValue(600);
wateringDeepSleep.setDefaultValue(5);
ntpServer.setDefaultValue("pool.ntp.org");
/* waterLevelMax 1000 */ /* 100cm in mm */
waterLevelMin.setDefaultValue(50); /* 5cm in mm */
@@ -547,6 +575,7 @@ void systemInit()
Homie.setLoopFunction(homieLoop);
Homie.onEvent(onHomieEvent);
//Homie.disableLogging();
Homie.setup();
mConfigured = Homie.isConfigured();
@@ -589,63 +618,46 @@ void systemInit()
bool mode1()
{
Serial.println("m1");
Serial.println("==== Mode 1 ====");
Serial << getCurrentTime() << " curtime" << endl;
/* Disable all sleeping stuff before reading sensors */
gpio_deep_sleep_hold_dis();
readSensors();
bool deltaTrigger = readSensors();
//queue sensor values for
if ((rtcDeepSleepTime == 0) ||
(rtcMoistureTrigger0 == 0) ||
(rtcMoistureTrigger1 == 0) ||
(rtcMoistureTrigger2 == 0) ||
(rtcMoistureTrigger3 == 0) ||
(rtcMoistureTrigger4 == 0) ||
(rtcMoistureTrigger5 == 0) ||
(rtcMoistureTrigger6 == 0))
if (deltaTrigger)
{
Serial.println("RTCm2");
Serial.println("1 delta triggered, going to mode2");
return true;
}
if (rtcDeepSleepTime == 0)
{
Serial.println("1 missing rtc value, going to mode2");
return true;
}
for (int i = 0; i < MAX_PLANTS; i++)
{
long trigger = getMoistureTrigger(i);
if (trigger == 0)
{
Serial << "Missing rtc trigger " << i << endl;
return true;
}
if (trigger == DEACTIVATED_PLANT)
{
continue;
}
long raw = mPlants[i].getCurrentMoisture();
if (raw == MISSING_SENSOR)
{
continue;
}
if (raw > trigger)
{
Serial << "plant " << i << " dry " << raw << " / " << trigger << " starting mode 2" << endl;
return true;
}
}
if ((rtcMoistureTrigger0 != DEACTIVATED_PLANT) && (mPlants[0].getSensorValue() < rtcMoistureTrigger0))
{
Serial.println("mt0");
return true;
}
if ((rtcMoistureTrigger1 != DEACTIVATED_PLANT) && (mPlants[1].getSensorValue() < rtcMoistureTrigger1))
{
Serial.println("mt1");
return true;
}
if ((rtcMoistureTrigger2 != DEACTIVATED_PLANT) && (mPlants[2].getSensorValue() < rtcMoistureTrigger2))
{
Serial.println("mt2");
return true;
}
if ((rtcMoistureTrigger3 != DEACTIVATED_PLANT) && (mPlants[3].getSensorValue() < rtcMoistureTrigger3))
{
Serial.println("mt3");
return true;
}
if ((rtcMoistureTrigger4 != DEACTIVATED_PLANT) && (mPlants[4].getSensorValue() < rtcMoistureTrigger4))
{
Serial.println("mt4");
return true;
}
if ((rtcMoistureTrigger5 != DEACTIVATED_PLANT) && (mPlants[5].getSensorValue() < rtcMoistureTrigger5))
{
Serial.println("mt5");
return true;
}
if ((rtcMoistureTrigger6 != DEACTIVATED_PLANT) && (mPlants[6].getSensorValue() < rtcMoistureTrigger6))
{
Serial.println("mt6");
return true;
}
//check how long it was already in mode1 if to long goto mode2
long cTime = getCurrentTime();
@@ -660,18 +672,22 @@ bool mode1()
Serial.println("Starting mode 2 after specified mode1 time");
return true;
}
else
{
Serial << "Mode2 Timer " << gotoMode2AfterThisTimestamp << " curtime " << cTime << endl;
}
return false;
}
void mode2()
{
Serial.println("m2");
Serial.println("==== Mode 2 ====");
systemInit();
/* Jump into Mode 3, if not configured */
if (!mConfigured)
{
Serial.println("m3");
Serial.println("==== Mode 3 ====");
mode3Active = true;
}
}
@@ -732,8 +748,7 @@ void setup()
else
{
Serial.println("nop");
Serial.flush();
esp_deep_sleep_start();
espDeepSleepFor(rtcDeepSleepTime);
}
}
@@ -741,22 +756,36 @@ void setup()
* @brief Cyclic call
* Executs the Homie base functionallity or triggers sleeping, if requested.
*/
long nextBlink = 0;
void loop()
{
if (!mDeepsleep)
if (!mDeepsleep || mode3Active)
{
Homie.loop();
}
else
{
Serial << "Bye" << endl;
Serial.flush();
esp_deep_sleep_start();
}
if (millis() > 30000 && !mode3Active)
{
Serial << (millis() / 1000) << "s alive" << endl;
Serial << (millis() / 1000) << "not terminated watchdog putting to sleep" << endl;
Serial.flush();
esp_deep_sleep_start();
espDeepSleepFor(rtcDeepSleepTime);
}
/* Toggel Senor LED to visualize mode 3 */
if (mode3Active)
{
if (nextBlink < millis())
{
nextBlink = millis() + 500;
digitalWrite(OUTPUT_SENSOR, !digitalRead(OUTPUT_SENSOR));
}
}
}
/** @}*/