// Clean, full sketch for ESP8266 (NodeMCU) // - RCSwitch receiver on D5 // - RTC DS3231 on I2C (SDA=D2, SCL=D1) // - Optional SD on CS = D8 (GPIO15) // - Always prints received RF data to Serial // - Saves to SD every N minutes if SD present // - Cleans files older than M days every 12 hours // - getPageID(...) returns numeric page id; switch-case used // - STA + periodic sending to server // - API to set STA & send interval #include #include #include "RTClib.h" #include #include #include #include #include #include #include // ---------------- CONFIG ---------------- String apSSID = "ESP8266_AP"; String apPassword = "12345678"; WiFiServer server(80); // ---------------- HW ---------------- RTC_DS3231 rtc; bool rtcReady = false; RCSwitch rx; const uint8_t RX_PIN = D1; //-------------------------------------led---------------------------------- #define LED_PIN 2 #define NUM_LEDS 8 uint32_t lastLedState[NUM_LEDS] = {0}; // uint8_t currentLedIndex = 0; // LED فعلی برای ارسال // bool ledUpdateNeeded = false; uint32_t targetColors[NUM_LEDS]; // رنگ‌های هدف Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800); uint32_t On_color = strip.Color(0,255,127); uint32_t off_color = strip.Color(0, 0, 0); //uint32_t off_color = strip.Color(210, 105, 30); uint32_t false_color = strip.Color(255, 255, 0); uint32_t Ok_color = strip.Color(0,255,127); uint32_t Red_color = strip.Color(255,0,0); //----------------------------------------------------------------------- #define SD_CS_PIN D8 bool sdReady = false; const char* CONFIG_FILE = "config.txt"; const int buttonPin = 16; // D0 bool wifiEnabled = true; bool StartConnectToModem = false; bool buttonPressed = false; int lastButtonState = HIGH; unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; // ---------------- Protocol & storage ---------------- const String PRIVATE_KEY = "as23f"; const String device_1 = "dr142"; const String device_2 = "dv154"; uint8_t device1_recived=0; uint8_t device2_recived=0; //bool device2_recived=false; const String ALLOWED_DEVICES[] = {device_1,device_2}; const int NUM_ALLOWED = 2; #define MAX_DEVICES 16 struct DeviceData { String deviceId; int soil; int gas; float temp; float hum; String timestamp; }; DeviceData lastRemoteData[MAX_DEVICES]; int deviceCount = 0; // RX frame parsing String receivedKey = ""; String receivedHex = ""; bool receivingPublicKey = true; int receivedChars = 0; unsigned long lastReceiveTime = 0; const unsigned long FRAME_TIMEOUT_MS = 500; const int EXPECTED_PLAIN_LEN = 25; // ---------------- Settings (default) ---------------- unsigned long saveIntervalMinutes = 5; int retentionDays = 90; unsigned long lastSaveMillis = 0; unsigned long lastCleanupMillis = 0; unsigned long lastCheckDevice = 0; // ---------------- WiFi client (STA) ---------------- String staSSID = ""; String staPassword = ""; unsigned long lastSendMillis = 0; unsigned long sendIntervalMinutes = 1; // پیش فرض // ---------------- Helpers ---------------- bool isHexChar(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } bool isDeviceAllowed(const String &id) { for (int i=0;i= 8 && isDigit(name[0])) { int y = name.substring(0,4).toInt(); int m = name.substring(4,6).toInt(); int d = name.substring(6,8).toInt(); if (y > 2000 && m>=1 && m<=12 && d>=1 && d<=31) { DateTime fileDate(y,m,d,0,0,0); TimeSpan diff = now - fileDate; if (diff.days() > retentionDays) SD.remove(name); } } } entry.close(); entry = root.openNextFile(); } root.close(); } // ---------------- Remote receiver ---------------- void processRemote() { if (rx.available()) { unsigned long value = rx.getReceivedValue(); unsigned int bitlen = rx.getReceivedBitlength(); if (bitlen == 8) { char c = (char)value; if (receivingPublicKey) { if (receivedKey.length() < 5) receivedKey += c; if (receivedKey.length() == 5) receivingPublicKey = false; } else { if (isHexChar(c)) receivedHex += c; } receivedChars++; lastReceiveTime = millis(); } rx.resetAvailable(); } if (receivedChars >= 5 && (millis() - lastReceiveTime) > FRAME_TIMEOUT_MS) { if (receivedKey != PRIVATE_KEY || (receivedHex.length() % 2) != 0) { resetFrame(); return; } String decoded; decoded.reserve(receivedHex.length()/2); for (int i=0; i+1= saveIntervalMinutes*60000UL) { if (sdReady) { String j="{\"device\":\""+deviceId+"\",\"soil\":"+String(soil)+",\"gas\":"+String(gas)+ ",\"temp\":"+String(temp)+",\"hum\":"+String(hum)+",\"time\":\""+ts+"\"}"; appendDataToSD(j); Serial.println("[INFO] appended to SD"); } lastSaveMillis=millis(); } resetFrame(); } } // ---------------- Page mapping ---------------- int getPageID(const String &page) { if (page == "info") return 1; if (page == "sensor") return 2; if (page == "time") return 3; if (page == "settime") return 4; if (page == "lastremote") return 5; if (page == "readfile") return 6; if (page == "files") return 7; if (page == "format") return 8; if (page == "set_save_interval") return 9; if (page == "set_retention_days") return 10; if (page == "sd_status") return 11; return 0; } String urlDecode(const String &input) { String s = input; s.replace("+", " "); for (int i = 0; i + 2 < s.length(); ++i) { if (s[i] == '%') { String hx = s.substring(i+1, i+3); char c = (char) strtol(hx.c_str(), NULL, 16); s = s.substring(0,i) + String(c) + s.substring(i+3); } } return s; } String getParamFromPath(const String &path, const String &key) { int q = path.indexOf('?'); if (q == -1) return ""; String qstr = path.substring(q+1); int start = 0; while (start < qstr.length()) { int amp = qstr.indexOf('&', start); if (amp == -1) amp = qstr.length(); int eq = qstr.indexOf('=', start); if (eq != -1 && eq < amp) { String k = qstr.substring(start, eq); String v = qstr.substring(eq+1, amp); if (k == key) return urlDecode(v); } start = amp + 1; } return ""; } // ---------------- HTTP handler ---------------- void handleClient() { if (!wifiEnabled) return; WiFiClient client = server.available(); if (!client) return; String req = ""; unsigned long start = millis(); while (client.connected() && millis() - start < 1500) { while (client.available()) { char c = client.read(); req += c; if (req.endsWith("\r\n\r\n")) break; } if (req.endsWith("\r\n\r\n")) break; } if (req.length() == 0) return; int lineEnd = req.indexOf("\r\n"); String firstLine = (lineEnd == -1) ? req : req.substring(0, lineEnd); Serial.print("[HTTP] "); Serial.println(firstLine); String path = ""; int sp1 = firstLine.indexOf(' '); int sp2 = firstLine.indexOf(" HTTP/"); if (sp1 != -1 && sp2 != -1) path = firstLine.substring(sp1+1, sp2); String page = getParamFromPath(path,"page"); String response=""; // --- API: set STA --- if(page=="set_sta") { String ssid = getParamFromPath(path,"ssid"); String pass = getParamFromPath(path,"pass"); if(ssid.length()>0) { staSSID=ssid; staPassword=pass; StartConnectToModem=WiFi.begin(staSSID.c_str(),staPassword.c_str()); if(sdReady) saveConfigToSD(); response="{\"status\":\"ok\",\"message\":\"STA updated, connecting...\"}"; } else response="{\"status\":\"error\",\"message\":\"SSID missing\"}"; client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: application/json")); client.print(F("Content-Length: ")); client.println(response.length()); client.println(F("Connection: close")); client.println(); client.println(response); client.stop(); return; } // --- API: set send interval --- if(page=="set_send_interval") { String v = getParamFromPath(path,"minutes"); int m=v.toInt(); if(m>0) { sendIntervalMinutes=m; if(sdReady) saveConfigToSD(); response="{\"status\":\"ok\",\"send_interval_minutes\":"+String(m)+"}"; } else response="{\"status\":\"error\",\"message\":\"invalid minutes\"}"; client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: application/json")); client.print(F("Content-Length: ")); client.println(response.length()); client.println(F("Connection: close")); client.println(); client.println(response); client.stop(); return; } // --- old page switch --- int pid = getPageID(page); switch(pid) { case 1: response="{\"status\":\"ok\",\"data\":\"ESP ready,StartConnectToModem:"+String(StartConnectToModem)+",wifiEnabled:"+String(wifiEnabled)+"\"}"; break; case 2: response="{\"status\":\"ok\",\"sensor\":"+String(analogRead(A0))+"}"; break; case 3: response=rtcReady?"{\"status\":\"ok\",\"data\":\""+dateTimeToISO(rtc.now())+"\"}":"{\"status\":\"error\",\"message\":\"RTC not ready\"}"; break; case 4: { String iso=getParamFromPath(path,"iso"); if(!rtcReady){ response="{\"status\":\"error\",\"message\":\"RTC not ready\"}"; break; } if(iso.length()>=19){ int y=iso.substring(0,4).toInt(), m=iso.substring(5,7).toInt(), d=iso.substring(8,10).toInt(); int hh=iso.substring(11,13).toInt(), mm=iso.substring(14,16).toInt(), ss=iso.substring(17,19).toInt(); rtc.adjust(DateTime(y,m,d,hh,mm,ss)); response="{\"status\":\"ok\",\"data\":\""+dateTimeToISO(rtc.now())+"\"}"; } else response="{\"status\":\"error\",\"message\":\"invalid iso\"}"; break; } case 5: { String arr="["; for(int i=0;i 0) { content.remove(content.length() - 3); } f.close(); response = "{\"status\":\"ok\",\"content\":[" + content + "]}"; break; } case 7: { // files if (!sdReady) { response = "{\"status\":\"error\",\"message\":\"SD not ready\"}"; break; } File root = SD.open("/"); String arr = "["; bool first = true; File entry = root.openNextFile(); while (entry) { String name = entry.name(); if (name.length() && name[0] == '/') name = name.substring(1); if (!first) arr += ","; arr += "\"" + name + "\""; first = false; entry.close(); entry = root.openNextFile(); } root.close(); arr += "]"; response = "{\"status\":\"ok\",\"files\":" + arr + "}"; break; } case 8: { // format (remove all files except config) if (!sdReady) { response = "{\"status\":\"error\",\"message\":\"SD not ready\"}"; break; } File root = SD.open("/"); File entry = root.openNextFile(); while (entry) { String name = entry.name(); if (name.length() && name[0] == '/') name = name.substring(1); if (name != String(CONFIG_FILE) && !entry.isDirectory()) SD.remove(name); entry.close(); entry = root.openNextFile(); } root.close(); response = "{\"status\":\"ok\",\"message\":\"formatted (config preserved)\"}"; break; } case 9: { // set_save_interval?min=NUM String v = getParamFromPath(path, "min"); int m = v.toInt(); if (m <= 0) response = "{\"status\":\"error\",\"message\":\"invalid min\"}"; else { saveIntervalMinutes = (unsigned long)m; if (sdReady) saveConfigToSD(); response = "{\"status\":\"ok\",\"save_interval_minutes\":" + String(m) + "}"; } break; } case 10: { // set_retention_days?days=NUM String v = getParamFromPath(path, "days"); int d = v.toInt(); if (d <= 0) response = "{\"status\":\"error\",\"message\":\"invalid days\"}"; else { retentionDays = d; if (sdReady) saveConfigToSD(); response = "{\"status\":\"ok\",\"retention_days\":" + String(d) + "}"; } break; } case 11: { // sd_status if (!sdReady) response = "{\"status\":\"error\",\"message\":\"SD not ready\"}"; else { uint64_t used = computeSDUsedBytes(); //total/free not reliably available via SD.h on ESP8266 response = "{\"status\":\"ok\",\"used_bytes\":" + String((unsigned long)used) + "}"; } break; } default: response = page.length()==0?"{\"status\":\"error\",\"message\":\"missing page\"}":"{\"status\":\"error\",\"message\":\"unknown page\"}"; break; } client.println(F("HTTP/1.1 200 OK")); client.println(F("Content-Type: application/json")); client.print(F("Content-Length: ")); client.println(response.length()); client.println(F("Connection: close")); client.println(); client.println(response); delay(5); client.stop(); Serial.println("[HTTP] client disconnected"); } time_t parseTimestamp(String ts) { int year = ts.substring(0, 4).toInt(); int month = ts.substring(5, 7).toInt(); int day = ts.substring(8, 10).toInt(); int hour = ts.substring(11, 13).toInt(); int min = ts.substring(14, 16).toInt(); int sec = ts.substring(17, 19).toInt(); tmElements_t tm; tm.Year = year - 1970; tm.Month = month; tm.Day = day; tm.Hour = hour; tm.Minute = min; tm.Second = sec; return makeTime(tm); } void checkDeviceData() { time_t now = rtc.now().unixtime(); int device1Index=-1; int device2Index=-1; for (int i = 0; i < MAX_DEVICES; i++) { if(i 0) { time_t t = parseTimestamp(lastRemoteData[i].timestamp); long diff = now - t; // اختلاف زمان به ثانیه if (diff < 60) { device1_recived=2; Serial.println(lastRemoteData[i].deviceId + " updated less than 60 seconds ago"); } else { device1_recived=1; Serial.println(lastRemoteData[i].deviceId + "--- updated more than 60 seconds ago"); } } else{ device1_recived=1; Serial.println(lastRemoteData[i].deviceId + "--- noDataRecive updated more than 60 seconds ago"); } } if(device2Index==-1) { device2_recived=0;//دریافتی از دستگاه 2 نداشتیم Serial.println(device_2 + " not exist"); } else { int i=device2Index; if (lastRemoteData[i].timestamp.length() > 0) { time_t t = parseTimestamp(lastRemoteData[i].timestamp); long diff = now - t; // اختلاف زمان به ثانیه if (diff < 60) { device2_recived=2; Serial.println(lastRemoteData[i].deviceId + " updated less than 60 seconds ago"); } else { device2_recived=1; Serial.println(lastRemoteData[i].deviceId + "--- updated more than 60 seconds ago"); } } else{ device1_recived=1; Serial.println(lastRemoteData[i].deviceId + "--- noDataRecive updated more than 60 seconds ago"); } } } void setLed(uint8_t idx, bool condition, uint32_t colorTrue, uint32_t colorFalse) { if (condition) { strip.setPixelColor(idx, colorTrue); } else { strip.setPixelColor(idx, colorFalse); } } // ---------------- Setup & Loop ---------------- void startEsp() { } void setup() { Serial.begin(115200); delay(100); //--------------------------------Start Led--------------------------------------------------------- strip.begin(); strip.show(); //چراغ اول روشن شود. یعنی دستگاه روشن است //setLed(0, true, On_color, On_color); //strip.show(); //----------------------------------------------------------------------------------------- //-------------------------------------Button Config---------------------------------------------------- pinMode(buttonPin, INPUT_PULLUP); //----------------------------------------------------------------------------------------- //------------------------------------Start Clock----------------------------------------------------- Wire.begin(D2,D3); rtcReady=rtc.begin(); if(rtcReady && rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); //----------------------------------------------------------------------------------------- //--------------------------------------Start Sdk--------------------------------------------------- rx.enableReceive(digitalPinToInterrupt(RX_PIN)); rx.setProtocol(1); rx.setPulseLength(300); Serial.println("[INFO] RCSwitch enabled on D1"); //----------------------------------------------------------------------------------------- //-----------------------------------------Start SD Card------------------------------------------------ sdReady=SD.begin(SD_CS_PIN); if(sdReady){ Serial.println("[INFO] SD ready"); loadConfigFromSD(); } else Serial.println("[WARN] SD init failed"); //----------------------------------------------------------------------------------------- //-----------------------------------Start ESP------------------------------------------------------ if(wifiEnabled){ WiFi.softAP(apSSID,apPassword,6); server.begin(); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); } if(staSSID.length()>0){ StartConnectToModem=WiFi.begin(staSSID.c_str(),staPassword.c_str()); } //----------------------------------------------------------------------------------------- lastSaveMillis=millis(); lastCleanupMillis=millis(); lastSendMillis=millis();lastCheckDevice=millis(); } void CheckWifiBtn() { // ---------------- Button handling (toggle AP) ---------------- int reading = digitalRead(buttonPin); if (reading != lastButtonState) lastDebounceTime = millis(); if ((millis() - lastDebounceTime) > debounceDelay) { if (reading == LOW && !buttonPressed) { buttonPressed = true; wifiEnabled = !wifiEnabled; if (wifiEnabled) { WiFi.mode(WIFI_AP_STA); WiFi.softAP(apSSID, apPassword,6); server.begin(); Serial.println("✅ AP enabled"); Serial.print("IP: "); Serial.println(WiFi.softAPIP()); } else { WiFi.softAPdisconnect(true); WiFi.mode(WIFI_STA); Serial.println("❌ AP disabled"); } } if (reading == HIGH) buttonPressed = false; } lastButtonState = reading; } void SendDataToServer() { if (deviceCount > 0 && sendIntervalMinutes > 0) { if (lastSendMillis == 0) lastSendMillis = millis(); if ((millis() - lastSendMillis) >= sendIntervalMinutes * 60000UL) { Serial.println("[DEBUG] Entering send block"); Serial.print("[DEBUG] WiFi.status: "); Serial.println(WiFi.status()); if (WiFi.status() == WL_CONNECTED) { // Prepare JSON payload String payload = "["; for (int i = 0; i < deviceCount; i++) { if (i) payload += ","; DeviceData &d = lastRemoteData[i]; payload += "{\"device\":\"" + d.deviceId + "\",\"soil\":" + String(d.soil) + ",\"gas\":" + String(d.gas) + ",\"temp\":" + String(d.temp) + ",\"hum\":" + String(d.hum) + ",\"time\":\"" + d.timestamp + "\"}"; } payload += "]"; // Use WiFiClientSecure for HTTPS WiFiClientSecure client; client.setInsecure(); // SSL certificate not verified HTTPClient http; // Use URL with www if nabaksoft.ir ریدایرکت می‌کند String url = "https://www.nabaksoft.ir/greenhome/mygreenhome.php?aid="+PRIVATE_KEY+"&data=" + payload; http.begin(client, url); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); // Follow redirects automatically Serial.println("[HTTP] Sending data to server..."); int httpCode = http.GET(); if (httpCode > 0) { Serial.printf("[HTTP] Response code: %d\n", httpCode); String resp = http.getString(); Serial.printf("[HTTP] Server response: %s\n", resp.c_str()); } else { Serial.printf("[HTTP] Send failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); } else { Serial.println("[HTTP] WiFi not connected, skipping send"); } lastSendMillis = millis(); } } } void SetLedStates() { //دریافت کننده فعال است؟ /*setLed(1, rtcReady, Ok_color, off_color); //کارتخوان فعال است؟ setLed(2, sdReady, Ok_color, off_color); //wifi فعال است؟ setLed(3, wifiEnabled, Ok_color, off_color); //مودم فعال است یا خیر و اگر فعال است متصل است؟ if(staSSID.length()>0) { //اگر رمز دارد پس باید متصل بشه.اگر شده که سبز. اگر نشده قرمز setLed(4, WiFi.status()==WL_CONNECTED, Ok_color, false_color); } else setLed(4, true, off_color, off_color); setLed(5, true, off_color, off_color); setLed(6, true, off_color, off_color); setLed(7, true, off_color, off_color); //شدت روشنایی strip.setBrightness(20); strip.show();*/ int32_t colors[NUM_LEDS]; colors[0] = On_color; // دستگاه روشن colors[1] = rtcReady ? Ok_color : off_color; // وضعیت RTC colors[2] = sdReady ? Ok_color : off_color; // وضعیت SD colors[3] = wifiEnabled ? Ok_color : off_color; // وضعیت AP colors[4] = (staSSID.length() > 0 && WiFi.status() == WL_CONNECTED) ? Ok_color : false_color; // STA colors[5] = (device1_recived==2)?Ok_color:(device1_recived==1?false_color:Red_color); colors[6] = (device2_recived == 2)?Ok_color:(device2_recived==1?false_color:Red_color); colors[7] = off_color; // بررسی اینکه آیا تغییری نسبت به وضعیت قبلی وجود دارد bool changed = false; for (int i = 0; i < NUM_LEDS; i++) { if (colors[i] != lastLedState[i]) { lastLedState[i] = colors[i]; strip.setPixelColor(i, colors[i]); changed = true; } } // فقط وقتی تغییر بوده، strip.show() اجرا شود if (changed) { strip.setBrightness(20); strip.show(); // این تابع اکنون به حداقل فراخوانی کاهش یافته } } void loop() { CheckWifiBtn(); // ---------------- HTTP server handling ---------------- handleClient(); // ---------------- Process RF data ---------------- processRemote(); //---------------- Periodic cleanup every 12 hours ---------------- if ((millis() - lastCleanupMillis) >= (12UL * 60UL * 60UL * 1000UL)) { cleanupOldFilesOnSD(); lastCleanupMillis = millis(); } //هر یک دقیقه چک کنه وضعیت دو فرستنده چطور هست if ((millis() - lastCheckDevice) >= (60UL * 1000UL)) { checkDeviceData(); lastCheckDevice = millis(); } // ---------------- Periodic send to server ---------------- SendDataToServer(); SetLedStates(); } // if (wifiEnabled) { // WiFiClient client = server.available(); // if (client) handleClient(client); // }