/* KFS Smart Farm — field node (ESP32) — HTTP version (sessions 9-12, 17-21) Sensors : DHT22 (GPIO 4), capacitive soil moisture v1.2 (GPIO 34), LDR divider (GPIO 35), battery divider (GPIO 33) Actuator: relay module IN1 (GPIO 26) -> irrigation pump, IN2 (GPIO 27) -> greenhouse fan (active LOW modules) Every 30 s: POST /api/v1/readings then GET /api/v1/commands/pending -> drive relays -> ACK Libraries: "DHT sensor library" (Adafruit), "ArduinoJson" v7 */ #include #include #include #include // ---------- edit for your farm ---------- const char* WIFI_SSID = "KFS-Farm-IoT"; const char* WIFI_PASS = "change-me"; const char* SERVER = "http://192.168.1.10:5000"; // laptop / Raspberry Pi running app.py const char* API_KEY = "kfs-farm-node-key"; const char* NODE_ID = "field_A"; const int SOIL_DRY = 3200; // ADC in dry air (calibrate! session 2) const int SOIL_WET = 1300; // ADC in a cup of water // ----------------------------------------- #define DHT_PIN 4 #define SOIL_PIN 34 #define LDR_PIN 35 #define BAT_PIN 33 #define PUMP_PIN 26 #define FAN_PIN 27 #define PUMP_MAX_MS (30UL * 60UL * 1000UL) // hardware safety: never run the pump > 30 min DHT dht(DHT_PIN, DHT22); unsigned long lastSend = 0, pumpOffAt = 0, fanOffAt = 0; int readSoilAdc() { // median of 9 samples -> rejects spikes (session 3) int v[9]; for (int i = 0; i < 9; i++) { v[i] = analogRead(SOIL_PIN); delay(5); } for (int i = 1; i < 9; i++) { int k = v[i], j = i - 1; while (j >= 0 && v[j] > k) { v[j + 1] = v[j]; j--; } v[j + 1] = k; } return v[4]; } float soilPercent(int adc) { float p = (float)(SOIL_DRY - adc) / (SOIL_DRY - SOIL_WET) * 100.0; return constrain(p, 0, 100); } void relay(int pin, bool on) { digitalWrite(pin, on ? LOW : HIGH); } // active-LOW relay board void connectWiFi() { if (WiFi.status() == WL_CONNECTED) return; WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); for (int i = 0; i < 40 && WiFi.status() != WL_CONNECTED; i++) delay(250); Serial.printf("WiFi %s IP=%s RSSI=%d\n", WiFi.status() == WL_CONNECTED ? "OK" : "FAIL", WiFi.localIP().toString().c_str(), WiFi.RSSI()); } void sendReading() { float t = dht.readTemperature(), h = dht.readHumidity(); int soil = readSoilAdc(); JsonDocument doc; doc["node_id"] = NODE_ID; if (!isnan(t)) doc["air_temp_c"] = t; // missing value -> field omitted (server stores NULL) if (!isnan(h)) doc["air_humidity_pct"] = h; doc["soil_moisture_pct"] = soilPercent(soil); doc["light"] = analogRead(LDR_PIN); doc["battery_v"] = analogReadMilliVolts(BAT_PIN) * 2 / 1000.0; // 100k/100k divider doc["rssi"] = WiFi.RSSI(); doc["fw"] = "1.3.0"; String body; serializeJson(doc, body); HTTPClient http; http.begin(String(SERVER) + "/api/v1/readings"); http.addHeader("Content-Type", "application/json"); http.addHeader("X-API-Key", API_KEY); int code = http.POST(body); Serial.printf("POST %d %s\n", code, body.c_str()); http.end(); } void pollCommands() { HTTPClient http; http.begin(String(SERVER) + "/api/v1/commands/pending?node_id=" + NODE_ID); http.addHeader("X-API-Key", API_KEY); if (http.GET() == 200) { JsonDocument doc; deserializeJson(doc, http.getString()); for (JsonObject c : doc["commands"].as()) { bool on = String((const char*)c["action"]) == "on"; unsigned long ms = min((unsigned long)c["duration_min"].as() * 60000UL, PUMP_MAX_MS); if (String((const char*)c["actuator"]) == "pump") { relay(PUMP_PIN, on); pumpOffAt = on ? millis() + ms : 0; } else { relay(FAN_PIN, on); fanOffAt = on ? millis() + ms : 0; } Serial.printf("CMD #%d %s %s\n", (int)c["id"], (const char*)c["actuator"], (const char*)c["action"]); HTTPClient ack; ack.begin(String(SERVER) + "/api/v1/commands/" + String((int)c["id"]) + "/ack"); ack.addHeader("X-API-Key", API_KEY); ack.POST("{}"); ack.end(); } } http.end(); } void setup() { Serial.begin(115200); pinMode(PUMP_PIN, OUTPUT); pinMode(FAN_PIN, OUTPUT); relay(PUMP_PIN, false); relay(FAN_PIN, false); // fail-safe: everything OFF at boot analogReadResolution(12); dht.begin(); connectWiFi(); } void loop() { if (pumpOffAt && millis() > pumpOffAt) { relay(PUMP_PIN, false); pumpOffAt = 0; Serial.println("pump auto-off"); } if (fanOffAt && millis() > fanOffAt) { relay(FAN_PIN, false); fanOffAt = 0; } if (millis() - lastSend > 30000 || lastSend == 0) { lastSend = millis(); connectWiFi(); if (WiFi.status() == WL_CONNECTED) { sendReading(); pollCommands(); } else { relay(PUMP_PIN, false); } // lost the server -> stop irrigating } }