#include <WiFi.h>
#include <WebServer.h>
#include "DHTesp.h"
#include "RTClib.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <LiquidCrystal_I2C.h>
// ====== Константи ======
#define PUMP_PIN 5
#define DHT_PIN 15
#define SOIL_PIN 34
#define PH_PIN 35
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASS = "";
const char* OPENAI_KEY = "b453666eb3d04c1fa7c608375d88f7fc";
enum PumpMode { WAIT, ACTIVE, REST, ERROR_STATE };
PumpMode currentMode = WAIT;
unsigned long lastModeChange = 0;
const int soilLimit = 35;
const int delta = 5;
const int minFlow = 50;
const unsigned long MIN_ON = 10000;
const unsigned long MIN_OFF = 15000;
const unsigned long REST_TIME = 20000;
// ====== Об'єкти ======
WebServer web(80);
RTC_DS1307 rtc;
DHTesp dht;
LiquidCrystal_I2C lcd(0x27,16,2);
// ====== Дані ======
String lastAI = "Awaiting advice...";
#define HISTORY 50
float tHistory[HISTORY], hHistory[HISTORY], phHistory[HISTORY];
int sHistory[HISTORY];
int idx = 0;
// ====== Web UI ======
void pageDashboard() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Smart Irrigation</title>
<style>
body { font-family:Arial; background:#eef; padding:20px; }
h1 { color:#2c3e50; }
.card { background:white; padding:10px; margin:10px; border-radius:8px; box-shadow:0 2px 4px rgba(0,0,0,0.2);}
#charts { display:flex; flex-wrap:wrap; }
canvas { margin:10px; background:#fff; border:1px solid #ccc; }
button { padding:10px 20px; margin-top:10px; }
</style>
</head>
<body>
<h1>🌿 Smart Irrigation</h1>
<div class="card"><b>AI Suggestion:</b> <span id="ai">Loading...</span></div>
<div id="charts">
<canvas id="tempChart" width="300" height="150"></canvas>
<canvas id="humChart" width="300" height="150"></canvas>
<canvas id="soilChart" width="300" height="150"></canvas>
<canvas id="phChart" width="300" height="150"></canvas>
</div>
<button onclick="fetch('/pump').then(r=>r.text()).then(alert)">🚿 Manual Pump</button>
<script>
async function refresh(){
let res = await fetch('/json');
let d = await res.json();
document.getElementById("ai").innerText = d.ai;
function draw(id,label,data,color){
let ctx=document.getElementById(id).getContext("2d");
ctx.clearRect(0,0,300,150);
ctx.beginPath();
ctx.strokeStyle=color;
ctx.moveTo(0,150-data[0]);
for(let i=1;i<data.length;i++){
ctx.lineTo(i*6,150-data[i]);
}
ctx.stroke();
ctx.fillText(label,10,10);
}
draw("tempChart","Temp", d.hist.map(x=>x.temp),"red");
draw("humChart","Humidity", d.hist.map(x=>x.hum),"blue");
draw("soilChart","Soil", d.hist.map(x=>x.soil),"green");
draw("phChart","pH", d.hist.map(x=>x.ph*20),"purple");
}
setInterval(refresh,3000);
refresh();
</script>
</body>
</html>
)rawliteral";
web.send(200,"text/html",html);
}
void pageData(){
DynamicJsonDocument doc(2048);
JsonArray hist = doc.createNestedArray("hist");
for(int i=0;i<HISTORY;i++){
int j=(idx+i)%HISTORY;
if(tHistory[j]==0 && hHistory[j]==0) continue;
JsonObject o=hist.createNestedObject();
o["temp"]=tHistory[j];
o["hum"]=hHistory[j];
o["soil"]=sHistory[j];
o["ph"]=phHistory[j];
}
doc["ai"]=lastAI;
String out; serializeJson(doc,out);
web.send(200,"application/json",out);
}
void pagePump(){
digitalWrite(PUMP_PIN,HIGH);
delay(1000);
digitalWrite(PUMP_PIN,LOW);
web.send(200,"text/plain","Pump pulse executed");
}
// ====== GPT ======
void askAI(String prompt){
DynamicJsonDocument req(1024);
req["model"]="gpt-3.5-turbo";
JsonArray arr=req.createNestedArray("messages");
JsonObject sys=arr.createNestedObject();
sys["role"]="system";
sys["content"]="You are an irrigation assistant. Keep it short.";
JsonObject usr=arr.createNestedObject();
usr["role"]="user";
usr["content"]=prompt;
HTTPClient http;
http.begin("https://artificialintelligence.openai.azure.com/openai/deployments/test/chat/completions?api-version=2023-05-15");
http.addHeader("Content-Type","application/json");
http.addHeader("api-key",OPENAI_KEY);
String body; serializeJson(req,body);
int code=http.POST(body);
if(code==200){
DynamicJsonDocument resp(2048);
deserializeJson(resp,http.getString());
lastAI=resp["choices"][0]["message"]["content"].as<String>();
Serial.println("AI says: "+lastAI);
} else {
Serial.println("Error: "+String(code));
}
http.end();
}
// ====== FSM ======
bool canStart(int soil){ return soil<soilLimit && (millis()-lastModeChange>MIN_OFF);}
bool canStop(int soil){ return soil>soilLimit+delta && (millis()-lastModeChange>MIN_ON);}
void fsm(int soil,int flow){
switch(currentMode){
case WAIT:
if(canStart(soil)){
digitalWrite(PUMP_PIN,HIGH);
currentMode=ACTIVE;
lastModeChange=millis();
}
break;
case ACTIVE:
if(canStop(soil)){
digitalWrite(PUMP_PIN,LOW);
currentMode=REST;
lastModeChange=millis();
}
if(flow>=0 && flow<minFlow){
digitalWrite(PUMP_PIN,LOW);
currentMode=ERROR_STATE;
}
break;
case REST:
if(millis()-lastModeChange>REST_TIME){
currentMode=WAIT;
lastModeChange=millis();
}
break;
case ERROR_STATE:
// Manual reset required
break;
}
}
// ====== Flow Simulation ======
int getFlow(){ return random(0,2); }
// ====== Setup ======
int once=0;
void setup(){
Serial.begin(115200);
pinMode(SOIL_PIN,INPUT);
pinMode(PH_PIN,INPUT);
pinMode(PUMP_PIN,OUTPUT);
digitalWrite(PUMP_PIN,LOW);
WiFi.begin(WIFI_SSID,WIFI_PASS);
while(WiFi.status()!=WL_CONNECTED){ delay(300); Serial.print("."); }
Serial.println("IP: "+WiFi.localIP().toString());
web.on("/",pageDashboard);
web.on("/json",pageData);
web.on("/pump",pagePump);
web.begin();
dht.setup(DHT_PIN,DHTesp::DHT22);
rtc.begin();
lcd.init(); lcd.backlight();
}
// ====== Loop ======
void loop(){
web.handleClient();
TempAndHumidity env = dht.getTempAndHumidity();
float t=env.temperature;
float h=env.humidity;
int soilRaw=analogRead(SOIL_PIN);
int soilPct=map(soilRaw,0,4095,100,0);
int phRaw=analogRead(PH_PIN);
float voltage=phRaw*(3.3/4095.0);
float phVal=7+((2.5-voltage)/0.18);
DateTime now=rtc.now();
String stamp=String(now.hour())+":"+String(now.minute())+":"+String(now.second());
String line="["+stamp+"] T:"+String(t,1)+"C H:"+String(h,1)+"% S:"+String(soilPct)+"% pH:"+String(phVal,2);
Serial.println(line);
lcd.clear();
lcd.setCursor(0,0); lcd.print("T:"+String(t,1)+" H:"+String(h,0));
lcd.setCursor(0,1); lcd.print("S:"+String(soilPct)+" pH:"+String(phVal,1));
// зберігаємо в історію
tHistory[idx]=t; hHistory[idx]=h; sHistory[idx]=soilPct; phHistory[idx]=phVal;
idx=(idx+1)%HISTORY;
fsm(soilPct,getFlow());
delay(1000);
if(once==0){
askAI("Based on this data, should irrigation run?\n"+line);
once++;
}
}