// 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 #include #include #include "RTClib.h" #include #include #include #include // ---------------- CONFIG ---------------- const char* ssid = "ESP8266_AP"; const char* password = "12345678"; WiFiServer server(80); // ---------------- HW ---------------- #define LED_PIN 1 // TX = GPIO1 #define NUM_LEDS 8 Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800); RTC_DS3231 rtc; bool rtcReady = false; RCSwitch rx; // RCSwitch for RXB45 const uint8_t RX_PIN = D1; #define SD_CS_PIN D8 // safe for boot (GPIO15) const int buttonPin = 16; // D0 bool sdReady = false; const char* CONFIG_FILE = "config.txt"; bool wifiEnabled = true; // وضعیت WiFi bool buttonPressed = false; // وضعیت فشار داده شده int lastButtonState = HIGH; unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; // 200ms برای حذف لرزش // ---------------- Protocol & storage ---------------- const String PRIVATE_KEY = "as23f"; // shared key const String ALLOWED_DEVICES[] = {"dr142","abcde","fghij"}; const int NUM_ALLOWED = 3; #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 (same logic as earlier RCSwitch-based receiver) 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; // minutes (configurable) int retentionDays = 90; // days (configurable) unsigned long lastSaveMillis = 0; unsigned long lastCleanupMillis = 0; // ---------------- 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 (RCSwitch pulses) ---------------- void processRemote() { bool rxa=rx.available(); //Serial.println("rxa:"+rxa); if (rxa) { unsigned long value = rx.getReceivedValue(); unsigned int bitlen = rx.getReceivedBitlength(); // debug to ensure we see raw values // Serial.print(F("[DEBUG] got value=")); // Serial.print(value); // Serial.print(F(" bits=")); // Serial.println(bitlen); if (bitlen == 8) { char c = (char)value; if (receivingPublicKey) { if (receivedKey.length() < 5) receivedKey += c; if (receivedKey.length() == 5) { receivingPublicKey = false; Serial.println(F("[DEBUG] public key received -> switching to HEX")); } } else { if (isHexChar(c)) receivedHex += c; else { // noise Serial.print(F("[DEBUG] ignored non-hex char: 0x")); Serial.println((int)(uint8_t)c, HEX); } } receivedChars++; lastReceiveTime = millis(); } rx.resetAvailable(); } // when frame timeout -> process if (receivedChars >= 5 && (millis() - lastReceiveTime) > FRAME_TIMEOUT_MS) { Serial.println(F("[DEBUG] frame timeout -> processing...")); if (receivedKey != PRIVATE_KEY) { Serial.println(F("[WARN] key mismatch")); resetFrame(); return; } if ((receivedHex.length() % 2) != 0) { Serial.println(F("[WARN] odd hex length")); resetFrame(); return; } // hex -> bytes String decoded; decoded.reserve(receivedHex.length()/2); for (int i=0; i+1 < receivedHex.length(); i+=2) { char hex2[3] = { receivedHex[i], receivedHex[i+1], '\0' }; char b = (char) strtol(hex2, NULL, 16); decoded += b; } if (decoded.length() != EXPECTED_PLAIN_LEN) { Serial.print(F("[WARN] decoded length != expected: ")); Serial.println(decoded.length()); resetFrame(); return; } String plain = decryptData(decoded, PRIVATE_KEY); // check format markers (positions 5,10,15,20 should be S,G,T,H) if (plain.length() != EXPECTED_PLAIN_LEN || plain[5] != 'S' || plain[10] != 'G' || plain[15] != 'T' || plain[20] != 'H') { Serial.println(F("[WARN] format check failed")); resetFrame(); return; } String deviceId = plain.substring(0,5); if (!isDeviceAllowed(deviceId)) { Serial.print(F("[WARN] device not allowed: ")); Serial.println(deviceId); resetFrame(); return; } // parse values int soil = plain.substring(6,10).toInt(); int gas = plain.substring(11,15).toInt(); float temp = plain.substring(16,20).toInt() / 10.0; float hum = plain.substring(21,25).toInt() / 10.0; String ts; if (rtcReady) ts = dateTimeToISO(rtc.now()); else ts = String(millis()); // PRINT ALWAYS Serial.print(F("[RX] device=")); Serial.print(deviceId); Serial.print(F(" soil=")); Serial.print(soil); Serial.print(F(" gas=")); Serial.print(gas); Serial.print(F(" temp=")); Serial.print(temp); Serial.print(F(" hum=")); Serial.print(hum); Serial.print(F(" time=")); Serial.println(ts); // update array (replace if exists else append) bool updated = false; for (int i=0;i= (saveIntervalMinutes * 60000UL)) { if (sdReady) { // prepare JSON line String j = "{\"device\":\"" + deviceId + "\",\"soil\":" + String(soil) + ",\"gas\":" + String(gas) + ",\"temp\":" + String(temp) + ",\"hum\":" + String(hum) + ",\"time\":\"" + ts + "\"}"; appendDataToSD(j); Serial.println(F("[INFO] appended to SD")); } else { Serial.println(F("[INFO] SD not ready, skipped save")); } lastSaveMillis = millis(); } resetFrame(); } } // ---------------- Page mapping to integer (user requested) ---------------- 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; } // URL-decode (simple) 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(WiFiClient &client) { 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(F("[HTTP] ")); Serial.println(firstLine); // extract path, e.g. GET /?page=info HTTP/1.1 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"); int pid = getPageID(page); String response = ""; switch (pid) { case 1: // info response = "{\"status\":\"ok\",\"data\":\"ESP ready\"}"; break; case 2: // sensor (A0) response = "{\"status\":\"ok\",\"sensor\":" + String(analogRead(A0)) + "}"; break; case 3: // time if (!rtcReady) response = "{\"status\":\"error\",\"message\":\"RTC not ready\"}"; else response = "{\"status\":\"ok\",\"datetime\":\"" + dateTimeToISO(rtc.now()) + "\"}"; break; case 4: { // settime (iso) 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\",\"datetime\":\"" + dateTimeToISO(rtc.now()) + "\"}"; } else response = "{\"status\":\"error\",\"message\":\"invalid iso\"}"; break; } case 5: { // lastremote 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: if (page.length() == 0) response = "{\"status\":\"error\",\"message\":\"missing page\"}"; else response = "{\"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(F("[HTTP] client disconnected")); } // ---------------- Setup & Loop ---------------- void setup() { Serial.begin(115200); delay(2000); //led strip.begin(); strip.show(); //چراغ اول روشن شود. یعنی دستگاه روشن است setLed(0, true, strip.Color(0,255,0), strip.Color(0,0,0)); pinMode(buttonPin, INPUT_PULLUP); // کلید به GND وصل میشه Wire.begin(D2, D3); rtcReady = rtc.begin(); if (!rtcReady) Serial.println(F("RTC not found")); else if (rtc.lostPower()) { Serial.println(F("RTC lost power — setting to compile time")); rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } // RCSwitch init rx.enableReceive(digitalPinToInterrupt(RX_PIN)); // use interrupt on D1 rx.setProtocol(1); rx.setPulseLength(300); Serial.println(F("[INFO] RCSwitch receiver enabled on D5")); // SD init sdReady = SD.begin(SD_CS_PIN); if (sdReady) { Serial.println("[INFO] SD ready"); //loadConfigFromSD(); } else { Serial.println("[WARN] SD init failed — will continue without SD"); } // WiFi AP + server //WiFi.softAP(ssid, password); //server.begin(); if (wifiEnabled) { WiFi.softAP(ssid, password); server.begin(); Serial.print(F("AP: ")); Serial.println(ssid); Serial.print(F("IP: ")); Serial.println(WiFi.softAPIP()); } lastSaveMillis = millis(); lastCleanupMillis = millis(); } void loop() { int reading = digitalRead(buttonPin); if (reading != lastButtonState) { lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { // تشخیص لبه فشار واقعی (HIGH → LOW) if (reading == LOW && !buttonPressed) { buttonPressed = true; // علامتگذاری فشار داده شده wifiEnabled = !wifiEnabled; // تغییر وضعیت AP if (wifiEnabled) { WiFi.mode(WIFI_AP_STA); WiFi.softAP("ESP-Server", "12345678"); Serial.println("✅ سرور (AP) فعال شد"); Serial.print("IP سرور: "); Serial.println(WiFi.softAPIP()); } else { WiFi.softAPdisconnect(true); WiFi.mode(WIFI_STA); Serial.println("❌ سرور (AP) غیرفعال شد"); } } // وقتی دکمه رها شد، آماده فشار بعدی if (reading == HIGH) { buttonPressed = false; } } lastButtonState = reading; // int buttonState = digitalRead(buttonPin); // if (buttonState == LOW && (millis() - lastDebounceTime) > debounceDelay) { // lastDebounceTime = millis(); // wifiEnabled = !wifiEnabled; // تغییر وضعیت سرور // if (wifiEnabled) { // WiFi.mode(WIFI_AP_STA); // مودم + AP // WiFi.softAP("ESP-Server", "12345678"); // روشن کردن سرور // Serial.println("✅ سرور (AP) فعال شد"); // Serial.print("IP سرور: "); // Serial.println(WiFi.softAPIP()); // } else { // WiFi.softAPdisconnect(true); // خاموش کردن AP // WiFi.mode(WIFI_STA); // فقط مودم // Serial.println("❌ سرور (AP) غیرفعال شد"); // } // } if (wifiEnabled) { WiFiClient client = server.available(); if (client) handleClient(client); } processRemote(); // WiFiClient client = server.available(); //if (client) handleClient(client); // periodic cleanup every 12 hours if ((millis() - lastCleanupMillis) >= (12UL * 60UL * 60UL * 1000UL)) { //cleanupOldFilesOnSD(); lastCleanupMillis = millis(); } //---------------leds--------------------------------- //دریافت کننده فعال است؟ setLed(0, rtcReady, strip.Color(0,255,0), strip.Color(255,0,0)); //کارتخوان فعال است؟ setLed(0, sdReady, strip.Color(0,255,0), strip.Color(255,0,0)); //wifi فعال است؟ setLed(0, wifiEnabled, strip.Color(0,255,0), strip.Color(0,0,0)); //مودم فعال است یا خیر و اگر فعال است متصل است؟ setLed(0, wifiEnabled, strip.Color(0,255,0), strip.Color(0,0,0)); //---------------leds--------------------------------- }