We have a smart garden system that also uses a terminal to send messages back and forth about the program but recently our terminal has been really slow and sometimes doesn’t send any messages, and sometimes only sends the last line of a message, or gets cut off. The other components and other widgets in our dashboard work completely. We are using the free version of the web dashboard in the Netherlands. Thank you for the help in advance.
#define BLYNK_TEMPLATE_ID "TMPL4TX2-8kTv"
#define BLYNK_TEMPLATE_NAME "Smart Garden"
#define BLYNK_AUTH_TOKEN "xxxxxxxxxxxxxxxxxxx"
#define BLYNK_DEVICE_NAME "wemos"
#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include "DHT.h"
#include <Wire.h>
#include <BH1750.h>
#include <WidgetTerminal.h>
#include <AIChatbot.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <ArduinoJson.h>
// Pump output = 200ml / 24s ≈ 8.3ml/s
// Per-plant watering duration (milliseconds)
unsigned long pumpDoseMs[] = {
12000, // Orchid ~100 ml
24000, // Monstera ~200 ml
18000, // Pothos ~150 ml
6000, // Snake Plant ~50 ml
24000, // Peace Lily ~200 ml
6000, // Cactus ~50 ml
6000, // Succulent ~50 ml
18000 // Basil ~150 ml
};
bool waitingForSoilToRecover = false;
bool pumpOn = false;
//tank info
const float TANK_CAPACITY_ML = 750.00; // 0.75 liter
float waterLeftMl = TANK_CAPACITY_ML; // start full
const float PUMP_FLOW_ML_PER_MS = 8.3 / 1000.0; // 8.3 ml/s → ml/ms
bool tankEmpty = false;
// ----- DHT22 Setup -----
#define DHTPIN D3
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// ----- BH1750 Light Sensor -----
BH1750 lightMeter;
// ----- Soil Moisture Sensor -----
#define MOISTURE_PIN A0
// ----- Relay / Pump -----
#define RELAY_PIN D0
// ----- Timer -----
BlynkTimer timer;
// ----- Terminal Widget -----
WidgetTerminal terminal(V20);
// ----- ChatGPT -----
AIChatbot chatbot;
// ----- Config -----
bool autoStatus = false; // auto status printing
int selectedPlant = 0;
// Global variables for last values
float lastTemp = NAN;
float lastHumidity = NAN;
float lastLux = NAN;
int lastSoilPct = -1;
int lastEnvScore = -1;
// ======================= PLANT PROFILES =======================
struct Range {float warnMin, idealMin, idealMax, warnMax; };
struct PlantProfile {
String name;
Range temp, humidity, light, soil;
};
PlantProfile orchid = {"Phalaenopsis Orchid", {16,18,26,30},{40,50,70,80},{300,700,2500,15000},{25,35,60,70}};
PlantProfile monstera = {"Monstera deliciosa", {15,18,28,32},{40,50,70,80},{300,700,3000,20000},{30,40,65,75}};
PlantProfile pothos = {"Golden Pothos", {12,18,26,30},{30,40,60,80},{100,300,2500,20000},{25,35,60,75}};
PlantProfile snakePlant = {"Snake Plant", {10,18,28,32},{20,30,50,70},{50,150,2500,20000},{10,15,40,55}};
PlantProfile peaceLily = {"Peace Lily", {15,18,26,30},{40,50,70,80},{150,300,2000,15000},{35,45,70,80}};
PlantProfile cactus = {"Desert Cactus", {8,20,30,35},{15,20,40,60},{3000,10000,40000,60000},{5,10,30,40}};
PlantProfile succulent = {"Succulent", {10,18,28,32},{15,20,50,70},{2500,8000,35000,60000},{5,10,30,40}};
PlantProfile basil = {"Basil", {15,21,29,32},{40,50,70,80},{5000,10000,40000,60000},{30,40,65,75}};
PlantProfile currentProfile;
void updateSensors(bool forcePrint=false);
// ======================= HELPER FUNCTIONS =======================
/*String extractChatGPTContent(const String &raw) {
int idx = raw.indexOf("content:");
if (idx == -1) return "Error: no content found";
idx += 8; // move past "content:"
// Look for the next field marker (e.g. "refusal:")
int endIdx = raw.indexOf("refusal:", idx);
if (endIdx == -1) endIdx = raw.length();
String content = raw.substring(idx, endIdx);
content.trim();
// Remove trailing commas or spaces
while (content.endsWith(",") || content.endsWith("\n")) {
content.remove(content.length()-1);
}
return content;
}*/
void updateCurrentProfile() {
switch(selectedPlant){
case 1: currentProfile = orchid; break;
case 2: currentProfile = monstera; break;
case 3: currentProfile = pothos; break;
case 4: currentProfile = snakePlant; break;
case 5: currentProfile = peaceLily; break;
case 6: currentProfile = cactus; break;
case 7: currentProfile = succulent; break;
case 8: currentProfile = basil; break;
default: break;
}
}
float scoreParameter(float value, const Range &r){
if(value >= r.idealMin && value <= r.idealMax) return 1.0;
if(value >= r.warnMin && value < r.idealMin) return (value - r.warnMin)/(r.idealMin - r.warnMin);
if(value > r.idealMax && value <= r.warnMax) return (r.warnMax - value)/(r.warnMax - r.idealMax);
return 0.0;
}
float computeEnvironmentScore(const PlantProfile &p, float t,float h,float lux,float soil){
float s = 0.2*scoreParameter(t,p.temp)
+ 0.1*scoreParameter(h,p.humidity)
+ 0.3*scoreParameter(lux,p.light)
+ 0.4*scoreParameter(soil,p.soil);
return constrain(s*100,0,100);
}
// ======================= BLYNK HANDLERS =======================
// Plant selection (V10)
BLYNK_WRITE(V10){
selectedPlant = param.asInt();
updateCurrentProfile();
if(!autoStatus){
terminal.print("Plant selected: ");
switch(selectedPlant){
case 1: terminal.println("Orchid 🌸"); break;
case 2: terminal.println("Monstera 🍃"); break;
case 3: terminal.println("Golden Pothos 🌿"); break;
case 4: terminal.println("Snake Plant 🐍"); break;
case 5: terminal.println("Peace Lily 🌼"); break;
case 6: terminal.println("Desert Cactus 🌵"); break;
case 7: terminal.println("Succulent 🌱"); break;
case 8: terminal.println("Basil 🌿"); break;
default: terminal.println("None"); break;
}
terminal.flush();
}
}
// Pump refill button
BLYNK_WRITE(V8)
{
int v = param.asInt();
if (v == 1) {
waterLeftMl = TANK_CAPACITY_ML;
tankEmpty = false;
Blynk.virtualWrite(V11, 0); // reset button
terminal.println("Tank refilled ✅");
terminal.flush();
}
}
// Terminal (V20)
BLYNK_WRITE(V20) {
String cmd = param.asStr();
cmd.trim();
cmd.toLowerCase();
if (cmd == "help") {
String out;
out.reserve(300);
out += "Commands:\n";
out += "help - Show this list\n";
out += "status - Show current plant & env status\n";
terminal.print(out);
terminal.flush();
}
else if (cmd == "status") {
updateSensors(true);
}
//else if (cmd == "mode auto") {
// autoStatus = true;
// terminal.println("✅ Auto logging ENABLED");
//terminal.flush();
//}
//else if (cmd == "mode manual") {
//autoStatus = false;
// terminal.println("⚠️ Auto logging DISABLED");
// terminal.flush();
// }
/*else if (cmd.startsWith("ask ")) {
String question = cmd.substring(4);
String context = "Plant: " + currentProfile.name + "\n"
+ "Temperature: " + String(lastTemp,1) + " °C (ideal " + String(currentProfile.temp.idealMin) + "-" + String(currentProfile.temp.idealMax) + ")\n"
+ "Humidity: " + String(lastHumidity,1) + " % (ideal " + String(currentProfile.humidity.idealMin) + "-" + String(currentProfile.humidity.idealMax) + ")\n"
+ "Light: " + String(lastLux,0) + " lux (ideal " + String(currentProfile.light.idealMin) + "-" + String(currentProfile.light.idealMax) + ")\n"
+ "Soil Moisture: " + String(lastSoilPct) + " % (ideal " + String(currentProfile.soil.idealMin) + "-" + String(currentProfile.soil.idealMax) + ")\n"
+ "Environment Score: " + String(lastEnvScore) + "/100\n";
String fullQuestion = question + "\nContext: " + context;
terminal.println(fullQuestion);
// Get raw JSON response from the chatbot
String rawResponse = chatbot.getResponse(fullQuestion);
// Extract just the assistant's content
String answer = extractChatGPTContent(rawResponse);
// Print to terminal
terminal.println("🤖 ChatGPT:");
terminal.println(answer);
terminal.println("----------------------------");
terminal.flush();
}*/
else {
terminal.println("Unknown command. Type 'help'");
terminal.flush();
}
}
void handleAutoWatering(int plantIndex, int soilPct) {
if (plantIndex <= 0) return;
int threshold = currentProfile.soil.idealMin;
if (soilPct >= threshold) {
waitingForSoilToRecover = false;
return;
}
if (waitingForSoilToRecover || tankEmpty) return;
// --- Soil just became dry → run pump once ---
unsigned long duration = pumpDoseMs[plantIndex - 1];
// Check if enough water left
float needed = duration * PUMP_FLOW_ML_PER_MS;
if (needed > waterLeftMl) {
terminal.println("Tank empty! Cannot water " + currentProfile.name);
terminal.flush();
tankEmpty = true;
return;
}
Serial.println("Auto watering " + currentProfile.name +
" for " + String(duration) + " ms");
pumpOn = true;
digitalWrite(RELAY_PIN, HIGH);
delay(duration);
digitalWrite(RELAY_PIN, LOW);
pumpOn = false;
waterLeftMl -= needed;
if (waterLeftMl <= 0) {
waterLeftMl = 0;
tankEmpty = true;
terminal.println("Tank empty! Please refill.");
terminal.flush();
}
waitingForSoilToRecover = true;
}
// ======================= SENSOR UPDATE =======================
void updateSensors(bool forcePrint){
float h=dht.readHumidity();
float t=dht.readTemperature();
float lux=lightMeter.readLightLevel();
int moistureRaw=analogRead(MOISTURE_PIN);
int moisturePct=constrain(map(moistureRaw,728,300,0,100),0,100);
lastTemp = t;
lastHumidity = h;
lastLux = lux;
lastSoilPct = moisturePct;
Blynk.virtualWrite(V0,t);
Blynk.virtualWrite(V1,h);
Blynk.virtualWrite(V2,dht.computeHeatIndex(t,h,false));
Blynk.virtualWrite(V3,lux);
Blynk.virtualWrite(V4,moisturePct);
if(selectedPlant==0){
digitalWrite(RELAY_PIN,LOW);
Blynk.virtualWrite(V5,0);
Blynk.virtualWrite(V6,0);
Blynk.virtualWrite(V7,"Select a plant");
if(autoStatus || forcePrint){
terminal.println("No plant selected.");
terminal.flush();
}
return;
}
// Automatic timed watering logic:
handleAutoWatering(selectedPlant, moisturePct);
// Pump always manually controllable but OFF after auto
digitalWrite(RELAY_PIN, LOW);
Blynk.virtualWrite(V5, 0);
int envScore = (int)computeEnvironmentScore(currentProfile,t,h,lux,moisturePct);
lastEnvScore = envScore;
Blynk.virtualWrite(V6,envScore);
// Status string
String status="";
String sep=" | ";
status += moisturePct<currentProfile.soil.idealMin?"Soil too dry":
moisturePct>currentProfile.soil.idealMax?"Soil too wet":"Soil OK"; status+=sep;
status += t<currentProfile.temp.warnMin?"Too cold":
t>currentProfile.temp.warnMax?"Too hot":
(t<currentProfile.temp.idealMin||t>currentProfile.temp.idealMax)?"Temp slightly off":"Temp OK"; status+=sep;
status += lux<currentProfile.light.warnMin?"Light too low":
lux>currentProfile.light.warnMax?"Light too bright":
(lux<currentProfile.light.idealMin||lux>currentProfile.light.idealMax)?"Light slightly off":"Light OK"; status+=sep;
status += h<currentProfile.humidity.warnMin?"Air too dry":
h>currentProfile.humidity.warnMax?"Air too humid":
(h<currentProfile.humidity.idealMin||h>currentProfile.humidity.idealMax)?"Humidity slightly off":"Humidity OK";
if(envScore>=80 && status.indexOf("too")==-1 && status.indexOf("off")==-1) status="Environment OK 🌱";
String htmlStatus = "<small><small>" + status + "</small></small>";
Blynk.virtualWrite(V7, htmlStatus);
int waterPct = (int)((waterLeftMl / TANK_CAPACITY_ML) * 100);
Blynk.virtualWrite(V12, waterPct); // V12: gauge in app
}
// Wrapper for timer
void updateSensorsWrapper(){ updateSensors(false); }
// ======================= SETUP & LOOP =======================
void setup(){
Serial.begin(115200);
dht.begin();
Wire.begin(D2,D1);
lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
pinMode(RELAY_PIN,OUTPUT);
digitalWrite(RELAY_PIN,LOW);
// ChatGPT setup
/*chatbot.begin(115200);
chatbot.setKey("xxxxxxxxxxx","chatgpt");
chatbot.selectAI("chatgpt","gpt-3.5-turbo");*/
// Blynk
Blynk.begin(BLYNK_AUTH_TOKEN,"NDL_24G","xxxxxxxxxxx");
timer.setInterval(3000L, updateSensorsWrapper);
}
BLYNK_CONNECTED() {
terminal.clear();
terminal.println("🌱 Smart Garden started");
terminal.println("Type 'help' for commands");
terminal.flush();
}
void loop(){
Blynk.run();
timer.run();
//chatbot.update();
}