#include <Arduino.h>
#include <EEPROM.h>
#include <WiFi.h>
#include <time.h>
#include <sntp.h>
// Pin definitions
#define LED_PIN 13 // LED representing solenoid valve
#define MOISTURE_PIN 34 // Potentiometer for soil moisture simulation
#define BUTTON_PIN 33 // Button
// EEPROM addresses and settings
#define EEPROM_SIZE 16
#define THRESHOLD_ADDR 0 // Address to store threshold
#define BOOTCOUNT_ADDR sizeof(int)
// Node ID (unique identifier for the device)
#define NODE_ID "ESP32_NODE_001"
// Base timestamp (Unix timestamp for August 11, 2025, 00:00:00 UTC)
#define BASE_TIMESTAMP 1754870400LL // Use long long for large numbers
// Variables
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR float lastMoisture = 0.0;
int moistureThreshold = 30; // Default threshold (will be loaded from EEPROM)
// Wifi rleated variables
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const char* time_zone = "IST-5:30"; // IST time zone
// soil moisture Percentage
float readSoilMoisture() {
// Read analog value
int analogValue = analogRead(MOISTURE_PIN);
// Convert (0–4095 from ESP32 ADC) to percentage (0–100%)
float moisture = map(analogValue, 0, 4095, 0, 100);
return moisture;
}
// Control LED based on moisture level
void controlActuator(float moisture) {
if (moisture < moistureThreshold) {
digitalWrite(LED_PIN, HIGH); // Turn ON LED (solenoid)
Serial.println("LED ON: Moisture below threshold");
} else {
digitalWrite(LED_PIN, LOW); // Turn OFF LED
Serial.println("LED OFF: Moisture above threshold");
}
}
unsigned long long getTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
return 1754870400LL + (bootCount * 60LL);
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
return mktime(&timeinfo);
}
void connectWifiSetNTP() {
// sntp_set_time_sync_notification_cb(timeavailable);
Serial.print("Connecting to WiFi");
WiFi.begin(ssid, password, 6);
unsigned long timeout = millis() + 2000; // 2-second timeout
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
if (millis() > timeout) {
Serial.println(" Not connected");
return;
}
}
Serial.println(" Connected!");
configTime(0, 0, ntpServer1, ntpServer2);
setenv("TZ", time_zone, 1);
tzset();
return;
}
uint8_t calculateCRC(const char* data) {
uint8_t crc = 0;
while (*data) {
crc ^= *data++;
}
return crc;
}
// Function to simulate LoRaWAN payload as JSON with CRC
void simulateLoRaWANPayload(float moisture) {
// Calculate simulated timestamp (base + bootCount * 60 seconds)
unsigned long long timestamp = getTime();
// Format JSON payload
char jsonPayload[128];
sprintf(jsonPayload, "{\"node_id\":\"%s\",\"timestamp\":%llu,\"moisture\":%.1f}", NODE_ID, timestamp, moisture);
// Calculate CRC on JSON string
uint8_t crc = calculateCRC(jsonPayload);
// Print JSON payload
Serial.print("LoRaWAN Payload (JSON): ");
Serial.println(jsonPayload);
// Print CRC
Serial.print("CRC: 0x");
if (crc < 16) Serial.print("0");
Serial.println(crc, HEX);
}
// Function to configure threshold via Serial (if button pressed)
void configureThreshold() {
Serial.println("Entering threshold configuration mode (button pressed).");
Serial.println("Current threshold: " + String(moistureThreshold) + "%");
Serial.println("Enter new threshold (0-100) or 'exit' to skip:");
String input = "";
unsigned long timeout = millis() + 10000; // 10-second timeout
while (millis() < timeout) {
if (Serial.available()) {
input = Serial.readStringUntil('\n');
input.trim();
if (input == "exit") {
Serial.println("Exiting config mode without changes.");
return;
}
int newThreshold = input.toInt();
if (newThreshold >= 0 && newThreshold <= 100) {
moistureThreshold = newThreshold;
EEPROM.write(THRESHOLD_ADDR, moistureThreshold);
EEPROM.commit();
Serial.println("New threshold saved: " + String(moistureThreshold) + "%");
return;
} else {
Serial.println("Invalid input. Try again.");
}
}
}
Serial.println("Config timeout. No changes made.");
}
void setup() {
// Initialize Serial for debugging
Serial.begin(115200);
delay(100); // Stabilize Serial
Serial.println("\nSmart Irrigation Node - Boot #" + String(++bootCount));
// Connect wifi and set NTP
connectWifiSetNTP();
// Initialize EEPROM
EEPROM.begin(EEPROM_SIZE);
moistureThreshold = EEPROM.read(THRESHOLD_ADDR); // Load threshold from EEPROM
if (moistureThreshold < 0 || moistureThreshold > 100) {
moistureThreshold = 30; // Reset to default if invalid
EEPROM.write(THRESHOLD_ADDR, moistureThreshold);
EEPROM.commit();
}
Serial.println("Loaded threshold from EEPROM: " + String(moistureThreshold) + "%");
// Initialize pins
pinMode(LED_PIN, OUTPUT);
pinMode(MOISTURE_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP); // Button with pull-up
// Check if wake-up was due to button (ext0) and enter config if pressed
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0 || digitalRead(BUTTON_PIN) == LOW) {
configureThreshold();
}
// Read soil moisture
float moisture = readSoilMoisture();
Serial.print("Soil Moisture: ");
Serial.print(moisture);
Serial.println("%");
// Control actuator (LED)
controlActuator(moisture);
// Simulate LoRaWAN payload as JSON
simulateLoRaWANPayload(moisture);
Serial.println("Entering deep sleep for 1 minute (or button press)...");
Serial.flush();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
// Small delay before entering the deep sleep.
// Configure deep sleep
esp_sleep_enable_timer_wakeup(60 * 1000000ULL); // 1 minute
// esp_deep_sleep_enable_gpio_wakeup(1ULL << BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
esp_sleep_enable_ext0_wakeup((gpio_num_t)BUTTON_PIN, 0); // Wake on button low (pressed)
delay(1000);
esp_deep_sleep_start(); // Enter deep sleep
}
void loop() {
}