716 lines
22 KiB
C++
716 lines
22 KiB
C++
// 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 <ESP8266WiFi.h>
|
|
#include <Wire.h>
|
|
#include "RTClib.h"
|
|
#include <RCSwitch.h>
|
|
#include <SD.h>
|
|
#include <SPI.h>
|
|
#include <Adafruit_NeoPixel.h>
|
|
// ---------------- 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<NUM_ALLOWED;i++) if (id == ALLOWED_DEVICES[i]) return true;
|
|
return false;
|
|
}
|
|
|
|
void resetFrame() {
|
|
receivedKey = "";
|
|
receivedHex = "";
|
|
receivingPublicKey = true;
|
|
receivedChars = 0;
|
|
}
|
|
//-----------------------------------
|
|
void setLed(uint8_t idx, bool condition, uint32_t colorTrue, uint32_t colorFalse) {
|
|
if (condition) {
|
|
strip.setPixelColor(idx, colorTrue);
|
|
} else {
|
|
strip.setPixelColor(idx, colorFalse);
|
|
}
|
|
}
|
|
//-----------------------------------
|
|
|
|
String dateTimeToISO(const DateTime &dt) {
|
|
char buf[25];
|
|
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d",
|
|
dt.year(), dt.month(), dt.day(),
|
|
dt.hour(), dt.minute(), dt.second());
|
|
return String(buf);
|
|
}
|
|
|
|
String safeFileNameForNow() {
|
|
if (rtcReady) {
|
|
DateTime now = rtc.now();
|
|
char fn[20];
|
|
sprintf(fn, "%04d%02d%02d.txt", now.year(), now.month(), now.day());
|
|
return String(fn);
|
|
} else {
|
|
unsigned long s = millis() / 1000UL;
|
|
return String("t") + String(s) + ".txt";
|
|
}
|
|
}
|
|
|
|
// XOR decrypt (same as earlier)
|
|
String decryptData(const String &data, const String &key) {
|
|
String out;
|
|
out.reserve(data.length());
|
|
for (int i=0;i<data.length();++i) out += (char)(data[i] ^ key[i % key.length()]);
|
|
return out;
|
|
}
|
|
|
|
// ---------------- SD / config helpers ----------------
|
|
void loadConfigFromSD() {
|
|
if (!sdReady) return;
|
|
Serial.println("InloadConfigFromSD1");
|
|
File f = SD.open(CONFIG_FILE, "r");
|
|
if (!f) return;
|
|
while (f.available()) {
|
|
Serial.println("InloadConfigFromSD1.1");
|
|
String line = f.readStringUntil('\n');
|
|
line.trim();
|
|
if (line.startsWith("save_interval_minutes=")) {
|
|
saveIntervalMinutes = (unsigned long) line.substring(22).toInt();
|
|
if (saveIntervalMinutes == 0) saveIntervalMinutes = 5;
|
|
} else if (line.startsWith("retention_days=")) {
|
|
retentionDays = line.substring(15).toInt();
|
|
if (retentionDays == 0) retentionDays = 90;
|
|
}
|
|
}
|
|
Serial.println("InloadConfigFromSD2");
|
|
|
|
f.close();
|
|
Serial.println("InloadConfigFromSD3");
|
|
}
|
|
|
|
void saveConfigToSD() {
|
|
if (!sdReady) return;
|
|
File f = SD.open(CONFIG_FILE, "w");
|
|
if (!f) return;
|
|
f.print("save_interval_minutes=");
|
|
f.println(saveIntervalMinutes);
|
|
f.print("retention_days=");
|
|
f.println(retentionDays);
|
|
f.close();
|
|
}
|
|
|
|
// // append data lines to today's file
|
|
void appendDataToSD(const String &line) {
|
|
if (!sdReady) return;
|
|
String fname = safeFileNameForNow();
|
|
File f = SD.open(fname, FILE_WRITE);
|
|
if (!f) {
|
|
// try open with "a" string if FILE_WRITE macro missing
|
|
f = SD.open(fname, "a");
|
|
if (!f) return;
|
|
}
|
|
f.println(line);
|
|
f.close();
|
|
}
|
|
|
|
// // compute used bytes (simple sum of file sizes)
|
|
uint64_t computeSDUsedBytes() {
|
|
if (!sdReady) return 0;
|
|
uint64_t used = 0;
|
|
File root = SD.open("/");
|
|
if (!root) return 0;
|
|
File entry = root.openNextFile();
|
|
while (entry) {
|
|
if (!entry.isDirectory()) used += (uint64_t)entry.size();
|
|
entry.close();
|
|
entry = root.openNextFile();
|
|
}
|
|
root.close();
|
|
return used;
|
|
}
|
|
|
|
// // cleanup files named YYYYMMDD.txt older than retentionDays, skip config
|
|
void cleanupOldFilesOnSD() {
|
|
if (!sdReady || !rtcReady) return;
|
|
File root = SD.open("/");
|
|
if (!root) return;
|
|
File entry = root.openNextFile();
|
|
DateTime now = rtc.now();
|
|
while (entry) {
|
|
String name = entry.name();
|
|
if (name.length() && name[0] == '/') name = name.substring(1);
|
|
if (name != String(CONFIG_FILE) && !entry.isDirectory()) {
|
|
if (name.length() >= 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<deviceCount;i++) {
|
|
if (lastRemoteData[i].deviceId == deviceId) {
|
|
lastRemoteData[i].deviceId = deviceId;
|
|
lastRemoteData[i].soil = soil;
|
|
lastRemoteData[i].gas = gas;
|
|
lastRemoteData[i].temp = temp;
|
|
lastRemoteData[i].hum = hum;
|
|
lastRemoteData[i].timestamp = ts;
|
|
updated = true; break;
|
|
}
|
|
}
|
|
if (!updated && deviceCount < MAX_DEVICES) {
|
|
lastRemoteData[deviceCount].deviceId = deviceId;
|
|
lastRemoteData[deviceCount].soil = soil;
|
|
lastRemoteData[deviceCount].gas = gas;
|
|
lastRemoteData[deviceCount].temp = temp;
|
|
lastRemoteData[deviceCount].hum = hum;
|
|
lastRemoteData[deviceCount].timestamp = ts;
|
|
deviceCount++;
|
|
}
|
|
|
|
// save to SD if it's time and SD ready
|
|
if ((millis() - lastSaveMillis) >= (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<deviceCount;i++) {
|
|
if (i) arr += ",";
|
|
DeviceData &d = lastRemoteData[i];
|
|
arr += "{\"device\":\"" + d.deviceId + "\",\"soil\":" + String(d.soil) + ",\"gas\":" + String(d.gas) +
|
|
",\"temp\":" + String(d.temp) + ",\"hum\":" + String(d.hum) + ",\"time\":\"" + d.timestamp + "\"}";
|
|
}
|
|
arr += "]";
|
|
response = "{\"status\":\"ok\",\"data\":" + arr + "}";
|
|
break;
|
|
}
|
|
case 6: { // readfile?name=filename
|
|
if (!sdReady) { response = "{\"status\":\"error\",\"message\":\"SD not ready\"}"; break; }
|
|
String name = getParamFromPath(path, "name");
|
|
if (name.length() == 0) { response = "{\"status\":\"error\",\"message\":\"missing name\"}"; break; }
|
|
File f = SD.open(name, "r");
|
|
if (!f) { response = "{\"status\":\"error\",\"message\":\"file not found\"}"; break; }
|
|
String content = "";
|
|
while (f.available()) {
|
|
char c = f.read();
|
|
if (c == '"') content += "\""; else content += c;
|
|
if (c == '}')
|
|
{
|
|
//content += c;
|
|
content += ",";
|
|
}
|
|
}
|
|
if (content.length() > 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---------------------------------
|
|
} |