Skip to content

Instantly share code, notes, and snippets.

@Thewest123
Last active August 4, 2022 15:11
Show Gist options
  • Save Thewest123/9e1db8db753056add4f5902816aaa618 to your computer and use it in GitHub Desktop.
Save Thewest123/9e1db8db753056add4f5902816aaa618 to your computer and use it in GitHub Desktop.
Smart Plant Watering (Thinger.io + NodeMCU + OTA)
//#define _DEBUG_ //Uncomment for ThingerIO debug print
#include <ThingerESP8266.h>
#include <ThingerConsole.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUDP.h>
#include <ArduinoOTA.h>
#include <NTPClient.h>
#define USERNAME "<THINGER_USERNAME>"
#define DEVICE_ID "<THINGER_ID>"
#define DEVICE_CREDENTIAL "<THINGER_SECRET>"
#define SSID "WIFI_SSID"
#define SSID_PASSWORD "<WIFI_PASSWORD>"
#define HOSTNAME "ESP-MintGrower"
#define OTA_PASSWORD "<OTA_PASSWORD>"
#define pumpPin 13
#define ledsPin 15
#define analogPin A0
#define moisturePin 14
const int turnOffTimes[] = {23,0,1,2,3,4,5,6}; //Hours when LEDs are disabled, according to NTP time
const int AirValue = 680; //Moisture sensor value in air
const int WaterValue = 280; //Moisture sensor value in water
const int utcOffsetInSeconds = 7200; //UTC offset - set to 2 hours (Czech Republic)
// --------------------------------------------------------------------
ThingerESP8266 thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerConsole console(thing);
// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
int soilMoistureValue;
int soilMoisturePercent;
bool leds_enabled = false;
bool pump_enabled = false;
int moisture = 99;
unsigned long lastMillisNTP = 10000;
unsigned long lastMillisPump = 10000;
unsigned long lastMillisBlink = 10000;
unsigned long lastMillisMoistureRead = 10000;
int onboardLedState = LOW;
bool input = false;
void setup() {
Serial.begin(115200);
setupPins();
setupWiFi();
setupOTA();
setupThingerIO();
//NTP Client
timeClient.begin();
updateNtpTime();
}
void loop() {
ArduinoOTA.handle();
unsigned long currentMillis = millis();
//Update NTP every 2 hours
if (currentMillis - lastMillisNTP >= 7200000) {
lastMillisNTP = currentMillis;
updateNtpTime();
}
//Turn on/off lights based on NTP time and schedule
if (leds_enabled && isNightTime()) {
disableLEDs();
}
else if (!leds_enabled && !isNightTime()) {
enableLEDs();
}
//Read Soil Moisture every 1s
if (currentMillis - lastMillisMoistureRead >= 1000) {
lastMillisMoistureRead = currentMillis;
readSoilMoisture();
}
//Pump water if moisture is below 30% and last pumping was more than 5 minutes ago
if (moisture <= 30 && currentMillis-lastMillisPump > 301000) {
printTerminal("Automatic pumping started");
enablePump();
}
//Disable pump after 5 seconds of running
if(pump_enabled && currentMillis-lastMillisPump > 5000) {
disablePump();
}
//Blink the LED every 5s
if (currentMillis - lastMillisBlink >= 500) {
lastMillisBlink = currentMillis;
onboardLedState = !onboardLedState;
digitalWrite(LED_BUILTIN, onboardLedState);
}
thing.handle();
}
/*
* Methods for measuring
*/
void readSoilMoisture() {
soilMoistureValue = analogRead(analogPin);
soilMoisturePercent = constrain(map(soilMoistureValue, AirValue, WaterValue, 0, 100), 0, 100);
moisture = soilMoisturePercent;
}
void enablePump() {
lastMillisPump = millis();
printTerminal("Pump enabled");
pump_enabled = true;
digitalWrite(pumpPin, HIGH);
thing.stream("pump_enabled");
pson data;
data["value1"] = moisture;
thing.call_endpoint("ifttt_pump_enabled", data);
}
void disablePump() {
digitalWrite(pumpPin, LOW);
pump_enabled = false;
thing.stream("pump_enabled");
printTerminal("Pump disabled");
}
void enableLEDs() {
leds_enabled = true;
digitalWrite(ledsPin, HIGH);
thing.stream("leds_enabled");
printTerminal("LEDs enabled");;
}
void disableLEDs() {
digitalWrite(ledsPin, LOW);
leds_enabled = false;
thing.stream("leds_enabled");
printTerminal("LEDs disabled");
}
/*
* Utils
*/
void printTerminal(String message) {
Serial.println(message);
console.println(message);
}
bool isNightTime() {
for (int i = 0; i < sizeof(turnOffTimes); i++)
{
if (timeClient.getHours() == turnOffTimes[i]) {
return true;
}
}
return false;
}
void updateNtpTime() {
timeClient.update();
printTerminal("(NTP) Time update: " + timeClient.getFormattedTime());
}
/*
* Setups
*/
void setupPins() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(pumpPin, OUTPUT);
pinMode(ledsPin, OUTPUT);
}
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.hostname(HOSTNAME);
WiFi.begin(SSID, SSID_PASSWORD);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
printTerminal("(WiFi) Connection Failed! Rebooting...");
delay(20000);
ESP.restart();
}
printTerminal("(WiFi) Connected!");
}
void setupOTA() {
ArduinoOTA.setPort(8266);
ArduinoOTA.setHostname(HOSTNAME);
ArduinoOTA.setPassword(OTA_PASSWORD);
ArduinoOTA.onStart([]() {
printTerminal("(OTA) Start");
});
ArduinoOTA.onEnd([]() {
printTerminal("(OTA) End");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("(OTA) Progress: %u%%\r", (progress / (total / 100)));
console.printf("(OTA) Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("(OTA) Error[%u]: ", error);
console.printf("(OTA) Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) printTerminal("Auth Failed");
else if (error == OTA_BEGIN_ERROR) printTerminal("Begin Failed");
else if (error == OTA_CONNECT_ERROR) printTerminal("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) printTerminal("Receive Failed");
else if (error == OTA_END_ERROR) printTerminal("End Failed");
});
ArduinoOTA.begin();
}
void setupThingerIO() {
//Thinger.io set resources
thing["millis"] >> outputValue(millis());
thing["RSSI"] >> outputValue (WiFi.RSSI());
thing["moisture"] >> outputValue(moisture);
thing["leds_enabled"] >> [](pson& out){
out = leds_enabled ? 1 : 0;
};
thing["pump_enabled"] >> [](pson& out){
out = pump_enabled ? 1 : 0;
};
thing["enablePump"] << inputValue(input,{
input = false;
enablePump();
});
thing["updateCurrentLEDsState"] << inputValue(input,{
input = false;
thing.stream("leds_enabled");
printTerminal("LEDs state updated");
});
thing.handle();
}
@Thewest123
Copy link
Author

Why have a seperate function for OTA, yet Thinger.io handles OTA by itself??

I don't think Thinger.io had its own OTA system at the time of writing this script. But thank you for the info, really good to know for future projects! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment