Compare commits
2 Commits
f69c0f2493
...
18b30d7c3f
| Author | SHA1 | Date | |
|---|---|---|---|
| 18b30d7c3f | |||
| abe1a64338 |
172
Config.h
Normal file
172
Config.h
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// -------------------- پیکربندی --------------------
|
||||||
|
#define FLASH_CS PA4
|
||||||
|
#define FLASH_MOSI PA7
|
||||||
|
#define FLASH_MISO PA6
|
||||||
|
#define FLASH_SCK PA5
|
||||||
|
#define SCREEN_WIDTH 128
|
||||||
|
#define SCREEN_HEIGHT 64
|
||||||
|
#define OLED_RESET -1
|
||||||
|
#define MQ7_PIN PA2
|
||||||
|
#define SDA_PIN PB9
|
||||||
|
#define SCL_PIN PB8
|
||||||
|
#define PWRKEY_PIN PB5
|
||||||
|
#define SENSOR_READ_INTERVAL 10000
|
||||||
|
|
||||||
|
#define POWER_PIN PA1
|
||||||
|
|
||||||
|
|
||||||
|
#define VOLTAGE_DIVIDER_PIN PA0 // پین خواندن ولتاژ
|
||||||
|
#define VOLTAGE_DIVIDER_RATIO 2.0 // نسبت تقسیم (R1=R2=10k => نسبت = 2)
|
||||||
|
#define ADC_REF_VOLTAGE 3.3 // ولتاژ مرجع ADC در STM32 (معمولاً 3.3V)
|
||||||
|
#define ADC_RESOLUTION 4096 // رزولوشن ADC 12-bit = 4096
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------- آدرسهای حافظه --------------------
|
||||||
|
#define CONFIG_ADDRESS 0x000000
|
||||||
|
#define DATA_ADDRESS 0x010000
|
||||||
|
|
||||||
|
// -------------------- آدرس حافظه داخلی STM32 --------------------
|
||||||
|
#define INTERNAL_CONFIG_ADDR 0x8007800 // آخرین صفحه فلش در STM32F103C8
|
||||||
|
|
||||||
|
// -------------------- انومها --------------------
|
||||||
|
enum SIMType { SIM_UNKNOWN = 0, SIM_HAMRAHE_AVAL = 1, SIM_IRANCELL = 2, SIM_RIGHTEL = 3 };
|
||||||
|
|
||||||
|
// -------------------- ساختارها --------------------
|
||||||
|
struct DeviceConfig {
|
||||||
|
char signature[4];
|
||||||
|
char deviceId[16];
|
||||||
|
char serverPhoneNumber[16];
|
||||||
|
char serverUrl[64];
|
||||||
|
unsigned long uploadInterval;
|
||||||
|
unsigned long smsInterval;
|
||||||
|
unsigned long saveInterval;
|
||||||
|
SIMType simType;
|
||||||
|
bool smsEnabled;
|
||||||
|
bool verified;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SensorData {
|
||||||
|
float temperature;
|
||||||
|
float humidity;
|
||||||
|
float coPPM;
|
||||||
|
float lightLux;
|
||||||
|
unsigned long timestamp;
|
||||||
|
uint8_t sent;
|
||||||
|
float volage;
|
||||||
|
int power;
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------- توابع کمکی --------------------
|
||||||
|
inline String simTypeToString(SIMType type) {
|
||||||
|
switch(type) {
|
||||||
|
case SIM_HAMRAHE_AVAL: return "Hamrah Aval";
|
||||||
|
case SIM_IRANCELL: return "Irancell";
|
||||||
|
case SIM_RIGHTEL: return "Rightel";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String simTypeToAPN(SIMType type) {
|
||||||
|
switch(type) {
|
||||||
|
case SIM_HAMRAHE_AVAL: return "mcinet";
|
||||||
|
case SIM_IRANCELL: return "mtnirancell";
|
||||||
|
case SIM_RIGHTEL: return "rightel";
|
||||||
|
default: return "mcinet";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String generateVerificationCode(String tokenCode) {
|
||||||
|
long token = tokenCode.toInt();
|
||||||
|
long verification = (token * 7 + 12345) % 100000;
|
||||||
|
char buffer[6];
|
||||||
|
sprintf(buffer, "%05ld", verification);
|
||||||
|
return String(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
||||||
|
|
||||||
|
// #ifndef CONFIG_H
|
||||||
|
// #define CONFIG_H
|
||||||
|
|
||||||
|
// #include <Arduino.h>
|
||||||
|
|
||||||
|
// // -------------------- پیکربندی --------------------
|
||||||
|
// #define FLASH_CS PA4
|
||||||
|
// #define FLASH_MOSI PA7
|
||||||
|
// #define FLASH_MISO PA6
|
||||||
|
// #define FLASH_SCK PA5
|
||||||
|
// #define SCREEN_WIDTH 128
|
||||||
|
// #define SCREEN_HEIGHT 64
|
||||||
|
// #define OLED_RESET -1
|
||||||
|
// #define MQ7_PIN PA2
|
||||||
|
// #define SDA_PIN PB9
|
||||||
|
// #define SCL_PIN PB8
|
||||||
|
// #define PWRKEY_PIN PB5
|
||||||
|
// #define SENSOR_READ_INTERVAL 60000
|
||||||
|
|
||||||
|
// // -------------------- آدرسهای حافظه --------------------
|
||||||
|
// #define CONFIG_ADDRESS 0x000000
|
||||||
|
// #define DATA_ADDRESS 0x010000
|
||||||
|
|
||||||
|
// // -------------------- انومها --------------------
|
||||||
|
// enum SIMType { SIM_UNKNOWN = 0, SIM_HAMRAHE_AVAL = 1, SIM_IRANCELL = 2, SIM_RIGHTEL = 3 };
|
||||||
|
|
||||||
|
// // -------------------- ساختارها --------------------
|
||||||
|
// struct DeviceConfig {
|
||||||
|
// char signature[4];
|
||||||
|
// char deviceId[16];
|
||||||
|
// char serverPhoneNumber[16];
|
||||||
|
// char serverUrl[64];
|
||||||
|
// unsigned long uploadInterval;
|
||||||
|
// unsigned long smsInterval;
|
||||||
|
// unsigned long saveInterval;
|
||||||
|
// SIMType simType;
|
||||||
|
// bool smsEnabled;
|
||||||
|
// bool verified;
|
||||||
|
// bool valid;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// struct SensorData {
|
||||||
|
// float temperature;
|
||||||
|
// float humidity;
|
||||||
|
// float coPPM;
|
||||||
|
// float lightLux;
|
||||||
|
// unsigned long timestamp;
|
||||||
|
// uint8_t sent;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // -------------------- توابع کمکی --------------------
|
||||||
|
// inline String simTypeToString(SIMType type) {
|
||||||
|
// switch(type) {
|
||||||
|
// case SIM_HAMRAHE_AVAL: return "Hamrah Aval";
|
||||||
|
// case SIM_IRANCELL: return "Irancell";
|
||||||
|
// case SIM_RIGHTEL: return "Rightel";
|
||||||
|
// default: return "Unknown";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// inline String simTypeToAPN(SIMType type) {
|
||||||
|
// switch(type) {
|
||||||
|
// case SIM_HAMRAHE_AVAL: return "mcinet";
|
||||||
|
// case SIM_IRANCELL: return "mtnirancell";
|
||||||
|
// case SIM_RIGHTEL: return "rightel";
|
||||||
|
// default: return "mcinet";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// inline String generateVerificationCode(String tokenCode) {
|
||||||
|
// long token = tokenCode.toInt();
|
||||||
|
// long verification = (token * 7 + 12345) % 100000;
|
||||||
|
// char buffer[6];
|
||||||
|
// sprintf(buffer, "%05ld", verification);
|
||||||
|
// return String(buffer);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #endif // CONFIG_H
|
||||||
|
|
||||||
320
Display.h
Normal file
320
Display.h
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
#ifndef DISPLAY_H
|
||||||
|
#define DISPLAY_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <Adafruit_SSD1306.h>
|
||||||
|
#include <Fonts/FreeSans9pt7b.h>
|
||||||
|
#include <Fonts/FreeSansBold12pt7b.h>
|
||||||
|
#include <Fonts/FreeSansBold18pt7b.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
// -------------------- متغیرهای خارجی --------------------
|
||||||
|
extern Adafruit_SSD1306 display;
|
||||||
|
extern DeviceConfig config;
|
||||||
|
extern SensorData currentData;
|
||||||
|
extern bool sht31Connected;
|
||||||
|
extern bool lightSensorConnected;
|
||||||
|
extern bool coSensorConnected;
|
||||||
|
extern bool calibrationComplete;
|
||||||
|
extern bool awaitingSMS2;
|
||||||
|
extern int signalStrength;
|
||||||
|
extern String lastMessage;
|
||||||
|
extern int displayMode;
|
||||||
|
extern unsigned long lastDisplayChange;
|
||||||
|
extern unsigned long lastNetworkStatusUpdate;
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
void updateNetworkStatus();
|
||||||
|
|
||||||
|
// -------------------- توابع نمایشگر --------------------
|
||||||
|
inline void displayAllParameters() {
|
||||||
|
display.clearDisplay();
|
||||||
|
//display.setTextColor(SSD1306_WHITE);
|
||||||
|
|
||||||
|
// عنوان با فونت زیبا
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(8, 12);
|
||||||
|
display.print("All Parameters");
|
||||||
|
|
||||||
|
// خط جداکننده
|
||||||
|
display.drawLine(0, 15, 128, 15, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// ارتفاع 12-14
|
||||||
|
// دما
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(1, 30);
|
||||||
|
display.print("T:");//16 پیکسل
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(18, 30);
|
||||||
|
display.print(currentData.temperature, 1);//34 پیکسل
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
//display.setCursor(55, 30);
|
||||||
|
//display.print("-");//3 پیکسل
|
||||||
|
|
||||||
|
// رطوبت
|
||||||
|
display.setCursor(65, 30);
|
||||||
|
display.print("H:");//16 پیکسل
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(83, 30);
|
||||||
|
display.print(currentData.humidity, 0);//34 پیکسل
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(110, 31);
|
||||||
|
display.print("%");
|
||||||
|
|
||||||
|
// نور
|
||||||
|
display.setCursor(1, 55);
|
||||||
|
display.print("L:");//15 پیکسل
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(20, 55);
|
||||||
|
display.print((int)currentData.lightLux);//40 پیکسل
|
||||||
|
|
||||||
|
//گاز
|
||||||
|
display.setCursor(70, 55);
|
||||||
|
display.print("G:");//15 پیکسل
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(88, 55);
|
||||||
|
display.print((int)currentData.coPPM);//40 پیکسل
|
||||||
|
|
||||||
|
// برگشت به فونت پیشفرض
|
||||||
|
display.setFont();
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void displayTemperature() {
|
||||||
|
display.clearDisplay();
|
||||||
|
|
||||||
|
// عنوان
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(18, 14);
|
||||||
|
display.print("Temperature");
|
||||||
|
|
||||||
|
// خط جداکننده
|
||||||
|
display.drawLine(0, 18, 128, 18, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// مقدار دما - عدد بزرگ و نرم
|
||||||
|
display.setFont(&FreeSansBold18pt7b);
|
||||||
|
display.setCursor(10, 50);
|
||||||
|
display.print(currentData.temperature, 1);
|
||||||
|
|
||||||
|
// واحد
|
||||||
|
display.setFont(&FreeSansBold12pt7b);
|
||||||
|
display.setCursor(90, 45);
|
||||||
|
display.print("C");
|
||||||
|
|
||||||
|
// علامت درجه
|
||||||
|
display.drawCircle(85, 30, 3, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// وضعیت سنسور
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(50, 63);
|
||||||
|
if (!sht31Connected) {
|
||||||
|
display.print("ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
display.setFont();
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void displayHumidity() {
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
|
||||||
|
// عنوان
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(30, 14);
|
||||||
|
display.print("Humidity");
|
||||||
|
|
||||||
|
// خط جداکننده
|
||||||
|
display.drawLine(0, 18, 128, 18, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// مقدار رطوبت - عدد بزرگ و نرم
|
||||||
|
display.setFont(&FreeSansBold18pt7b);
|
||||||
|
display.setCursor(25, 50);
|
||||||
|
display.print(currentData.humidity, 0);
|
||||||
|
|
||||||
|
// واحد درصد
|
||||||
|
display.setFont(&FreeSansBold12pt7b);
|
||||||
|
display.setCursor(75, 48);
|
||||||
|
display.print("%");
|
||||||
|
|
||||||
|
// وضعیت سنسور
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(50, 63);
|
||||||
|
if (!sht31Connected) {
|
||||||
|
display.print("ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
display.setFont();
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void displayCO() {
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
|
||||||
|
// عنوان
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(35, 14);
|
||||||
|
display.print("CO Gas");
|
||||||
|
|
||||||
|
// خط جداکننده
|
||||||
|
display.drawLine(0, 18, 128, 18, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// مقدار CO (منفی = در حال کالیبره) - عدد بزرگ و نرم
|
||||||
|
display.setFont(&FreeSansBold18pt7b);
|
||||||
|
display.setCursor(8, 50);
|
||||||
|
display.print(currentData.coPPM, 0);
|
||||||
|
|
||||||
|
// واحد
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(70, 48);
|
||||||
|
display.print("ppm");
|
||||||
|
|
||||||
|
// وضعیت سنسور و کالیبراسیون
|
||||||
|
display.setCursor(5, 63);
|
||||||
|
if (!calibrationComplete) {
|
||||||
|
display.print("Calibrating...");
|
||||||
|
} else if (!coSensorConnected) {
|
||||||
|
display.print("Sensor ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
display.setFont();
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void displayLight() {
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
|
||||||
|
// عنوان
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(40, 14);
|
||||||
|
display.print("Light");
|
||||||
|
|
||||||
|
// خط جداکننده
|
||||||
|
display.drawLine(0, 18, 128, 18, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// مقدار نور - عدد بزرگ و نرم
|
||||||
|
display.setFont(&FreeSansBold18pt7b);
|
||||||
|
display.setCursor(8, 50);
|
||||||
|
display.print((int)currentData.lightLux);
|
||||||
|
|
||||||
|
// واحد
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(75, 48);
|
||||||
|
display.print("lux");
|
||||||
|
|
||||||
|
// وضعیت سنسور
|
||||||
|
display.setCursor(50, 63);
|
||||||
|
if (!lightSensorConnected) {
|
||||||
|
display.print("ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
display.setFont();
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void displaySIMStatus() {
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
|
||||||
|
// عنوان
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(25, 14);
|
||||||
|
display.print("SIM Status");
|
||||||
|
|
||||||
|
// خط جداکننده
|
||||||
|
display.drawLine(0, 18, 128, 18, SSD1306_WHITE);
|
||||||
|
|
||||||
|
// بقیه اطلاعات با فونت پیشفرض برای جا شدن بیشتر
|
||||||
|
display.setFont();
|
||||||
|
|
||||||
|
if (!config.verified) {
|
||||||
|
// وضعیت تنظیم نشده
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(20, 28);
|
||||||
|
if (!awaitingSMS2) {
|
||||||
|
display.print("Waiting SMS1");
|
||||||
|
display.setCursor(15, 42);
|
||||||
|
display.print("for activation");
|
||||||
|
} else {
|
||||||
|
display.print("Waiting SMS2");
|
||||||
|
display.setCursor(15, 42);
|
||||||
|
display.print("for config");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// وضعیت تنظیم شده
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(5, 22);
|
||||||
|
display.print("ID:");
|
||||||
|
display.setCursor(25, 22);
|
||||||
|
display.print(config.deviceId);
|
||||||
|
|
||||||
|
display.setCursor(5, 32);
|
||||||
|
display.print("SIM:");
|
||||||
|
display.setCursor(30, 32);
|
||||||
|
display.print(simTypeToString(config.simType));
|
||||||
|
|
||||||
|
display.setCursor(5, 42);
|
||||||
|
display.print("Upload:");
|
||||||
|
display.setCursor(50, 42);
|
||||||
|
display.print(config.uploadInterval);
|
||||||
|
display.print("m");
|
||||||
|
|
||||||
|
// سیگنال با نشانگر گرافیکی
|
||||||
|
display.setCursor(80, 42);
|
||||||
|
display.print("Sig:");
|
||||||
|
display.setCursor(105, 42);
|
||||||
|
display.print(signalStrength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// آخرین پیام
|
||||||
|
display.setFont(&FreeSans9pt7b);
|
||||||
|
display.setCursor(5, 62);
|
||||||
|
if (lastMessage.length() > 14) {
|
||||||
|
display.print(lastMessage.substring(0, 14));
|
||||||
|
} else {
|
||||||
|
display.print(lastMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
display.setFont();
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void updateDisplay() {
|
||||||
|
// تغییر صفحه هر 3 ثانیه
|
||||||
|
if (millis() - lastDisplayChange > 3000) {
|
||||||
|
displayMode = (displayMode + 1) % 6; // 6 صفحه داریم
|
||||||
|
lastDisplayChange = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// بهروزرسانی وضعیت شبکه هر 30 ثانیه
|
||||||
|
if (millis() - lastNetworkStatusUpdate > 30000) {
|
||||||
|
updateNetworkStatus();
|
||||||
|
lastNetworkStatusUpdate = millis();
|
||||||
|
}
|
||||||
|
switch(displayMode) {
|
||||||
|
case 0:
|
||||||
|
displayAllParameters();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
displayTemperature();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
displayHumidity();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
displayCO();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
displayLight();
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
displaySIMStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DISPLAY_H
|
||||||
|
|
||||||
429
Memory.h
Normal file
429
Memory.h
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
#ifndef MEMORY_H
|
||||||
|
#define MEMORY_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
// -------------------- کتابخانه EEPROM داخلی برای STM32 --------------------
|
||||||
|
#if defined(STM32F1xx) || defined(STM32F3xx) || defined(STM32F4xx)
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// -------------------- متغیرهای خارجی --------------------
|
||||||
|
extern SPIClass flashSPI;
|
||||||
|
extern DeviceConfig config;
|
||||||
|
|
||||||
|
// -------------------- تنظیمات EEPROM داخلی --------------------
|
||||||
|
#define EEPROM_CONFIG_START 0 // آدرس شروع در EEPROM
|
||||||
|
#define EEPROM_SIGNATURE_ADDR 0 // آدرس سیگنچور در EEPROM
|
||||||
|
#define EEPROM_CONFIG_SIZE sizeof(DeviceConfig)
|
||||||
|
|
||||||
|
// -------------------- توابع حافظه SPI Flash --------------------
|
||||||
|
inline void flashInit() {
|
||||||
|
pinMode(FLASH_CS, OUTPUT);
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
flashSPI.begin();
|
||||||
|
flashSPI.setClockDivider(SPI_CLOCK_DIV4);
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool flashIsBusy() {
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x05);
|
||||||
|
uint8_t status = flashSPI.transfer(0);
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
return (status & 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void flashWaitForReady() {
|
||||||
|
unsigned long start = millis();
|
||||||
|
while (flashIsBusy()) {
|
||||||
|
if (millis() - start > 1000) {
|
||||||
|
Serial1.println("⚠️ Flash wait timeout");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool flashReadBytes(uint32_t addr, uint8_t *data, uint32_t len) {
|
||||||
|
if (len == 0) return false;
|
||||||
|
|
||||||
|
flashWaitForReady();
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x03); // Read command
|
||||||
|
flashSPI.transfer((addr >> 16) & 0xFF);
|
||||||
|
flashSPI.transfer((addr >> 8) & 0xFF);
|
||||||
|
flashSPI.transfer(addr & 0xFF);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
data[i] = flashSPI.transfer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool flashWriteBytes(uint32_t addr, uint8_t *data, uint32_t len) {
|
||||||
|
if (len == 0) return false;
|
||||||
|
|
||||||
|
// Write Enable
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x06);
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
// Page Program
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x02);
|
||||||
|
flashSPI.transfer((addr >> 16) & 0xFF);
|
||||||
|
flashSPI.transfer((addr >> 8) & 0xFF);
|
||||||
|
flashSPI.transfer(addr & 0xFF);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; i++) {
|
||||||
|
flashSPI.transfer(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
flashWaitForReady();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void flashSectorErase(uint32_t addr) {
|
||||||
|
// Write Enable
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x06);
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
// Sector Erase
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x20);
|
||||||
|
flashSPI.transfer((addr >> 16) & 0xFF);
|
||||||
|
flashSPI.transfer((addr >> 8) & 0xFF);
|
||||||
|
flashSPI.transfer(addr & 0xFF);
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
|
||||||
|
flashWaitForReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool testFlashCommunication() {
|
||||||
|
Serial1.print("Testing flash communication... ");
|
||||||
|
|
||||||
|
// دستور خواندن JEDEC ID
|
||||||
|
digitalWrite(FLASH_CS, LOW);
|
||||||
|
flashSPI.transfer(0x9F);
|
||||||
|
uint8_t manuf = flashSPI.transfer(0);
|
||||||
|
uint8_t type = flashSPI.transfer(0);
|
||||||
|
uint8_t capacity = flashSPI.transfer(0);
|
||||||
|
digitalWrite(FLASH_CS, HIGH);
|
||||||
|
|
||||||
|
if (manuf == 0xFF || manuf == 0x00) {
|
||||||
|
Serial1.println("❌ FAILED (No response)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial1.print("✅ OK (Manuf: 0x");
|
||||||
|
Serial1.print(manuf, HEX);
|
||||||
|
Serial1.print(", Type: 0x");
|
||||||
|
Serial1.print(type, HEX);
|
||||||
|
Serial1.print(", Capacity: 0x");
|
||||||
|
Serial1.print(capacity, HEX);
|
||||||
|
Serial1.println(")");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- توابع EEPROM داخلی STM32 --------------------
|
||||||
|
inline bool eepromIsAvailable() {
|
||||||
|
#if defined(STM32F1xx) || defined(STM32F3xx) || defined(STM32F4xx)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool eepromWriteConfig(const DeviceConfig &cfg) {
|
||||||
|
if (!eepromIsAvailable()) {
|
||||||
|
Serial1.println("❌ EEPROM not available on this board");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial1.print("Writing config to EEPROM (");
|
||||||
|
Serial1.print(sizeof(cfg));
|
||||||
|
Serial1.println(" bytes)...");
|
||||||
|
|
||||||
|
uint8_t *data = (uint8_t*)&cfg;
|
||||||
|
|
||||||
|
// نوشتن در EEPROM
|
||||||
|
for (uint16_t i = 0; i < sizeof(cfg); i++) {
|
||||||
|
EEPROM.write(EEPROM_CONFIG_START + i, data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ذخیره تغییرات
|
||||||
|
#if defined(EEPROM_commit)
|
||||||
|
EEPROM.commit();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Serial1.println("✅ Config written to EEPROM");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool eepromReadConfig(DeviceConfig &cfg) {
|
||||||
|
if (!eepromIsAvailable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *data = (uint8_t*)&cfg;
|
||||||
|
|
||||||
|
// خواندن از EEPROM
|
||||||
|
for (uint16_t i = 0; i < sizeof(cfg); i++) {
|
||||||
|
data[i] = EEPROM.read(EEPROM_CONFIG_START + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی سیگنچور
|
||||||
|
if (strcmp(cfg.signature, "CFG") != 0) {
|
||||||
|
Serial1.println("❌ Invalid signature in EEPROM");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial1.println("✅ Config read from EEPROM");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool eepromClearConfig() {
|
||||||
|
if (!eepromIsAvailable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// پاک کردن با نوشتن 0xFF
|
||||||
|
for (uint16_t i = 0; i < EEPROM_CONFIG_SIZE; i++) {
|
||||||
|
EEPROM.write(EEPROM_CONFIG_START + i, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(EEPROM_commit)
|
||||||
|
EEPROM.commit();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Serial1.println("✅ EEPROM cleared");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- توابع مدیریت کانفیگ هوشمند --------------------
|
||||||
|
inline void saveConfig() {
|
||||||
|
Serial1.println("\n💾 SAVING CONFIGURATION");
|
||||||
|
|
||||||
|
config.valid = true;
|
||||||
|
uint8_t buffer[sizeof(DeviceConfig)];
|
||||||
|
memcpy(buffer, &config, sizeof(DeviceConfig));
|
||||||
|
|
||||||
|
bool flashSuccess = false;
|
||||||
|
bool eepromSuccess = false;
|
||||||
|
|
||||||
|
// 1. ذخیره در حافظه SPI Flash (اصلی)
|
||||||
|
Serial1.println("1. Saving to SPI Flash...");
|
||||||
|
if (testFlashCommunication()) {
|
||||||
|
flashSectorErase(CONFIG_ADDRESS);
|
||||||
|
if (flashWriteBytes(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig))) {
|
||||||
|
// تأیید
|
||||||
|
uint8_t verifyBuffer[sizeof(DeviceConfig)];
|
||||||
|
if (flashReadBytes(CONFIG_ADDRESS, verifyBuffer, sizeof(DeviceConfig))) {
|
||||||
|
if (memcmp(buffer, verifyBuffer, sizeof(DeviceConfig)) == 0) {
|
||||||
|
flashSuccess = true;
|
||||||
|
Serial1.println(" ✅ SPI Flash: Saved and verified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flashSuccess) {
|
||||||
|
Serial1.println(" ❌ SPI Flash: Failed or not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. ذخیره در EEPROM داخلی (پشتیبان)
|
||||||
|
Serial1.println("2. Saving to internal EEPROM...");
|
||||||
|
eepromSuccess = eepromWriteConfig(config);
|
||||||
|
|
||||||
|
if (eepromSuccess) {
|
||||||
|
Serial1.println(" ✅ EEPROM: Backup saved");
|
||||||
|
} else {
|
||||||
|
Serial1.println(" ⚠️ EEPROM: Backup not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
// خلاصه
|
||||||
|
Serial1.println("\n📊 SAVE SUMMARY:");
|
||||||
|
Serial1.print(" SPI Flash: ");
|
||||||
|
Serial1.println(flashSuccess ? "✅" : "❌");
|
||||||
|
Serial1.print(" Internal EEPROM: ");
|
||||||
|
Serial1.println(eepromSuccess ? "✅" : "⚠️");
|
||||||
|
|
||||||
|
if (flashSuccess || eepromSuccess) {
|
||||||
|
Serial1.println("✅ Configuration saved successfully");
|
||||||
|
} else {
|
||||||
|
Serial1.println("❌ CRITICAL: Could not save config anywhere!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool loadConfigFromFlash() {
|
||||||
|
Serial1.print("Trying to load from SPI Flash... ");
|
||||||
|
|
||||||
|
uint8_t buffer[sizeof(DeviceConfig)];
|
||||||
|
|
||||||
|
if (!testFlashCommunication()) {
|
||||||
|
Serial1.println("❌ Flash not responding");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flashReadBytes(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig))) {
|
||||||
|
Serial1.println("❌ Failed to read from flash");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&config, buffer, sizeof(DeviceConfig));
|
||||||
|
|
||||||
|
if (strcmp(config.signature, "CFG") != 0) {
|
||||||
|
Serial1.println("❌ Invalid signature in flash");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.valid = true;
|
||||||
|
Serial1.println("✅ Success");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool loadConfigFromEEPROM() {
|
||||||
|
Serial1.print("Trying to load from internal EEPROM... ");
|
||||||
|
|
||||||
|
if (!eepromIsAvailable()) {
|
||||||
|
Serial1.println("❌ EEPROM not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eepromReadConfig(config)) {
|
||||||
|
Serial1.println("❌ Failed to read from EEPROM");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.valid = true;
|
||||||
|
Serial1.println("✅ Success");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void createDefaultConfig() {
|
||||||
|
Serial1.println("Creating default configuration...");
|
||||||
|
|
||||||
|
strcpy(config.signature, "CFG");
|
||||||
|
strcpy(config.deviceId, "");
|
||||||
|
strcpy(config.serverPhoneNumber, "");
|
||||||
|
strcpy(config.serverUrl, "https://ghback.nabaksoft.ir");
|
||||||
|
config.uploadInterval = 5;
|
||||||
|
config.smsInterval = 10;
|
||||||
|
config.saveInterval = 60;
|
||||||
|
config.simType = SIM_UNKNOWN;
|
||||||
|
config.smsEnabled = false;
|
||||||
|
config.verified = false;
|
||||||
|
config.valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void readConfig() {
|
||||||
|
Serial1.println("\n📖 LOADING CONFIGURATION");
|
||||||
|
|
||||||
|
bool configLoaded = false;
|
||||||
|
String source = "";
|
||||||
|
|
||||||
|
// استراتژی: اول SPI Flash، اگر نشد EEPROM داخلی
|
||||||
|
Serial1.println("Strategy: SPI Flash → Internal EEPROM");
|
||||||
|
|
||||||
|
// 1. اول از SPI Flash بخوان
|
||||||
|
if (loadConfigFromFlash()) {
|
||||||
|
configLoaded = true;
|
||||||
|
source = "SPI Flash";
|
||||||
|
|
||||||
|
// همچنین در EEPROM هم بروزرسانی کن (sync)
|
||||||
|
eepromWriteConfig(config);
|
||||||
|
}
|
||||||
|
// 2. اگر SPI Flash کار نکرد، از EEPROM داخلی بخوان
|
||||||
|
else if (loadConfigFromEEPROM()) {
|
||||||
|
configLoaded = true;
|
||||||
|
source = "Internal EEPROM (backup)";
|
||||||
|
|
||||||
|
// سعی کن دوباره در SPI Flash ذخیره کنی (بازیابی)
|
||||||
|
Serial1.println("Attempting to restore to SPI Flash...");
|
||||||
|
uint8_t buffer[sizeof(DeviceConfig)];
|
||||||
|
memcpy(buffer, &config, sizeof(DeviceConfig));
|
||||||
|
|
||||||
|
if (testFlashCommunication()) {
|
||||||
|
flashSectorErase(CONFIG_ADDRESS);
|
||||||
|
flashWriteBytes(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig));
|
||||||
|
Serial1.println("✅ Restored to SPI Flash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. اگر هیچ کدام کار نکرد، کانفیگ پیشفرض بساز
|
||||||
|
if (!configLoaded) {
|
||||||
|
Serial1.println("❌ No valid config found in any memory");
|
||||||
|
source = "Default";
|
||||||
|
|
||||||
|
createDefaultConfig();
|
||||||
|
saveConfig(); // ذخیره کانفیگ جدید
|
||||||
|
configLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// نمایش نتیجه
|
||||||
|
Serial1.println("\n📊 LOAD RESULT:");
|
||||||
|
Serial1.print(" Status: ");
|
||||||
|
Serial1.println(configLoaded ? "✅ LOADED" : "❌ FAILED");
|
||||||
|
Serial1.print(" Source: ");
|
||||||
|
Serial1.println(source);
|
||||||
|
Serial1.print(" Device ID: ");
|
||||||
|
Serial1.println(strlen(config.deviceId) > 0 ? config.deviceId : "[Empty]");
|
||||||
|
Serial1.print(" Verified: ");
|
||||||
|
Serial1.println(config.verified ? "YES" : "NO");
|
||||||
|
|
||||||
|
if (configLoaded) {
|
||||||
|
Serial1.println("✅ Configuration ready");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// تابع دیباگ برای نمایش وضعیت حافظهها
|
||||||
|
inline void memoryStatus() {
|
||||||
|
Serial1.println("\n🔍 MEMORY STATUS");
|
||||||
|
Serial1.println("================");
|
||||||
|
|
||||||
|
// تست SPI Flash
|
||||||
|
Serial1.print("SPI Flash: ");
|
||||||
|
Serial1.println(testFlashCommunication() ? "✅ Available" : "❌ Not available");
|
||||||
|
|
||||||
|
// تست EEPROM داخلی
|
||||||
|
Serial1.print("Internal EEPROM: ");
|
||||||
|
Serial1.println(eepromIsAvailable() ? "✅ Available" : "❌ Not available");
|
||||||
|
|
||||||
|
// وضعیت کانفیگ فعلی
|
||||||
|
Serial1.print("Config in RAM: ");
|
||||||
|
Serial1.println(config.valid ? "✅ Valid" : "❌ Invalid");
|
||||||
|
|
||||||
|
Serial1.println("================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// تابع ریست کامل (برای تست)
|
||||||
|
inline void resetAllConfig() {
|
||||||
|
Serial1.println("\n⚠️ RESETTING ALL CONFIGURATION");
|
||||||
|
|
||||||
|
// پاک کردن SPI Flash
|
||||||
|
if (testFlashCommunication()) {
|
||||||
|
flashSectorErase(CONFIG_ADDRESS);
|
||||||
|
Serial1.println("✅ SPI Flash erased");
|
||||||
|
}
|
||||||
|
|
||||||
|
// پاک کردن EEPROM
|
||||||
|
eepromClearConfig();
|
||||||
|
|
||||||
|
// ایجاد کانفیگ پیشفرض
|
||||||
|
createDefaultConfig();
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
Serial1.println("✅ All configuration reset to defaults\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MEMORY_H
|
||||||
14
STM32_InternalFlash.h.h
Normal file
14
STM32_InternalFlash.h.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// STM32_InternalFlash.h
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "stm32f1xx_hal.h"
|
||||||
|
|
||||||
|
bool STM32_InternalFlash_Write(uint32_t address, const uint8_t* data, uint32_t size);
|
||||||
|
bool STM32_InternalFlash_Read(uint32_t address, uint8_t* data, uint32_t size);
|
||||||
|
bool STM32_InternalFlash_Erase(uint32_t address);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
199
Sensors.h
Normal file
199
Sensors.h
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
#ifndef SENSORS_H
|
||||||
|
#define SENSORS_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Adafruit_SHT31.h>
|
||||||
|
#include <BH1750.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
// -------------------- متغیرهای خارجی --------------------
|
||||||
|
extern Adafruit_SHT31 sht31;
|
||||||
|
extern BH1750 lightMeter;
|
||||||
|
extern SensorData currentData;
|
||||||
|
extern bool sht31Connected;
|
||||||
|
extern bool lightSensorConnected;
|
||||||
|
extern bool coSensorConnected;
|
||||||
|
extern bool calibrationComplete;
|
||||||
|
extern unsigned long systemStartTime;
|
||||||
|
extern unsigned long lastSensorRead;
|
||||||
|
extern float MQ7_R0;
|
||||||
|
|
||||||
|
#define VCC 4.63 // ولتاژ واقعی سنسور
|
||||||
|
#define RL 10000.0 // مقاومت بار RL
|
||||||
|
#define VDIV_RATIO 0.681
|
||||||
|
|
||||||
|
// ثابتهای کالیبراسیون
|
||||||
|
extern const long calibrationPeriod;
|
||||||
|
|
||||||
|
// -------------------- توابع CO --------------------
|
||||||
|
inline void calibrateMQ7() {
|
||||||
|
Serial1.println("Calibrating MQ7...");
|
||||||
|
|
||||||
|
float sumRS = 0;
|
||||||
|
for(int i = 0; i < 100; i++) {
|
||||||
|
int adc = analogRead(MQ7_PIN);
|
||||||
|
float v_adc = adc * (5.0 / 4095.0); // ولتاژ ADC 12 بیتی، 5V
|
||||||
|
float v_ao = v_adc / VDIV_RATIO; // اصلاح نسبت تقسیم ولتاژ
|
||||||
|
|
||||||
|
if (v_ao < 0.01) v_ao = 0.01;
|
||||||
|
|
||||||
|
float RS = RL * (VCC / v_ao - 1.0); // محاسبه مقاومت سنسور
|
||||||
|
sumRS += RS;
|
||||||
|
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// نسبت RS/R0 در هوای پاک ≈ 9.8
|
||||||
|
MQ7_R0 = (sumRS / 100.0) / 9.8;
|
||||||
|
calibrationComplete = true;
|
||||||
|
|
||||||
|
Serial1.print("MQ7 R0 calibrated: ");
|
||||||
|
Serial1.println(MQ7_R0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
inline float readCOImproved() {
|
||||||
|
float sumVoltage = 0;
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
int adc = analogRead(MQ7_PIN);
|
||||||
|
float v_adc = adc * (5.0 / 4095.0); // ولتاژ ADC 12 بیتی، 5V
|
||||||
|
sumVoltage += v_adc;
|
||||||
|
delay(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
float v_avg = sumVoltage / 20.0;
|
||||||
|
if (v_avg < 0.01) v_avg = 0.01;
|
||||||
|
if (v_avg > 4.99) v_avg = 4.99;
|
||||||
|
|
||||||
|
float v_ao = v_avg / VDIV_RATIO; // اصلاح تقسیم ولتاژ
|
||||||
|
float RS = RL * (VCC / v_ao - 1.0); // مقاومت سنسور
|
||||||
|
|
||||||
|
float ratio = RS / MQ7_R0;
|
||||||
|
float ppm = 0;
|
||||||
|
|
||||||
|
if (ratio > 0) {
|
||||||
|
// رابطه لگاریتمی تقریبی دیتاشیت MQ-7
|
||||||
|
ppm = 100.0 * pow(ratio, -1.53);
|
||||||
|
|
||||||
|
if (ppm < 0) ppm = 0;
|
||||||
|
if (ppm > 5000) ppm = 5000; // MQ-7 تا 5k ppm
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calibrationComplete) {
|
||||||
|
ppm = -ppm;
|
||||||
|
// Serial1.println("MQ7 not calibrated!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ppm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- بررسی وضعیت سنسورها --------------------
|
||||||
|
inline void checkSensorStatus() {
|
||||||
|
float temp = sht31.readTemperature();
|
||||||
|
float hum = sht31.readHumidity();
|
||||||
|
sht31Connected = !isnan(temp) && !isnan(hum) && temp > -40 && temp < 125 && hum >= 0 && hum <= 100;
|
||||||
|
|
||||||
|
float lux = lightMeter.readLightLevel();
|
||||||
|
lightSensorConnected = !isnan(lux) && lux >= 0;
|
||||||
|
|
||||||
|
int adc = analogRead(MQ7_PIN);
|
||||||
|
coSensorConnected = (adc > 0 && adc < 4095);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- لاگ کامل وضعیت سیستم --------------------
|
||||||
|
inline void printFullStatus() {
|
||||||
|
extern bool simConnected;
|
||||||
|
extern bool networkRegistered;
|
||||||
|
extern bool networkConnected;
|
||||||
|
extern int signalStrength;
|
||||||
|
extern bool awaitingSMS2;
|
||||||
|
extern String lastMessage;
|
||||||
|
extern DeviceConfig config;
|
||||||
|
|
||||||
|
Serial1.println("\n========== SYSTEM STATUS ==========");
|
||||||
|
|
||||||
|
Serial1.println("--- SENSORS ---");
|
||||||
|
Serial1.print(" Temperature: ");
|
||||||
|
Serial1.print(currentData.temperature, 1);
|
||||||
|
Serial1.print(" C [");
|
||||||
|
Serial1.print(sht31Connected ? "OK" : "ERR");
|
||||||
|
Serial1.println("]");
|
||||||
|
|
||||||
|
Serial1.print(" Humidity: ");
|
||||||
|
Serial1.print(currentData.humidity, 1);
|
||||||
|
Serial1.print(" % [");
|
||||||
|
Serial1.print(sht31Connected ? "OK" : "ERR");
|
||||||
|
Serial1.println("]");
|
||||||
|
|
||||||
|
Serial1.print(" Light: ");
|
||||||
|
Serial1.print(currentData.lightLux, 0);
|
||||||
|
Serial1.print(" lux [");
|
||||||
|
Serial1.print(lightSensorConnected ? "OK" : "ERR");
|
||||||
|
Serial1.println("]");
|
||||||
|
|
||||||
|
Serial1.print(" CO: ");
|
||||||
|
Serial1.print(currentData.coPPM, 1);
|
||||||
|
Serial1.print(" ppm [");
|
||||||
|
Serial1.print(coSensorConnected ? "OK" : "ERR");
|
||||||
|
Serial1.print("] Cal:");
|
||||||
|
Serial1.println(calibrationComplete ? "Complete" : "In Progress");
|
||||||
|
|
||||||
|
Serial1.println("--- NETWORK ---");
|
||||||
|
Serial1.print(" SIM Connected: ");
|
||||||
|
Serial1.println(simConnected ? "Yes" : "No");
|
||||||
|
Serial1.print(" Network Registered:");
|
||||||
|
Serial1.println(networkRegistered ? "Yes" : "No");
|
||||||
|
Serial1.print(" Signal Strength: ");
|
||||||
|
Serial1.print(signalStrength);
|
||||||
|
Serial1.println("/31");
|
||||||
|
Serial1.print(" Internet: ");
|
||||||
|
Serial1.println(networkConnected ? "Online" : "Offline");
|
||||||
|
|
||||||
|
Serial1.println("--- CONFIG ---");
|
||||||
|
Serial1.print(" Verified: ");
|
||||||
|
Serial1.println(config.verified ? "Yes" : "No");
|
||||||
|
if (config.verified) {
|
||||||
|
Serial1.print(" Device ID: ");
|
||||||
|
Serial1.println(config.deviceId);
|
||||||
|
Serial1.print(" SIM Type: ");
|
||||||
|
Serial1.println(simTypeToString(config.simType));
|
||||||
|
Serial1.print(" Upload Int: ");
|
||||||
|
Serial1.print(config.uploadInterval);
|
||||||
|
Serial1.println(" min");
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial1.println("--- SMS STATUS ---");
|
||||||
|
Serial1.print(" Awaiting SMS2: ");
|
||||||
|
Serial1.println(awaitingSMS2 ? "Yes" : "No");
|
||||||
|
Serial1.print(" Last Message: ");
|
||||||
|
Serial1.println(lastMessage);
|
||||||
|
|
||||||
|
Serial1.println("====================================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- خواندن سنسورها --------------------
|
||||||
|
inline void readSensors() {
|
||||||
|
currentData.temperature = sht31.readTemperature();
|
||||||
|
currentData.humidity = sht31.readHumidity();
|
||||||
|
|
||||||
|
if (!calibrationComplete && (millis() - systemStartTime >= calibrationPeriod)) {
|
||||||
|
calibrationComplete = true;
|
||||||
|
Serial1.println("✅ Calibration period completed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
currentData.coPPM = readCOImproved();
|
||||||
|
|
||||||
|
currentData.lightLux = lightMeter.readLightLevel();
|
||||||
|
if (isnan(currentData.lightLux)) currentData.lightLux = 0;
|
||||||
|
|
||||||
|
checkSensorStatus();
|
||||||
|
|
||||||
|
printFullStatus();
|
||||||
|
|
||||||
|
lastSensorRead = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SENSORS_H
|
||||||
|
|
||||||
389
TestLCD.ino
389
TestLCD.ino
@@ -1,265 +1,196 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <Adafruit_SHT31.h>
|
|
||||||
#include <Adafruit_GFX.h>
|
#include <Adafruit_GFX.h>
|
||||||
#include <Adafruit_SSD1306.h>
|
#include <Adafruit_SSD1306.h>
|
||||||
|
#include <Fonts/FreeSans9pt7b.h>
|
||||||
|
#include <Fonts/FreeSansBold12pt7b.h>
|
||||||
|
#include <Fonts/FreeSansBold18pt7b.h>
|
||||||
|
#include <Adafruit_SHT31.h>
|
||||||
#include <BH1750.h>
|
#include <BH1750.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
// تعریف پینها
|
// -------------------- فایلهای پروژه --------------------
|
||||||
#define ADC_PIN PA2 // یا A2 اگر تعریف شده
|
#include "Config.h"
|
||||||
#define LED_PIN PC13
|
#include "Memory.h"
|
||||||
#define OLED_ADDR 0x3C
|
#include "Sensors.h"
|
||||||
|
#include "EC200U.h"
|
||||||
|
#include "Display.h"
|
||||||
|
#include "Voltage_Reader.h"
|
||||||
|
|
||||||
// پینهای I2C
|
// -------------------- متغیرهای سراسری --------------------
|
||||||
#define I2C_SDA PB9
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||||
#define I2C_SCL PB8
|
|
||||||
|
|
||||||
// آبجکتها
|
|
||||||
Adafruit_SHT31 sht31;
|
|
||||||
BH1750 lightMeter;
|
BH1750 lightMeter;
|
||||||
Adafruit_SSD1306 display(128, 64, &Wire, -1);
|
Adafruit_SHT31 sht31;
|
||||||
|
HardwareSerial EC200U(USART3);
|
||||||
|
SPIClass flashSPI(FLASH_MOSI, FLASH_MISO, FLASH_SCK);
|
||||||
|
|
||||||
// متغیرها
|
DeviceConfig config;
|
||||||
float temperature, humidity, light;
|
SensorData currentData;
|
||||||
int raw_adc = 0;
|
|
||||||
float voltage = 0;
|
|
||||||
float co_ppm = 0;
|
|
||||||
|
|
||||||
// تابع دیباگ برای تست ADC
|
// متغیرهای موقت برای پردازش پیامکها
|
||||||
void testADC() {
|
String tempPhoneNumber = "";
|
||||||
Serial1.print("ADC Pin: PA2 (Pin ");
|
String tempTokenCode = "";
|
||||||
Serial1.print(ADC_PIN);
|
bool awaitingSMS2 = false;
|
||||||
Serial1.println(")");
|
|
||||||
|
|
||||||
// خواندن 10 نمونه
|
|
||||||
Serial1.println("Reading ADC values:");
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
int val = analogRead(ADC_PIN);
|
|
||||||
Serial1.print("Sample ");
|
|
||||||
Serial1.print(i);
|
|
||||||
Serial1.print(": ");
|
|
||||||
Serial1.print(val);
|
|
||||||
Serial1.print(" (");
|
|
||||||
Serial1.print(val * 3.3 / 4095.0, 3);
|
|
||||||
Serial1.println("V)");
|
|
||||||
delay(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// تابع برای بررسی اتصال MQ-7
|
unsigned long lastSensorRead = 0;
|
||||||
void checkMQ7Connection() {
|
unsigned long lastUpload = 0;
|
||||||
Serial1.println("\n=== Testing MQ-7 Connection ===");
|
unsigned long lastDisplayChange = 0;
|
||||||
|
unsigned long lastNetworkStatusUpdate = 0;
|
||||||
// حالت 1: اتصال باز (هوای آزاد)
|
|
||||||
Serial1.println("1. Disconnect MQ-7 (open circuit):");
|
|
||||||
delay(3000);
|
|
||||||
raw_adc = analogRead(ADC_PIN);
|
|
||||||
voltage = raw_adc * 3.3 / 4095.0;
|
|
||||||
Serial1.print(" ADC: ");
|
|
||||||
Serial1.print(raw_adc);
|
|
||||||
Serial1.print(" -> Voltage: ");
|
|
||||||
Serial1.print(voltage, 3);
|
|
||||||
Serial1.println("V");
|
|
||||||
|
|
||||||
// حالت 2: اتصال به GND
|
|
||||||
Serial1.println("2. Short MQ-7 output to GND:");
|
|
||||||
Serial1.println(" (Connect AOUT to GND temporarily)");
|
|
||||||
delay(5000);
|
|
||||||
raw_adc = analogRead(ADC_PIN);
|
|
||||||
voltage = raw_adc * 3.3 / 4095.0;
|
|
||||||
Serial1.print(" ADC: ");
|
|
||||||
Serial1.print(raw_adc);
|
|
||||||
Serial1.print(" -> Voltage: ");
|
|
||||||
Serial1.print(voltage, 3);
|
|
||||||
Serial1.println("V");
|
|
||||||
|
|
||||||
// حالت 3: اتصال به 3.3V
|
|
||||||
Serial1.println("3. Short MQ-7 output to 3.3V:");
|
|
||||||
Serial1.println(" (Connect AOUT to 3.3V temporarily)");
|
|
||||||
delay(5000);
|
|
||||||
raw_adc = analogRead(ADC_PIN);
|
|
||||||
voltage = raw_adc * 3.3 / 4095.0;
|
|
||||||
Serial1.print(" ADC: ");
|
|
||||||
Serial1.print(raw_adc);
|
|
||||||
Serial1.print(" -> Voltage: ");
|
|
||||||
Serial1.print(voltage, 3);
|
|
||||||
Serial1.println("V");
|
|
||||||
|
|
||||||
Serial1.println("=== End Test ===");
|
|
||||||
}
|
|
||||||
|
|
||||||
float readCO() {
|
bool networkConnected = false;
|
||||||
// خواندن مستقیم ADC
|
int displayMode = 0;
|
||||||
raw_adc = analogRead(ADC_PIN);
|
String lastError = "";
|
||||||
voltage = raw_adc * 3.3 / 4095.0;
|
String currentAPN = "";
|
||||||
|
String lastMessage = "";
|
||||||
// اگر ولتاژ خیلی کم یا زیاد باشد
|
|
||||||
if (voltage < 0.1) {
|
|
||||||
return 0.1; // حداقل مقدار
|
|
||||||
}
|
|
||||||
if (voltage > 4.9) {
|
|
||||||
return 1000; // حداکثر مقدار
|
|
||||||
}
|
|
||||||
|
|
||||||
// محاسبه Rs (مقاومت سنسور)
|
|
||||||
float Rs = 10000.0 * (5.0 - voltage) / voltage;
|
|
||||||
|
|
||||||
// فرض R0 ثابت (میتوانید کالیبره کنید)
|
|
||||||
float R0 = 10000.0;
|
|
||||||
float ratio = Rs / R0;
|
|
||||||
|
|
||||||
// محاسبه PPM با فرمول MQ-7
|
|
||||||
float ppm = 99.042 * pow(ratio, -1.518);
|
|
||||||
|
|
||||||
// محدود کردن خروجی
|
|
||||||
if (ppm < 0.5) ppm = 0.5;
|
|
||||||
if (ppm > 1000) ppm = 1000;
|
|
||||||
|
|
||||||
return ppm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// وضعیت سنسورها و اتصالات
|
||||||
|
bool sht31Connected = false;
|
||||||
|
bool lightSensorConnected = false;
|
||||||
|
bool coSensorConnected = false;
|
||||||
|
bool simConnected = false;
|
||||||
|
bool networkRegistered = false;
|
||||||
|
int signalStrength = 0;
|
||||||
|
|
||||||
|
// متغیرهای CO کالیبراسیون
|
||||||
|
unsigned long systemStartTime = 0;
|
||||||
|
const long calibrationPeriod = 600000; // 10 دقیقه
|
||||||
|
bool calibrationComplete = false;
|
||||||
|
float MQ7_R0 = 10000.0;
|
||||||
|
|
||||||
|
bool isInCall = false;
|
||||||
|
|
||||||
|
|
||||||
|
bool powerState = false;
|
||||||
|
|
||||||
|
// -------------------- Setup --------------------
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial1.begin(115200);
|
Serial1.begin(115200);
|
||||||
Serial1.println("\n=== STM32 4-Sensor Monitor ===");
|
|
||||||
|
|
||||||
// تنظیم پینها
|
pinMode(PWRKEY_PIN, OUTPUT);
|
||||||
pinMode(LED_PIN, OUTPUT);
|
digitalWrite(PWRKEY_PIN, LOW);
|
||||||
digitalWrite(LED_PIN, HIGH); // روشن برای نشان دادن فعالیت
|
|
||||||
|
pinMode(POWER_PIN, INPUT);
|
||||||
|
|
||||||
|
powerState = digitalRead(POWER_PIN) == HIGH;
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
// تنظیم ADC
|
Serial1.println("\n\n🚀 IoT Device Starting...");
|
||||||
analogReadResolution(12); // برای STM32 مهم است!
|
|
||||||
|
|
||||||
// تنظیم I2C
|
systemStartTime = millis();
|
||||||
Wire.setSDA(I2C_SDA);
|
|
||||||
Wire.setSCL(I2C_SCL);
|
pinMode(MQ7_PIN, INPUT);
|
||||||
|
|
||||||
|
Wire.setSDA(SDA_PIN);
|
||||||
|
Wire.setSCL(SCL_PIN);
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
|
|
||||||
// OLED
|
// 1. ولتاژ را تست کن
|
||||||
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
|
bool voltageOK = voltageReader.testCircuit();
|
||||||
Serial1.println("OLED not found!");
|
|
||||||
while(1);
|
if (!voltageOK) {
|
||||||
|
Serial1.println("⚠️ Voltage issue detected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// SHT31
|
// 2. اطلاعات دیباگ ولتاژ
|
||||||
if (!sht31.begin(0x44)) {
|
voltageReader.debugInfo();
|
||||||
Serial1.println("SHT31 not found!");
|
|
||||||
|
// 3. خواندن اولیه ولتاژ
|
||||||
|
float initialVoltage = voltageReader.readVoltage();
|
||||||
|
Serial1.print("Initial voltage: ");
|
||||||
|
Serial1.print(initialVoltage, 2);
|
||||||
|
Serial1.println("V");
|
||||||
|
|
||||||
|
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
|
||||||
|
Serial1.println("❌ OLED failed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// BH1750
|
sht31.begin(0x44);
|
||||||
if (!lightMeter.begin()) {
|
lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
|
||||||
Serial1.println("BH1750 not found!");
|
|
||||||
|
flashInit();
|
||||||
|
readConfig();
|
||||||
|
|
||||||
|
calibrateMQ7();
|
||||||
|
|
||||||
|
awaitingSMS2 = false;
|
||||||
|
tempPhoneNumber = "";
|
||||||
|
tempTokenCode = "";
|
||||||
|
|
||||||
|
EC200U.begin(115200);
|
||||||
|
initEC200U();
|
||||||
|
if (config.verified) {
|
||||||
|
Serial1.println("Device ID: " + String(config.deviceId));
|
||||||
|
currentAPN = simTypeToAPN(config.simType);
|
||||||
|
//initEC200U();
|
||||||
} else {
|
} else {
|
||||||
lightMeter.configure(BH1750::CONTINUOUS_HIGH_RES_MODE);
|
Serial1.println("⚠️ Not configured - Waiting for SMS");
|
||||||
}
|
}
|
||||||
|
|
||||||
// تست اولیه ADC
|
readSensors();
|
||||||
testADC();
|
|
||||||
|
|
||||||
// نمایش صفحه شروع
|
updateNetworkStatus();
|
||||||
display.clearDisplay();
|
lastNetworkStatusUpdate = millis();
|
||||||
display.setTextSize(1);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.setCursor(20, 20);
|
|
||||||
display.println("ADC Test Mode");
|
|
||||||
display.setCursor(30, 35);
|
|
||||||
display.println("Check Serial");
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
Serial1.println("\nSend 't' to test MQ-7 connection");
|
Serial1.println("✅ Ready - CO calibration: " + String(calibrationComplete ? "Complete" : "In progress"));
|
||||||
Serial1.println("Send 'r' to read normal values");
|
|
||||||
|
if (config.verified && networkConnected) {
|
||||||
|
Serial1.println("\n🚀 First upload - sending data immediately...");
|
||||||
|
uploadData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------- Loop --------------------
|
||||||
void loop() {
|
void loop() {
|
||||||
// بررسی دستورات سریال
|
if (!isInCall) {
|
||||||
if (Serial1.available()) {
|
digitalWrite(PWRKEY_PIN, LOW);
|
||||||
char cmd = Serial1.read();
|
}
|
||||||
|
|
||||||
|
bool currentPower = digitalRead(POWER_PIN) == HIGH;
|
||||||
|
|
||||||
|
if (currentPower != powerState) {
|
||||||
|
|
||||||
if (cmd == 't' || cmd == 'T') {
|
powerState = currentPower;
|
||||||
checkMQ7Connection();
|
|
||||||
|
if (powerState) {
|
||||||
|
Serial.println("5V Connected");
|
||||||
|
} else {
|
||||||
|
Serial.println("5V Disconnected");
|
||||||
}
|
}
|
||||||
if (cmd == 'r' || cmd == 'R') {
|
lastUpload -= 86400000L;
|
||||||
// خواندن حالت عادی
|
}
|
||||||
temperature = sht31.readTemperature();
|
|
||||||
humidity = sht31.readHumidity();
|
checkSMS();
|
||||||
light = lightMeter.readLightLevel();
|
|
||||||
co_ppm = readCO();
|
if (!calibrationComplete && (millis() - systemStartTime >= calibrationPeriod)) {
|
||||||
|
calibrationComplete = true;
|
||||||
Serial1.print("\nTemp: ");
|
Serial1.println("✅ CO calibration period completed!");
|
||||||
Serial1.print(temperature, 1);
|
}
|
||||||
Serial1.print("C, Hum: ");
|
|
||||||
Serial1.print(humidity, 1);
|
if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) {
|
||||||
Serial1.print("%, Light: ");
|
readSensors();
|
||||||
Serial1.print(light, 0);
|
|
||||||
Serial1.print("lx, CO: ");
|
float voltage = voltageReader.readVoltage();
|
||||||
Serial1.print(co_ppm, 1);
|
Serial1.print("📊 System Voltage: ");
|
||||||
Serial1.print("ppm, ADC: ");
|
Serial1.print(voltage, 2);
|
||||||
Serial1.print(raw_adc);
|
Serial1.println("V");
|
||||||
Serial1.print(" (");
|
|
||||||
Serial1.print(voltage, 3);
|
currentData.volage=voltage;
|
||||||
Serial1.println("V)");
|
|
||||||
|
if(currentPower)
|
||||||
|
currentData.power=1;
|
||||||
|
else
|
||||||
|
currentData.power=0;
|
||||||
|
|
||||||
|
unsigned long uploadIntervalMs = config.uploadInterval * 60000UL;
|
||||||
|
if (millis() - lastUpload > uploadIntervalMs) {
|
||||||
|
Serial1.print("Upload interval reached (");
|
||||||
|
Serial1.print(config.uploadInterval);
|
||||||
|
Serial1.println(" min), starting upload...");
|
||||||
|
uploadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// خواندن و نمایش معمول
|
updateDisplay();
|
||||||
temperature = sht31.readTemperature();
|
delay(100);
|
||||||
humidity = sht31.readHumidity();
|
}
|
||||||
light = lightMeter.readLightLevel();
|
|
||||||
co_ppm = readCO();
|
|
||||||
|
|
||||||
// نمایش روی OLED
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
|
|
||||||
// عنوان
|
|
||||||
display.setCursor(40, 0);
|
|
||||||
display.println("SENSORS");
|
|
||||||
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
|
|
||||||
|
|
||||||
// دما
|
|
||||||
display.setCursor(0, 15);
|
|
||||||
display.print("Temp: ");
|
|
||||||
display.print(temperature, 1);
|
|
||||||
display.print("C");
|
|
||||||
|
|
||||||
// رطوبت
|
|
||||||
display.setCursor(0, 25);
|
|
||||||
display.print("Hum: ");
|
|
||||||
display.print(humidity, 1);
|
|
||||||
display.print("%");
|
|
||||||
|
|
||||||
// نور
|
|
||||||
display.setCursor(0, 35);
|
|
||||||
display.print("Light:");
|
|
||||||
if (light < 1000) {
|
|
||||||
display.print(light, 0);
|
|
||||||
} else {
|
|
||||||
display.print(light / 1000, 1);
|
|
||||||
display.print("k");
|
|
||||||
}
|
|
||||||
display.print("lx");
|
|
||||||
|
|
||||||
// CO با نمایش ولتاژ
|
|
||||||
display.setCursor(0, 45);
|
|
||||||
display.print("CO: ");
|
|
||||||
display.print(co_ppm, 1);
|
|
||||||
display.print("ppm");
|
|
||||||
|
|
||||||
// نمایش ADC و ولتاژ در پایین
|
|
||||||
display.drawLine(0, 55, 128, 55, SSD1306_WHITE);
|
|
||||||
display.setCursor(0, 58);
|
|
||||||
display.print("ADC:");
|
|
||||||
display.print(raw_adc);
|
|
||||||
display.print(" V:");
|
|
||||||
display.print(voltage, 2);
|
|
||||||
display.print("V");
|
|
||||||
|
|
||||||
// چشمک زدن LED برای نشان دادن فعالیت
|
|
||||||
static unsigned long lastBlink = 0;
|
|
||||||
if (millis() - lastBlink > 1000) {
|
|
||||||
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
|
|
||||||
lastBlink = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
display.display();
|
|
||||||
delay(500);
|
|
||||||
}
|
|
||||||
|
|||||||
142
Voltage_Reader.h
Normal file
142
Voltage_Reader.h
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#ifndef VOLTAGE_READER_H
|
||||||
|
#define VOLTAGE_READER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
class VoltageReader {
|
||||||
|
private:
|
||||||
|
float filteredVoltage = 0.0;
|
||||||
|
bool firstReading = true; // فلگ برای اولین خواندن
|
||||||
|
const float alpha = 0.3; // ضریب فیلتر را کمی بیشتر کردم
|
||||||
|
|
||||||
|
public:
|
||||||
|
VoltageReader() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
pinMode(VOLTAGE_DIVIDER_PIN, INPUT_ANALOG);
|
||||||
|
analogReadResolution(12); // تنظیم رزولوشن ADC به 12-bit
|
||||||
|
delay(100); // کمی بیشتر صبر کن
|
||||||
|
|
||||||
|
// چند بار خواندن برای تخلیه خازنهای داخلی
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
analogRead(VOLTAGE_DIVIDER_PIN);
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// خواندن ولتاژ بدون فیلتر
|
||||||
|
float readRawVoltage() {
|
||||||
|
int samples = 32; // تعداد نمونهها را بیشتر کردم
|
||||||
|
long sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < samples; i++) {
|
||||||
|
sum += analogRead(VOLTAGE_DIVIDER_PIN);
|
||||||
|
delayMicroseconds(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rawValue = sum / samples;
|
||||||
|
|
||||||
|
// تبدیل به ولتاژ
|
||||||
|
float voltageAtPin = (rawValue * ADC_REF_VOLTAGE) / ADC_RESOLUTION;
|
||||||
|
|
||||||
|
// محاسبه ولتاژ اصلی
|
||||||
|
float actualVoltage = voltageAtPin * VOLTAGE_DIVIDER_RATIO;
|
||||||
|
|
||||||
|
return actualVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// خواندن با فیلتر
|
||||||
|
float readVoltage() {
|
||||||
|
float currentVoltage = readRawVoltage();
|
||||||
|
|
||||||
|
// اگر اولین بار است، فیلتر را با مقدار فعلی مقداردهی کن
|
||||||
|
if (firstReading) {
|
||||||
|
filteredVoltage = currentVoltage;
|
||||||
|
firstReading = false;
|
||||||
|
} else {
|
||||||
|
// اعمال فیلتر Low-pass
|
||||||
|
filteredVoltage = (alpha * currentVoltage) + ((1 - alpha) * filteredVoltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentVoltage; // actual voltage برمیگردانیم
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت ولتاژ فیلتر شده
|
||||||
|
float getFilteredVoltage() {
|
||||||
|
return filteredVoltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// تست سلامت مدار
|
||||||
|
bool testCircuit() {
|
||||||
|
Serial1.println("\n🔌 Testing voltage divider circuit...");
|
||||||
|
|
||||||
|
// چند بار خواندن برای اطمینان
|
||||||
|
float sum = 0;
|
||||||
|
int readings = 10;
|
||||||
|
|
||||||
|
for (int i = 0; i < readings; i++) {
|
||||||
|
sum += readRawVoltage();
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
float avgVoltage = sum / readings;
|
||||||
|
|
||||||
|
Serial1.print("Average voltage: ");
|
||||||
|
Serial1.print(avgVoltage, 2);
|
||||||
|
Serial1.println("V");
|
||||||
|
|
||||||
|
Serial1.print("Raw ADC value: ");
|
||||||
|
Serial1.println(analogRead(VOLTAGE_DIVIDER_PIN));
|
||||||
|
|
||||||
|
// ولتاژ در پین
|
||||||
|
float pinVoltage = avgVoltage / VOLTAGE_DIVIDER_RATIO;
|
||||||
|
Serial1.print("Voltage at PA0 pin: ");
|
||||||
|
Serial1.print(pinVoltage, 2);
|
||||||
|
Serial1.println("V");
|
||||||
|
|
||||||
|
if (avgVoltage > 4.5 && avgVoltage < 5.5) {
|
||||||
|
Serial1.println("✅ Circuit OK (within expected 5V ±10%)");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Serial1.print("❌ Expected ~5V, got ");
|
||||||
|
Serial1.print(avgVoltage, 2);
|
||||||
|
Serial1.println("V");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// نمایش اطلاعات دیباگ
|
||||||
|
void debugInfo() {
|
||||||
|
float rawVoltage = readRawVoltage();
|
||||||
|
int rawADC = analogRead(VOLTAGE_DIVIDER_PIN);
|
||||||
|
float pinVoltage = rawVoltage / VOLTAGE_DIVIDER_RATIO;
|
||||||
|
|
||||||
|
Serial1.println("\n📊 Voltage Debug Info:");
|
||||||
|
Serial1.print("Raw ADC value: ");
|
||||||
|
Serial1.println(rawADC);
|
||||||
|
Serial1.print("Voltage at PA0 pin: ");
|
||||||
|
Serial1.print(pinVoltage, 3);
|
||||||
|
Serial1.println("V");
|
||||||
|
Serial1.print("Actual voltage (raw): ");
|
||||||
|
Serial1.print(rawVoltage, 3);
|
||||||
|
Serial1.println("V");
|
||||||
|
Serial1.print("Filtered voltage: ");
|
||||||
|
Serial1.print(filteredVoltage, 3);
|
||||||
|
Serial1.println("V");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ریست فیلتر
|
||||||
|
void resetFilter() {
|
||||||
|
filteredVoltage = 0.0;
|
||||||
|
firstReading = true;
|
||||||
|
Serial1.println("✅ Voltage filter reset");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ایجاد نمونه سراسری
|
||||||
|
VoltageReader voltageReader;
|
||||||
|
|
||||||
|
#endif // VOLTAGE_READER_H
|
||||||
@@ -1,519 +0,0 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include <Adafruit_GFX.h>
|
|
||||||
#include <Adafruit_SSD1306.h>
|
|
||||||
#include <Adafruit_SHT31.h>
|
|
||||||
#include <BH1750.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
|
|
||||||
// -------------------- پیکربندی حافظه SPI Flash --------------------
|
|
||||||
#define FLASH_CS PA4
|
|
||||||
#define FLASH_MOSI PA7
|
|
||||||
#define FLASH_MISO PA6
|
|
||||||
#define FLASH_SCK PA5
|
|
||||||
|
|
||||||
// کتابخانه جایگزین برای حافظه SPI
|
|
||||||
#include <SerialFlash.h>
|
|
||||||
|
|
||||||
// -------------------- پیکربندی OLED --------------------
|
|
||||||
#define SCREEN_WIDTH 128
|
|
||||||
#define SCREEN_HEIGHT 64
|
|
||||||
#define OLED_RESET -1
|
|
||||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
||||||
|
|
||||||
// -------------------- پیکربندی سنسورها --------------------
|
|
||||||
#define SOIL_PIN PA1
|
|
||||||
#define MQ7_PIN PA2
|
|
||||||
#define SDA_PIN PB9
|
|
||||||
#define SCL_PIN PB8
|
|
||||||
|
|
||||||
BH1750 lightMeter;
|
|
||||||
Adafruit_SHT31 sht31 = Adafruit_SHT31();
|
|
||||||
|
|
||||||
// -------------------- پیکربندی EC200U --------------------
|
|
||||||
#define PWRKEY_PIN PB5
|
|
||||||
HardwareSerial EC200U(USART3);
|
|
||||||
|
|
||||||
// -------------------- پارامترهای قابل تنظیم --------------------
|
|
||||||
#define SENSOR_READ_INTERVAL 60000 // خواندن سنسورها هر 1 دقیقه
|
|
||||||
|
|
||||||
// -------------------- ساختار تنظیمات --------------------
|
|
||||||
struct DeviceConfig {
|
|
||||||
char signature[4];
|
|
||||||
char apn[32];
|
|
||||||
char serverUrl[64];
|
|
||||||
char deviceName[16];
|
|
||||||
char smsNumber[16];
|
|
||||||
unsigned long webInterval; // دقیقه
|
|
||||||
unsigned long smsInterval; // دقیقه
|
|
||||||
unsigned long saveInterval; // ثانیه - فاصله ذخیره در قطعی
|
|
||||||
bool smsEnabled;
|
|
||||||
bool valid;
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------- ساختار داده سنسورها --------------------
|
|
||||||
struct SensorData {
|
|
||||||
float temperature;
|
|
||||||
float humidity;
|
|
||||||
int soilMoisture;
|
|
||||||
float coPPM;
|
|
||||||
float lightLux;
|
|
||||||
unsigned long timestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------------------- متغیرهای سراسری --------------------
|
|
||||||
DeviceConfig config;
|
|
||||||
SensorData currentData;
|
|
||||||
SensorData storedData;
|
|
||||||
|
|
||||||
unsigned long lastSensorRead = 0;
|
|
||||||
unsigned long lastUpload = 0;
|
|
||||||
unsigned long lastSMS = 0;
|
|
||||||
unsigned long lastSave = 0;
|
|
||||||
bool networkConnected = false;
|
|
||||||
bool dataStored = false;
|
|
||||||
int displayMode = 0;
|
|
||||||
unsigned long lastDisplayChange = 0;
|
|
||||||
|
|
||||||
// -------------------- توابع حافظه SPI Flash --------------------
|
|
||||||
void initFlash() {
|
|
||||||
SPI.setMOSI(FLASH_MOSI);
|
|
||||||
SPI.setMISO(FLASH_MISO);
|
|
||||||
SPI.setSCLK(FLASH_SCK);
|
|
||||||
SPI.begin();
|
|
||||||
|
|
||||||
pinMode(FLASH_CS, OUTPUT);
|
|
||||||
digitalWrite(FLASH_CS, HIGH);
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
|
|
||||||
// مقداردهی اولیه SerialFlash
|
|
||||||
if (!SerialFlash.begin(FLASH_CS)) {
|
|
||||||
Serial1.println("❌ Flash init failed!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial1.println("✅ Flash OK");
|
|
||||||
|
|
||||||
// نمایش اطلاعات حافظه
|
|
||||||
SerialFlash::ID id;
|
|
||||||
SerialFlash.readID(&id);
|
|
||||||
Serial1.print("Flash ID: ");
|
|
||||||
Serial1.print(id.id, HEX);
|
|
||||||
Serial1.print(" Size: ");
|
|
||||||
Serial1.print(SerialFlash.capacity(id) / 1024);
|
|
||||||
Serial1.println("KB");
|
|
||||||
}
|
|
||||||
|
|
||||||
void readConfig() {
|
|
||||||
// ابتدا از EEPROM داخلی بخوان
|
|
||||||
EEPROM.get(0, config);
|
|
||||||
|
|
||||||
// اگر تنظیمات معتبر نیست، از حافظه SPI بخوان
|
|
||||||
if (strcmp(config.signature, "CFG") != 0) {
|
|
||||||
if (SerialFlash.exists("config.bin")) {
|
|
||||||
SerialFlashFile file = SerialFlash.open("config.bin");
|
|
||||||
if (file) {
|
|
||||||
file.read(&config, sizeof(DeviceConfig));
|
|
||||||
file.close();
|
|
||||||
Serial1.println("✅ Config read from SPI Flash");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// اگر هنوز تنظیمات معتبر نیست، مقادیر پیشفرض
|
|
||||||
if (strcmp(config.signature, "CFG") != 0) {
|
|
||||||
strcpy(config.signature, "CFG");
|
|
||||||
strcpy(config.apn, "mcinet");
|
|
||||||
strcpy(config.serverUrl, "https://ghback.nabaksoft.ir/api/Telemetry/AddData");
|
|
||||||
strcpy(config.smsNumber, "+989120000000");
|
|
||||||
strcpy(config.deviceName, "ST-");
|
|
||||||
config.webInterval = 1;
|
|
||||||
config.smsInterval = 5;
|
|
||||||
config.saveInterval = 60; // پیشفرض 60 ثانیه
|
|
||||||
config.smsEnabled = false;
|
|
||||||
config.valid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveConfig() {
|
|
||||||
config.valid = true;
|
|
||||||
|
|
||||||
// در EEPROM داخلی ذخیره کن
|
|
||||||
EEPROM.put(0, config);
|
|
||||||
EEPROM.commit();
|
|
||||||
|
|
||||||
// در حافظه SPI هم ذخیره کن
|
|
||||||
if (SerialFlash.exists("config.bin")) {
|
|
||||||
SerialFlash.remove("config.bin");
|
|
||||||
}
|
|
||||||
|
|
||||||
SerialFlashFile file = SerialFlash.create("config.bin", sizeof(DeviceConfig));
|
|
||||||
if (file) {
|
|
||||||
file.write((byte*)&config, sizeof(DeviceConfig));
|
|
||||||
file.close();
|
|
||||||
Serial1.println("✅ Config saved to SPI Flash");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveDataToFlash() {
|
|
||||||
if (!dataStored) {
|
|
||||||
if (SerialFlash.exists("data.bin")) {
|
|
||||||
SerialFlash.remove("data.bin");
|
|
||||||
}
|
|
||||||
dataStored = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentData.timestamp = millis();
|
|
||||||
|
|
||||||
SerialFlashFile file = SerialFlash.open("data.bin");
|
|
||||||
if (!file) {
|
|
||||||
file = SerialFlash.create("data.bin", sizeof(SensorData));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
file.seek(0);
|
|
||||||
file.write((byte*)¤tData, sizeof(SensorData));
|
|
||||||
file.close();
|
|
||||||
Serial1.println("💾 Data saved to flash");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void readDataFromFlash() {
|
|
||||||
if (SerialFlash.exists("data.bin")) {
|
|
||||||
SerialFlashFile file = SerialFlash.open("data.bin");
|
|
||||||
if (file) {
|
|
||||||
file.read(&storedData, sizeof(SensorData));
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
// بررسی اعتبار داده
|
|
||||||
if (storedData.timestamp > 0) {
|
|
||||||
Serial1.println("📖 Stored data found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearStoredData() {
|
|
||||||
if (SerialFlash.exists("data.bin")) {
|
|
||||||
SerialFlash.remove("data.bin");
|
|
||||||
storedData.timestamp = 0;
|
|
||||||
dataStored = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- توابع EC200U --------------------
|
|
||||||
bool sendAT(String cmd, uint16_t wait = 2000) {
|
|
||||||
Serial1.print(">> ");
|
|
||||||
Serial1.println(cmd);
|
|
||||||
EC200U.println(cmd);
|
|
||||||
|
|
||||||
String response = "";
|
|
||||||
unsigned long start = millis();
|
|
||||||
|
|
||||||
while (millis() - start < wait) {
|
|
||||||
while (EC200U.available()) {
|
|
||||||
char c = EC200U.read();
|
|
||||||
response += c;
|
|
||||||
Serial1.write(c);
|
|
||||||
}
|
|
||||||
if (response.indexOf("OK") != -1) return true;
|
|
||||||
if (response.indexOf("ERROR") != -1) return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool waitForResponse(String expected, unsigned long timeout = 10000) {
|
|
||||||
unsigned long startTime = millis();
|
|
||||||
String response = "";
|
|
||||||
|
|
||||||
while (millis() - startTime < timeout) {
|
|
||||||
while (EC200U.available()) {
|
|
||||||
char c = EC200U.read();
|
|
||||||
response += c;
|
|
||||||
Serial1.write(c);
|
|
||||||
if (response.indexOf(expected) != -1) return true;
|
|
||||||
if (response.indexOf("ERROR") != -1) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sendSMS(String number, String message) {
|
|
||||||
if (!sendAT("AT+CMGF=1", 2000)) return false;
|
|
||||||
|
|
||||||
String cmd = "AT+CMGS=\"" + number + "\"";
|
|
||||||
if (!sendAT(cmd, 2000)) return false;
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
EC200U.print(message);
|
|
||||||
EC200U.write(26);
|
|
||||||
return waitForResponse("+CMGS:", 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void initEC200U() {
|
|
||||||
Serial1.println("🚀 Starting EC200U...");
|
|
||||||
|
|
||||||
digitalWrite(PWRKEY_PIN, HIGH);
|
|
||||||
delay(2000);
|
|
||||||
digitalWrite(PWRKEY_PIN, LOW);
|
|
||||||
delay(5000);
|
|
||||||
|
|
||||||
if (!sendAT("AT", 3000)) {
|
|
||||||
Serial1.println("❌ EC200U not responding!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + String(config.apn) + "\"";
|
|
||||||
sendAT(apnCmd, 2000);
|
|
||||||
|
|
||||||
if (sendAT("AT+QIACT=1", 15000)) {
|
|
||||||
networkConnected = true;
|
|
||||||
Serial1.println("✅ Network connected");
|
|
||||||
|
|
||||||
// اگر داده ذخیره شده وجود دارد، ارسال کن
|
|
||||||
if (storedData.timestamp > 0) {
|
|
||||||
Serial1.println("📤 Sending stored data...");
|
|
||||||
sendStoredData();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
networkConnected = false;
|
|
||||||
Serial1.println("❌ Network failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- توابع سنسورها --------------------
|
|
||||||
void readSensors() {
|
|
||||||
currentData.temperature = sht31.readTemperature();
|
|
||||||
currentData.humidity = sht31.readHumidity();
|
|
||||||
currentData.soilMoisture = readSoil();
|
|
||||||
currentData.coPPM = MQ7_ReadPPM();
|
|
||||||
currentData.lightLux = lightMeter.readLightLevel();
|
|
||||||
|
|
||||||
if (isnan(currentData.temperature)) currentData.temperature = -999;
|
|
||||||
if (isnan(currentData.humidity)) currentData.humidity = -999;
|
|
||||||
if (isnan(currentData.lightLux)) currentData.lightLux = 0;
|
|
||||||
|
|
||||||
lastSensorRead = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
int readSoil() {
|
|
||||||
int adcValue = analogRead(SOIL_PIN);
|
|
||||||
const int adcDry = 4095;
|
|
||||||
const int adcWet = 1200;
|
|
||||||
|
|
||||||
float soilPercent = (float)(adcDry - adcValue) / (adcDry - adcWet) * 100.0;
|
|
||||||
if (soilPercent > 100.0) soilPercent = 100.0;
|
|
||||||
if (soilPercent < 0.0) soilPercent = 0.0;
|
|
||||||
|
|
||||||
return (int)soilPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
float MQ7_ReadPPM() {
|
|
||||||
long sum = 0;
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
sum += analogRead(MQ7_PIN);
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
float adcValue = sum / 10.0;
|
|
||||||
float voltage = (adcValue / 4095.0) * 3.3;
|
|
||||||
|
|
||||||
float ppm;
|
|
||||||
if (voltage < 0.1) ppm = 0;
|
|
||||||
else if (voltage < 0.2) ppm = 10 * (voltage / 0.2);
|
|
||||||
else if (voltage < 0.5) ppm = 50 + (voltage - 0.2) * 300;
|
|
||||||
else if (voltage < 1.0) ppm = 140 + (voltage - 0.5) * 800;
|
|
||||||
else if (voltage < 2.0) ppm = 500 + (voltage - 1.0) * 1500;
|
|
||||||
else ppm = 2000 + (voltage - 2.0) * 2000;
|
|
||||||
|
|
||||||
if (ppm < 0) ppm = 0;
|
|
||||||
if (ppm > 10000) ppm = 10000;
|
|
||||||
|
|
||||||
return ppm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- توابع ارسال داده --------------------
|
|
||||||
bool sendData(SensorData data) {
|
|
||||||
if (data.temperature == -999 || data.humidity == -999) return false;
|
|
||||||
|
|
||||||
String dataStr = "temperatureC=" + String(data.temperature, 1) +
|
|
||||||
"&humidityPercent=" + String(data.humidity, 1) +
|
|
||||||
"&soilPercent=" + String(data.soilMoisture) +
|
|
||||||
"&gasPPM=" + String(data.coPPM, 0) +
|
|
||||||
"&lux=" + String(data.lightLux, 1);
|
|
||||||
|
|
||||||
String url = String(config.serverUrl) + "?deviceName=" + String(config.deviceName) + "&" + dataStr;
|
|
||||||
|
|
||||||
String httpCmd = "AT+QHTTPURL=" + String(url.length()) + ",80";
|
|
||||||
if (!sendAT(httpCmd, 5000)) return false;
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
EC200U.print(url);
|
|
||||||
|
|
||||||
if (!waitForResponse("OK", 5000)) return false;
|
|
||||||
|
|
||||||
return sendAT("AT+QHTTPGET=60", 15000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendStoredData() {
|
|
||||||
if (sendData(storedData)) {
|
|
||||||
Serial1.println("✅ Stored data sent successfully");
|
|
||||||
clearStoredData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- توابع نمایش OLED --------------------
|
|
||||||
void updateDisplay() {
|
|
||||||
if (millis() - lastDisplayChange > 5000) {
|
|
||||||
displayMode = (displayMode + 1) % 3;
|
|
||||||
lastDisplayChange = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setTextSize(1);
|
|
||||||
|
|
||||||
switch(displayMode) {
|
|
||||||
case 0:
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.setCursor(20, 10);
|
|
||||||
display.println("DEVICE");
|
|
||||||
display.setCursor(30, 35);
|
|
||||||
display.println(config.deviceName);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.print("T:");
|
|
||||||
display.print(currentData.temperature, 1);
|
|
||||||
display.print("C ");
|
|
||||||
display.print("H:");
|
|
||||||
display.print(currentData.humidity, 1);
|
|
||||||
display.println("%");
|
|
||||||
|
|
||||||
display.setCursor(0, 16);
|
|
||||||
display.print("S:");
|
|
||||||
display.print(currentData.soilMoisture);
|
|
||||||
display.print("% ");
|
|
||||||
display.print("C:");
|
|
||||||
display.print(currentData.coPPM, 0);
|
|
||||||
display.println("P");
|
|
||||||
|
|
||||||
display.setCursor(0, 32);
|
|
||||||
display.print("L:");
|
|
||||||
display.print(currentData.lightLux, 0);
|
|
||||||
display.println("Lx");
|
|
||||||
|
|
||||||
display.setCursor(0, 48);
|
|
||||||
display.print(networkConnected ? "ONLINE" : "OFFLINE");
|
|
||||||
if (dataStored) display.print(" *");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
display.setTextSize(3);
|
|
||||||
display.setCursor(10, 25);
|
|
||||||
display.print(currentData.temperature, 1);
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.setCursor(100, 30);
|
|
||||||
display.print("C");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
display.display();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- setup --------------------
|
|
||||||
void setup() {
|
|
||||||
Serial1.begin(115200);
|
|
||||||
delay(1000);
|
|
||||||
|
|
||||||
Serial1.println("\n\n🚀 System Starting...");
|
|
||||||
|
|
||||||
pinMode(PWRKEY_PIN, OUTPUT);
|
|
||||||
pinMode(SOIL_PIN, INPUT);
|
|
||||||
pinMode(MQ7_PIN, INPUT);
|
|
||||||
digitalWrite(PWRKEY_PIN, LOW);
|
|
||||||
|
|
||||||
Wire.setSDA(SDA_PIN);
|
|
||||||
Wire.setSCL(SCL_PIN);
|
|
||||||
Wire.begin();
|
|
||||||
|
|
||||||
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
|
|
||||||
Serial1.println("❌ OLED failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
sht31.begin(0x44);
|
|
||||||
lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
|
|
||||||
|
|
||||||
// مقداردهی حافظه
|
|
||||||
initFlash();
|
|
||||||
readConfig();
|
|
||||||
readDataFromFlash();
|
|
||||||
|
|
||||||
// اگر نام دستگاه تنظیم نشده، از UID استفاده کن
|
|
||||||
if (strlen(config.deviceName) < 3) {
|
|
||||||
uint32_t uid0 = *(uint32_t*)0x1FFFF7E8;
|
|
||||||
uint32_t uid1 = *(uint32_t*)0x1FFFF7EC;
|
|
||||||
uint32_t uid2 = *(uint32_t*)0x1FFFF7F0;
|
|
||||||
|
|
||||||
sprintf(config.deviceName, "ST-%08X", (unsigned int)(uid0 ^ uid1 ^ uid2));
|
|
||||||
saveConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
EC200U.begin(115200);
|
|
||||||
if (config.valid) {
|
|
||||||
initEC200U();
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial1.println("✅ System Ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- loop --------------------
|
|
||||||
void loop() {
|
|
||||||
// خواندن سنسورها در بازه زمانی مشخص
|
|
||||||
if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) {
|
|
||||||
readSensors();
|
|
||||||
Serial1.println("📊 Sensors read");
|
|
||||||
|
|
||||||
// اگر شبکه وصل است، داده را ارسال کن
|
|
||||||
if (networkConnected) {
|
|
||||||
if (millis() - lastUpload > config.webInterval * 60000) {
|
|
||||||
if (sendData(currentData)) {
|
|
||||||
Serial1.println("✅ Web data sent");
|
|
||||||
lastUpload = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// اگر شبکه قطع است، داده را ذخیره کن
|
|
||||||
else {
|
|
||||||
if (config.saveInterval > 0) {
|
|
||||||
if (millis() - lastSave > config.saveInterval * 1000) {
|
|
||||||
saveDataToFlash();
|
|
||||||
lastSave = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ارسال SMS در صورت فعال بودن
|
|
||||||
if (config.smsEnabled && millis() - lastSMS > config.smsInterval * 60000) {
|
|
||||||
String smsMsg = String(config.deviceName) +
|
|
||||||
" T:" + String(currentData.temperature, 1) +
|
|
||||||
" H:" + String(currentData.humidity, 1) +
|
|
||||||
" S:" + String(currentData.soilMoisture) + "%";
|
|
||||||
|
|
||||||
if (sendSMS(config.smsNumber, smsMsg)) {
|
|
||||||
Serial1.println("✅ SMS sent");
|
|
||||||
lastSMS = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDisplay();
|
|
||||||
|
|
||||||
// تلاش برای اتصال مجدد شبکه
|
|
||||||
static unsigned long lastReconnect = 0;
|
|
||||||
if (!networkConnected && millis() - lastReconnect > 60000) {
|
|
||||||
initEC200U();
|
|
||||||
lastReconnect = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include <BH1750.h>
|
|
||||||
#include "DHT.h"
|
|
||||||
|
|
||||||
// --------------------------- تنظیمات ---------------------------
|
|
||||||
#define DHTPIN A0
|
|
||||||
#define DHTTYPE DHT22
|
|
||||||
DHT dht(DHTPIN, DHTTYPE);
|
|
||||||
|
|
||||||
#define SOIL_PIN A1
|
|
||||||
|
|
||||||
#define MQ7_PIN A2
|
|
||||||
#define VREF 5.0 // ولتاژ مرجع ADC
|
|
||||||
#define ADC_MAX 4095.0 // برای STM32 (۱۲ بیتی)
|
|
||||||
|
|
||||||
#define SDA_PIN PB9
|
|
||||||
#define SCL_PIN PB8
|
|
||||||
BH1750 lightMeter;
|
|
||||||
|
|
||||||
// UART EC200U
|
|
||||||
HardwareSerial EC200U(1);
|
|
||||||
#define PWRKEY_PIN PB5
|
|
||||||
|
|
||||||
// ارسال هر n دقیقه
|
|
||||||
#define UPLOAD_INTERVAL_MIN 1
|
|
||||||
|
|
||||||
// ذخیره ساعت شبکه
|
|
||||||
String networkTime = "";
|
|
||||||
|
|
||||||
// --------------------------- توابع ---------------------------
|
|
||||||
|
|
||||||
// روشن کردن ماژول EC200U
|
|
||||||
void powerEC200U() {
|
|
||||||
pinMode(PWRKEY_PIN, OUTPUT);
|
|
||||||
digitalWrite(PWRKEY_PIN, HIGH);
|
|
||||||
delay(1000); // نگه داشتن HIGH حدود 1 ثانیه برای روشن کردن
|
|
||||||
digitalWrite(PWRKEY_PIN, LOW);
|
|
||||||
delay(2000); // کمی صبر قبل از AT
|
|
||||||
}
|
|
||||||
|
|
||||||
// ارسال دستور AT و نمایش پاسخ
|
|
||||||
void sendAT(String cmd, uint16_t wait = 1000) {
|
|
||||||
Serial.print(">> "); Serial.println(cmd);
|
|
||||||
EC200U.println(cmd);
|
|
||||||
unsigned long start = millis();
|
|
||||||
while (millis() - start < wait) {
|
|
||||||
while (EC200U.available()) Serial.write(EC200U.read());
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
// آمادهسازی ماژول و اینترنت
|
|
||||||
void initEC200U() {
|
|
||||||
Serial.println("🚀 Initializing EC200U...");
|
|
||||||
sendAT("AT",1000);
|
|
||||||
sendAT("ATI",1000);
|
|
||||||
sendAT("AT+CPIN?",1000);
|
|
||||||
sendAT("AT+CSQ",1000);
|
|
||||||
sendAT("AT+CREG?",1000);
|
|
||||||
sendAT("AT+CGATT=1",2000);
|
|
||||||
sendAT("AT+CGDCONT=1,\"IP\",\"mcinet\"",1000);
|
|
||||||
sendAT("AT+QIACT=1",5000);
|
|
||||||
sendAT("AT+QIACT?",2000);
|
|
||||||
Serial.println("✅ EC200U Ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
// خواندن ساعت شبکه
|
|
||||||
void getNetworkTime() {
|
|
||||||
sendAT("AT+QLTS=2", 3000);
|
|
||||||
networkTime = "";
|
|
||||||
unsigned long t = millis();
|
|
||||||
while (millis()-t < 3000) {
|
|
||||||
while(EC200U.available()) {
|
|
||||||
char c = EC200U.read();
|
|
||||||
Serial.write(c);
|
|
||||||
networkTime += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// خواندن سنسورها
|
|
||||||
float readDHT22() { return dht.readTemperature(); }
|
|
||||||
float readHumidity() { return dht.readHumidity(); }
|
|
||||||
|
|
||||||
// رطوبت خاک به درصد
|
|
||||||
int readSoilPercent() {
|
|
||||||
int val = analogRead(SOIL_PIN); // 0-4095
|
|
||||||
int perc = map(val, 3000, 1500, 0, 100); // خشک: 3000, خیس:1500 (تنظیم شود)
|
|
||||||
if(perc>100) perc=100;
|
|
||||||
if(perc<0) perc=0;
|
|
||||||
return perc;
|
|
||||||
}
|
|
||||||
|
|
||||||
float readMQ7ppm() {
|
|
||||||
// --- 1. میانگینگیری چند قرائت برای پایداری ---
|
|
||||||
long sum = 0;
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
sum += analogRead(MQ7_PIN);
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
float adcValue = sum / 10.0;
|
|
||||||
|
|
||||||
// --- 2. محاسبه ولتاژ خروجی ---
|
|
||||||
float voltage = (adcValue / ADC_MAX) * VREF;
|
|
||||||
|
|
||||||
// --- 3. تبدیل تقریبی به ppm ---
|
|
||||||
// منحنی تجربی بر اساس تست ماژولهای MQ7 آماده (ولتاژ به ppm CO)
|
|
||||||
// این ضرایب با ولتاژ 0.1V ~ 1V برای ppm=50~5000 کالیبره شدهاند.
|
|
||||||
// اگر مقدار خروجی بیش از 4V باشد، سنسور اشباع است.
|
|
||||||
float ppm;
|
|
||||||
|
|
||||||
if (voltage < 0.1) ppm = 0; // خیلی پاک
|
|
||||||
else if (voltage < 0.2) ppm = 10 * (voltage / 0.2); // محدوده پایین
|
|
||||||
else if (voltage < 0.5) ppm = 50 + (voltage - 0.2) * 300; // 50–140ppm
|
|
||||||
else if (voltage < 1.0) ppm = 140 + (voltage - 0.5) * 800; // تا حدود 500ppm
|
|
||||||
else if (voltage < 2.0) ppm = 500 + (voltage - 1.0) * 1500; // تا حدود 2000ppm
|
|
||||||
else ppm = 2000 + (voltage - 2.0) * 2000; // تا ~4000ppm
|
|
||||||
|
|
||||||
// --- 4. محدود کردن مقدار ---
|
|
||||||
if (ppm < 0) ppm = 0;
|
|
||||||
if (ppm > 10000) ppm = 10000;
|
|
||||||
|
|
||||||
// --- 5. نمایش برای دیباگ ---
|
|
||||||
Serial.print("MQ7 voltage: ");
|
|
||||||
Serial.print(voltage, 3);
|
|
||||||
Serial.print(" V, CO ≈ ");
|
|
||||||
Serial.print(ppm, 1);
|
|
||||||
Serial.println(" ppm");
|
|
||||||
|
|
||||||
return ppm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
float readLight() { return lightMeter.readLightLevel(); }
|
|
||||||
|
|
||||||
// ارسال داده HTTP GET
|
|
||||||
void sendData(float temp, float hum, int soilPerc, float mq7ppm, float lux, String timeStr) {
|
|
||||||
String dataStr = String(temp,1) + "," + String(hum,1) + "," + String(soilPerc) + "," + String(mq7ppm,0) + "," + String(lux,1) + "," + timeStr;
|
|
||||||
// 'http://localhost:5064/api/Telemetry/AddData?deviceName=dr110&temperatureC=24.5&humidityPercent=26.3&soilPercent=12&gasPPM=2&lux=103'
|
|
||||||
String url = "http://nabaksoft.ir/api/Telemetry/AddData?deviceName=dr110&temperatureC="+String(temp,1)
|
|
||||||
+"&humidityPercent="+String(hum,1)+"&soilPercent="+String(soilPerc)+"&gasPPM="+String(mq7ppm,0)+"&lux="+String(lux,1);
|
|
||||||
|
|
||||||
Serial.println("🌐 Sending data: " + dataStr);
|
|
||||||
|
|
||||||
sendAT("AT+QHTTPURL=" + String(url.length()) + ",80", 500);
|
|
||||||
delay(500);
|
|
||||||
EC200U.print(url);
|
|
||||||
delay(1000);
|
|
||||||
sendAT("AT+QHTTPGET=80", 5000);
|
|
||||||
sendAT("AT+QHTTPREAD=80", 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------- راهاندازی ---------------------------
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
delay(1000);
|
|
||||||
Serial.println("🚀 STM32 + EC200U + Sensors");
|
|
||||||
|
|
||||||
// روشن کردن ماژول EC200U
|
|
||||||
powerEC200U();
|
|
||||||
|
|
||||||
// UART EC200U
|
|
||||||
EC200U.setRx(PA10);
|
|
||||||
EC200U.setTx(PA9);
|
|
||||||
EC200U.begin(115200);
|
|
||||||
delay(1000);
|
|
||||||
|
|
||||||
// سنسورها
|
|
||||||
dht.begin();
|
|
||||||
Wire.setSDA(SDA_PIN);
|
|
||||||
Wire.setSCL(SCL_PIN);
|
|
||||||
Wire.begin();
|
|
||||||
if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
|
|
||||||
Serial.println("BH1750 Initialized successfully!");
|
|
||||||
} else {
|
|
||||||
Serial.println("Error initializing BH1750!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// آماده سازی ماژول و اینترنت
|
|
||||||
initEC200U();
|
|
||||||
|
|
||||||
// خواندن ساعت شبکه
|
|
||||||
getNetworkTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------- حلقه اصلی ---------------------------
|
|
||||||
unsigned long lastUpload = 0;
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
if (now - lastUpload > UPLOAD_INTERVAL_MIN*60UL*1000UL || lastUpload==0) {
|
|
||||||
// خواندن سنسورها
|
|
||||||
float temp = readDHT22();
|
|
||||||
float hum = readHumidity();
|
|
||||||
int soil = readSoilPercent();
|
|
||||||
float mq7 = readMQ7ppm();
|
|
||||||
float lux = readLight();
|
|
||||||
|
|
||||||
// دریافت ساعت شبکه
|
|
||||||
getNetworkTime();
|
|
||||||
|
|
||||||
// ارسال به سرور
|
|
||||||
sendData(temp, hum, soil, mq7, lux, networkTime);
|
|
||||||
|
|
||||||
lastUpload = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(500);
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
#include <SoftwareSerial.h>
|
|
||||||
|
|
||||||
SoftwareSerial esp8266(4, 5); // RX, TX
|
|
||||||
|
|
||||||
// صفحه پیشفرض HTML
|
|
||||||
const char* HTML_PAGE = "<!DOCTYPE html><html><body><h2>Hello from ESP8266</h2></body></html>";
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
esp8266.begin(9600);
|
|
||||||
|
|
||||||
delay(2000); // آمادهسازی ESP
|
|
||||||
|
|
||||||
// تنظیمات اولیه ESP8266
|
|
||||||
sendCommand("AT", "OK", 2000);
|
|
||||||
sendCommand("ATE0", "OK", 2000);
|
|
||||||
sendCommand("AT+CWMODE=2", "OK", 2000);
|
|
||||||
sendCommand("AT+CWSAP=\"ESP8266_AP\",\"12345678\",1,0", "OK", 5000);
|
|
||||||
sendCommand("AT+CIPMUX=1", "OK", 3000);
|
|
||||||
sendCommand("AT+CIPSERVER=1,80", "OK", 3000);
|
|
||||||
|
|
||||||
Serial.println(F("ESP8266 Ready. Connect to SSID: ESP8266_AP, Pass: 12345678"));
|
|
||||||
Serial.println(F("Then open http://192.168.4.1/ in your browser"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
if (esp8266.available()) {
|
|
||||||
String line = esp8266.readStringUntil('\n');
|
|
||||||
line.trim();
|
|
||||||
if (line.length() == 0) return;
|
|
||||||
|
|
||||||
Serial.println("[ESP8266] " + line);
|
|
||||||
|
|
||||||
if (line.startsWith("+IPD,")) {
|
|
||||||
int len = 0;
|
|
||||||
int linkId = parseIPDLine(line, len);
|
|
||||||
if (linkId < 0) {
|
|
||||||
Serial.println("Failed to parse IPD line.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
drainPayload(len);
|
|
||||||
|
|
||||||
// فراخوانی تابع پردازش و ارسال پاسخ
|
|
||||||
processAndSend(linkId, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================== توابع کمکی ===================
|
|
||||||
|
|
||||||
// تابع پردازش داده و ارسال پاسخ به کلاینت
|
|
||||||
void processAndSend(int linkId, const String &requestLine) {
|
|
||||||
String html;
|
|
||||||
|
|
||||||
// بررسی پارامتر صفحه در URL
|
|
||||||
if (requestLine.indexOf("GET /?page=") != -1) {
|
|
||||||
int start = requestLine.indexOf("GET /?page=") + 10;
|
|
||||||
int end = requestLine.indexOf(" ", start);
|
|
||||||
int pageNum = requestLine.substring(start, end).toInt();
|
|
||||||
|
|
||||||
switch (pageNum) {
|
|
||||||
case 1:
|
|
||||||
html = "<!DOCTYPE html><html><body><h2>صفحه ۱</h2></body></html>";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
html = "<!DOCTYPE html><html><body><h2>صفحه ۲</h2></body></html>";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
html = "<!DOCTYPE html><html><body><h2>صفحه ۳</h2></body></html>";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
html = "<!DOCTYPE html><html><body><h2>صفحه پیشفرض</h2></body></html>";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html = HTML_PAGE; // صفحه پیشفرض
|
|
||||||
}
|
|
||||||
|
|
||||||
// ساخت پاسخ HTTP
|
|
||||||
String response = "HTTP/1.1 200 OK\r\n";
|
|
||||||
response += "Content-Type: text/html\r\n";
|
|
||||||
response += "Content-Length: " + String(html.length()) + "\r\n";
|
|
||||||
response += "Connection: close\r\n\r\n";
|
|
||||||
response += html;
|
|
||||||
|
|
||||||
// ارسال پاسخ
|
|
||||||
String cmd = "AT+CIPSEND=" + String(linkId) + "," + String(response.length());
|
|
||||||
if (sendCommand(cmd, ">", 5000)) {
|
|
||||||
esp8266.print(response);
|
|
||||||
|
|
||||||
if (waitFor("SEND OK", 5000)) {
|
|
||||||
Serial.println("Response sent to client.");
|
|
||||||
} else {
|
|
||||||
Serial.println("SEND OK not received (timeout).");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Serial.println("CIPSEND prompt not received (timeout).");
|
|
||||||
}
|
|
||||||
|
|
||||||
// بستن کانکشن
|
|
||||||
String closeCmd = "AT+CIPCLOSE=" + String(linkId);
|
|
||||||
sendCommand(closeCmd, "OK", 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// استخراج linkId و len از خط +IPD
|
|
||||||
int parseIPDLine(const String& line, int &lenOut) {
|
|
||||||
int ipdPos = line.indexOf("+IPD,");
|
|
||||||
if (ipdPos == -1) return -1;
|
|
||||||
|
|
||||||
int firstComma = line.indexOf(',', ipdPos + 4);
|
|
||||||
int secondComma = line.indexOf(',', firstComma + 1);
|
|
||||||
if (firstComma == -1 || secondComma == -1) return -1;
|
|
||||||
|
|
||||||
int colon = line.indexOf(':', secondComma + 1);
|
|
||||||
if (colon == -1) {
|
|
||||||
lenOut = line.substring(secondComma + 1).toInt();
|
|
||||||
return line.substring(ipdPos + 4, firstComma).toInt();
|
|
||||||
} else {
|
|
||||||
lenOut = line.substring(firstComma + 1, secondComma).toInt();
|
|
||||||
return line.substring(ipdPos + 4, firstComma).toInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// پاکسازی payload بعد از +IPD
|
|
||||||
void drainPayload(int len) {
|
|
||||||
unsigned long t0 = millis();
|
|
||||||
int remaining = len;
|
|
||||||
while (millis() - t0 < 1000 && remaining > 0) {
|
|
||||||
if (esp8266.available()) {
|
|
||||||
esp8266.read();
|
|
||||||
remaining--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ارسال فرمان AT و انتظار ACK
|
|
||||||
bool sendCommand(const String& cmd, const char* ack, unsigned long timeout) {
|
|
||||||
esp8266.println(cmd);
|
|
||||||
return waitFor(ack, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// منتظر بودن برای وجود یک ACK/کلمه کلیدی تا مهلت معین
|
|
||||||
bool waitFor(const char* target, unsigned long timeout) {
|
|
||||||
unsigned long start = millis();
|
|
||||||
String buffer = "";
|
|
||||||
while (millis() - start < timeout) {
|
|
||||||
while (esp8266.available()) {
|
|
||||||
char c = esp8266.read();
|
|
||||||
buffer += c;
|
|
||||||
if (buffer.indexOf(target) != -1) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.print("Timeout waiting for: "); Serial.println(target);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
759
old/nodemcu3.ino
759
old/nodemcu3.ino
@@ -1,759 +0,0 @@
|
|||||||
// 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 <RH_ASK.h>
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include "RTClib.h"
|
|
||||||
//#include <RCSwitch.h>
|
|
||||||
#include <SD.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <ESP8266HTTPClient.h>
|
|
||||||
#include <WiFiClientSecureBearSSL.h>
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------- 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;
|
|
||||||
#define RF_RECEIVE_PIN D1
|
|
||||||
RH_ASK driver(1200, RF_RECEIVE_PIN, -1, -1);
|
|
||||||
|
|
||||||
#define LED_PIN 1 // TX = GPIO1
|
|
||||||
#define NUM_LEDS 8
|
|
||||||
|
|
||||||
//Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
|
|
||||||
|
|
||||||
#define SD_CS_PIN D8
|
|
||||||
bool sdReady = false;
|
|
||||||
const char* CONFIG_FILE = "config.txt";
|
|
||||||
|
|
||||||
const int buttonPin = 16; // D0
|
|
||||||
bool wifiEnabled = true;
|
|
||||||
bool ConnectToModem = 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 ALLOWED_DEVICES[] = {"dr142","abcde","fghij"};
|
|
||||||
const int NUM_ALLOWED = 3;
|
|
||||||
|
|
||||||
#define MAX_DEVICES 4
|
|
||||||
struct RemoteData {
|
|
||||||
String deviceId;
|
|
||||||
int soil;
|
|
||||||
int gas;
|
|
||||||
int temp;
|
|
||||||
int hum;
|
|
||||||
unsigned long timestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
RemoteData lastRemoteData[MAX_DEVICES];
|
|
||||||
/*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;
|
|
||||||
|
|
||||||
// ---------------- 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<NUM_ALLOWED;i++) if (id == ALLOWED_DEVICES[i]) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetFrame() {
|
|
||||||
receivedKey = "";
|
|
||||||
receivedHex = "";
|
|
||||||
receivingPublicKey = true;
|
|
||||||
receivedChars = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ----------------
|
|
||||||
// -------------------- loadConfigFromSD --------------------
|
|
||||||
// -------------------- loadConfigFromSD --------------------
|
|
||||||
void loadConfigFromSD() {
|
|
||||||
if (!sdReady) {
|
|
||||||
Serial.println("[CONFIG] SD not ready, skipping load");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[CONFIG] Loading configuration from SD...");
|
|
||||||
File f = SD.open(CONFIG_FILE, "r");
|
|
||||||
if (!f) {
|
|
||||||
Serial.println("[CONFIG] Config file not found, using defaults");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (f.available()) {
|
|
||||||
String line = f.readStringUntil('\n');
|
|
||||||
line.trim();
|
|
||||||
if (line.length() == 0) continue;
|
|
||||||
|
|
||||||
Serial.print("[CONFIG] Line: ");
|
|
||||||
Serial.println(line);
|
|
||||||
|
|
||||||
if (line.startsWith("save_interval_minutes=")) {
|
|
||||||
saveIntervalMinutes = line.substring(strlen("save_interval_minutes=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded save_interval_minutes = ");
|
|
||||||
Serial.println(saveIntervalMinutes);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("retention_days=")) {
|
|
||||||
retentionDays = line.substring(strlen("retention_days=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded retention_days = ");
|
|
||||||
Serial.println(retentionDays);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("wifi_ssid=")) {
|
|
||||||
apSSID = line.substring(strlen("wifi_ssid="));
|
|
||||||
Serial.print("[CONFIG] Loaded wifi_ssid = ");
|
|
||||||
Serial.println(apSSID);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("wifi_password=")) {
|
|
||||||
apPassword = line.substring(strlen("wifi_password="));
|
|
||||||
Serial.print("[CONFIG] Loaded wifi_password = ");
|
|
||||||
Serial.println(apPassword);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sta_ssid=")) {
|
|
||||||
staSSID = line.substring(strlen("sta_ssid="));
|
|
||||||
Serial.print("[CONFIG] Loaded sta_ssid = ");
|
|
||||||
Serial.println(staSSID);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sta_password=")) {
|
|
||||||
staPassword = line.substring(strlen("sta_password="));
|
|
||||||
Serial.print("[CONFIG] Loaded sta_password = ");
|
|
||||||
Serial.println(staPassword);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sendIntervalMinutes=")) {
|
|
||||||
sendIntervalMinutes = line.substring(strlen("sendIntervalMinutes=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded sendIntervalMinutes = ");
|
|
||||||
Serial.println(sendIntervalMinutes);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Serial.print("[CONFIG] Unknown line ignored: ");
|
|
||||||
Serial.println(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
Serial.println("[CONFIG] Finished loading configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- saveConfigToSD --------------------
|
|
||||||
void saveConfigToSD() {
|
|
||||||
if (!sdReady) {
|
|
||||||
Serial.println("[CONFIG] SD not ready, cannot save config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[CONFIG] Saving configuration to SD...");
|
|
||||||
|
|
||||||
// اول فایل قبلی رو پاک کن
|
|
||||||
if (SD.exists(CONFIG_FILE)) {
|
|
||||||
SD.remove(CONFIG_FILE);
|
|
||||||
Serial.println("[CONFIG] Old config file removed");
|
|
||||||
}
|
|
||||||
|
|
||||||
File f = SD.open(CONFIG_FILE, FILE_WRITE);
|
|
||||||
if (!f) {
|
|
||||||
Serial.println("[CONFIG] Failed to open config file for writing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// همه مقادیر رو یکجا مینویسیم
|
|
||||||
f.print("save_interval_minutes=");
|
|
||||||
f.println(saveIntervalMinutes);
|
|
||||||
|
|
||||||
f.print("retention_days=");
|
|
||||||
f.println(retentionDays);
|
|
||||||
|
|
||||||
f.print("sendIntervalMinutes=");
|
|
||||||
f.println(sendIntervalMinutes);
|
|
||||||
|
|
||||||
f.print("wifi_ssid=");
|
|
||||||
f.println(apSSID);
|
|
||||||
|
|
||||||
f.print("wifi_password=");
|
|
||||||
f.println(apPassword);
|
|
||||||
|
|
||||||
f.print("sta_ssid=");
|
|
||||||
f.println(staSSID);
|
|
||||||
|
|
||||||
f.print("sta_password=");
|
|
||||||
f.println(staPassword);
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
Serial.println("[CONFIG] Configuration saved successfully (file rewritten)");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void appendDataToSD(const String &line) {
|
|
||||||
if (!sdReady) return;
|
|
||||||
String fname = safeFileNameForNow();
|
|
||||||
File f = SD.open(fname, FILE_WRITE);
|
|
||||||
if (!f) f = SD.open(fname, "a");
|
|
||||||
if (!f) return;
|
|
||||||
f.println(line);
|
|
||||||
f.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ----------------
|
|
||||||
void processRemote() {
|
|
||||||
uint8_t buf[32]; // حداکثر طول بسته
|
|
||||||
uint8_t buflen = sizeof(buf);
|
|
||||||
|
|
||||||
if (driver.recv(buf, &buflen)) {
|
|
||||||
buf[buflen] = '\0'; // تبدیل به رشته
|
|
||||||
String plain = String((char*)buf);
|
|
||||||
|
|
||||||
Serial.print("[RX] Received: ");
|
|
||||||
Serial.println(plain);
|
|
||||||
|
|
||||||
// 📌 فرض: فرمت مثل "dr142S0345G0123T0254H0456"
|
|
||||||
if (plain.length() >= 25) {
|
|
||||||
RemoteData _remoteData;
|
|
||||||
_remoteData.soil = plain.substring(6, 10).toInt();
|
|
||||||
_remoteData.gas = plain.substring(11, 15).toInt();
|
|
||||||
_remoteData.deviceId = plain.substring(0, 5);
|
|
||||||
_remoteData.temp = plain.substring(16, 20).toInt();
|
|
||||||
_remoteData.hum = plain.substring(21, 25).toInt();
|
|
||||||
_remoteData.timestamp = millis();
|
|
||||||
bool updated=false;
|
|
||||||
for (int i=0;i<deviceCount;i++) {
|
|
||||||
if (lastRemoteData[i].deviceId==_remoteData.deviceId) {
|
|
||||||
lastRemoteData[i]=_remoteData;
|
|
||||||
updated=true; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!updated && deviceCount<MAX_DEVICES) lastRemoteData[deviceCount++]=_remoteData;
|
|
||||||
|
|
||||||
if ((millis()-lastSaveMillis) >= saveIntervalMinutes*60000UL) {
|
|
||||||
if (sdReady) {
|
|
||||||
String j="{\"device\":\""+_remoteData.deviceId+"\",\"soil\":"+String(_remoteData.soil)+",\"gas\":"+String(_remoteData.gas)+
|
|
||||||
",\"temp\":"+String(_remoteData.temp)+",\"hum\":"+String(_remoteData.hum)+",\"time\":\""+_remoteData.timestamp+"\"}";
|
|
||||||
appendDataToSD(j);
|
|
||||||
Serial.println("[INFO] appended to SD");
|
|
||||||
}
|
|
||||||
lastSaveMillis=millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// String logLine = plain + "," + String(millis());
|
|
||||||
// saveToSD(logLine);
|
|
||||||
|
|
||||||
//digitalWrite(LED_GREEN, HIGH);
|
|
||||||
delay(50);
|
|
||||||
//digitalWrite(LED_GREEN, LOW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- 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(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("[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;
|
|
||||||
ConnectToModem=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\"}"; break;
|
|
||||||
case 2: response="{\"status\":\"ok\",\"sensor\":"+String(analogRead(A0))+"}"; break;
|
|
||||||
case 3: response=rtcReady?"{\"status\":\"ok\",\"datetime\":\""+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\",\"datetime\":\""+dateTimeToISO(rtc.now())+"\"}";
|
|
||||||
} else response="{\"status\":\"error\",\"message\":\"invalid iso\"}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 5: {
|
|
||||||
String arr="[";
|
|
||||||
for(int i=0;i<deviceCount;i++){ if(i) arr+=","; RemoteData 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: {
|
|
||||||
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: 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");
|
|
||||||
}
|
|
||||||
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 setup() {
|
|
||||||
Serial.begin(115200); delay(2000);
|
|
||||||
|
|
||||||
//led
|
|
||||||
// strip.begin();
|
|
||||||
// strip.show();
|
|
||||||
|
|
||||||
//چراغ اول روشن شود. یعنی دستگاه روشن است
|
|
||||||
// setLed(0, true, strip.Color(0,255,0), strip.Color(0,0,0));
|
|
||||||
// strip.show();
|
|
||||||
|
|
||||||
pinMode(buttonPin, INPUT_PULLUP);
|
|
||||||
Wire.begin(D2,D3);
|
|
||||||
rtcReady=rtc.begin();
|
|
||||||
if(rtcReady && rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__),F(__TIME__)));
|
|
||||||
|
|
||||||
if (!driver.init()) {
|
|
||||||
Serial.println("[ERR] RH_ASK init failed!");
|
|
||||||
} else {
|
|
||||||
Serial.println("[INFO] RH_ASK ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
// rx.enableReceive(digitalPinToInterrupt(RX_PIN));
|
|
||||||
// rx.setProtocol(1);
|
|
||||||
// rx.setPulseLength(300);
|
|
||||||
Serial.println("[INFO] RCSwitch enabled on D5");
|
|
||||||
|
|
||||||
sdReady=SD.begin(SD_CS_PIN);
|
|
||||||
if(sdReady){ Serial.println("[INFO] SD ready"); loadConfigFromSD(); }
|
|
||||||
else Serial.println("[WARN] SD init failed");
|
|
||||||
|
|
||||||
if(wifiEnabled){ WiFi.softAP(apSSID,apPassword); server.begin(); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); }
|
|
||||||
|
|
||||||
if(staSSID.length()>0){ ConnectToModem=WiFi.begin(staSSID.c_str(),staPassword.c_str()); }
|
|
||||||
|
|
||||||
lastSaveMillis=millis(); lastCleanupMillis=millis(); lastSendMillis=millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// ---------------- 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);
|
|
||||||
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;
|
|
||||||
|
|
||||||
// ---------------- HTTP server handling ----------------
|
|
||||||
if (wifiEnabled) {
|
|
||||||
WiFiClient client = server.available();
|
|
||||||
if (client) handleClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- Process RF data ----------------
|
|
||||||
processRemote();
|
|
||||||
|
|
||||||
// ---------------- Periodic cleanup every 12 hours ----------------
|
|
||||||
if ((millis() - lastCleanupMillis) >= (12UL * 60UL * 60UL * 1000UL)) {
|
|
||||||
cleanupOldFilesOnSD();
|
|
||||||
lastCleanupMillis = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- Debug info before sending ----------------
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------- Periodic send to server ----------------
|
|
||||||
// ---------------- Periodic send to server ----------------
|
|
||||||
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 += ",";
|
|
||||||
RemoteData 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//دریافت کننده فعال است؟
|
|
||||||
// setLed(1, rtcReady, strip.Color(0,255,0), strip.Color(255,0,0));
|
|
||||||
// //کارتخوان فعال است؟
|
|
||||||
// setLed(2, sdReady, strip.Color(0,255,0), strip.Color(255,0,0));
|
|
||||||
// //wifi فعال است؟
|
|
||||||
// setLed(3, wifiEnabled, strip.Color(0,255,0), strip.Color(0,0,0));
|
|
||||||
// //مودم فعال است یا خیر و اگر فعال است متصل است؟
|
|
||||||
// if(staSSID.length()>0)
|
|
||||||
// setLed(4, ConnectToModem, strip.Color(0,255,0), strip.Color(255,0,0));
|
|
||||||
// else
|
|
||||||
// setLed(4, true, strip.Color(0,0,0), strip.Color(0,0,0));
|
|
||||||
|
|
||||||
// strip.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,759 +0,0 @@
|
|||||||
// 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 <RH_ASK.h>
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include "RTClib.h"
|
|
||||||
//#include <RCSwitch.h>
|
|
||||||
#include <SD.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <ESP8266HTTPClient.h>
|
|
||||||
#include <WiFiClientSecureBearSSL.h>
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------- 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;
|
|
||||||
#define RF_RECEIVE_PIN D1
|
|
||||||
RH_ASK driver(1200, RF_RECEIVE_PIN, -1, -1);
|
|
||||||
|
|
||||||
#define LED_PIN 1 // TX = GPIO1
|
|
||||||
#define NUM_LEDS 8
|
|
||||||
|
|
||||||
//Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
|
|
||||||
|
|
||||||
|
|
||||||
#define SD_CS_PIN D8
|
|
||||||
bool sdReady = false;
|
|
||||||
const char* CONFIG_FILE = "config.txt";
|
|
||||||
|
|
||||||
const int buttonPin = 16; // D0
|
|
||||||
bool wifiEnabled = true;
|
|
||||||
bool ConnectToModem = 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 ALLOWED_DEVICES[] = {"dr142","abcde","fghij"};
|
|
||||||
const int NUM_ALLOWED = 3;
|
|
||||||
|
|
||||||
#define MAX_DEVICES 4
|
|
||||||
struct RemoteData {
|
|
||||||
String deviceId;
|
|
||||||
int soil;
|
|
||||||
int gas;
|
|
||||||
int temp;
|
|
||||||
int hum;
|
|
||||||
unsigned long timestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
RemoteData lastRemoteData[MAX_DEVICES];
|
|
||||||
/*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;
|
|
||||||
|
|
||||||
// ---------------- 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<NUM_ALLOWED;i++) if (id == ALLOWED_DEVICES[i]) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetFrame() {
|
|
||||||
receivedKey = "";
|
|
||||||
receivedHex = "";
|
|
||||||
receivingPublicKey = true;
|
|
||||||
receivedChars = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ----------------
|
|
||||||
// -------------------- loadConfigFromSD --------------------
|
|
||||||
// -------------------- loadConfigFromSD --------------------
|
|
||||||
void loadConfigFromSD() {
|
|
||||||
if (!sdReady) {
|
|
||||||
Serial.println("[CONFIG] SD not ready, skipping load");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[CONFIG] Loading configuration from SD...");
|
|
||||||
File f = SD.open(CONFIG_FILE, "r");
|
|
||||||
if (!f) {
|
|
||||||
Serial.println("[CONFIG] Config file not found, using defaults");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (f.available()) {
|
|
||||||
String line = f.readStringUntil('\n');
|
|
||||||
line.trim();
|
|
||||||
if (line.length() == 0) continue;
|
|
||||||
|
|
||||||
Serial.print("[CONFIG] Line: ");
|
|
||||||
Serial.println(line);
|
|
||||||
|
|
||||||
if (line.startsWith("save_interval_minutes=")) {
|
|
||||||
saveIntervalMinutes = line.substring(strlen("save_interval_minutes=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded save_interval_minutes = ");
|
|
||||||
Serial.println(saveIntervalMinutes);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("retention_days=")) {
|
|
||||||
retentionDays = line.substring(strlen("retention_days=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded retention_days = ");
|
|
||||||
Serial.println(retentionDays);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("wifi_ssid=")) {
|
|
||||||
apSSID = line.substring(strlen("wifi_ssid="));
|
|
||||||
Serial.print("[CONFIG] Loaded wifi_ssid = ");
|
|
||||||
Serial.println(apSSID);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("wifi_password=")) {
|
|
||||||
apPassword = line.substring(strlen("wifi_password="));
|
|
||||||
Serial.print("[CONFIG] Loaded wifi_password = ");
|
|
||||||
Serial.println(apPassword);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sta_ssid=")) {
|
|
||||||
staSSID = line.substring(strlen("sta_ssid="));
|
|
||||||
Serial.print("[CONFIG] Loaded sta_ssid = ");
|
|
||||||
Serial.println(staSSID);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sta_password=")) {
|
|
||||||
staPassword = line.substring(strlen("sta_password="));
|
|
||||||
Serial.print("[CONFIG] Loaded sta_password = ");
|
|
||||||
Serial.println(staPassword);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sendIntervalMinutes=")) {
|
|
||||||
sendIntervalMinutes = line.substring(strlen("sendIntervalMinutes=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded sendIntervalMinutes = ");
|
|
||||||
Serial.println(sendIntervalMinutes);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Serial.print("[CONFIG] Unknown line ignored: ");
|
|
||||||
Serial.println(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
Serial.println("[CONFIG] Finished loading configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- saveConfigToSD --------------------
|
|
||||||
void saveConfigToSD() {
|
|
||||||
if (!sdReady) {
|
|
||||||
Serial.println("[CONFIG] SD not ready, cannot save config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[CONFIG] Saving configuration to SD...");
|
|
||||||
|
|
||||||
// اول فایل قبلی رو پاک کن
|
|
||||||
if (SD.exists(CONFIG_FILE)) {
|
|
||||||
SD.remove(CONFIG_FILE);
|
|
||||||
Serial.println("[CONFIG] Old config file removed");
|
|
||||||
}
|
|
||||||
|
|
||||||
File f = SD.open(CONFIG_FILE, FILE_WRITE);
|
|
||||||
if (!f) {
|
|
||||||
Serial.println("[CONFIG] Failed to open config file for writing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// همه مقادیر رو یکجا مینویسیم
|
|
||||||
f.print("save_interval_minutes=");
|
|
||||||
f.println(saveIntervalMinutes);
|
|
||||||
|
|
||||||
f.print("retention_days=");
|
|
||||||
f.println(retentionDays);
|
|
||||||
|
|
||||||
f.print("sendIntervalMinutes=");
|
|
||||||
f.println(sendIntervalMinutes);
|
|
||||||
|
|
||||||
f.print("wifi_ssid=");
|
|
||||||
f.println(apSSID);
|
|
||||||
|
|
||||||
f.print("wifi_password=");
|
|
||||||
f.println(apPassword);
|
|
||||||
|
|
||||||
f.print("sta_ssid=");
|
|
||||||
f.println(staSSID);
|
|
||||||
|
|
||||||
f.print("sta_password=");
|
|
||||||
f.println(staPassword);
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
Serial.println("[CONFIG] Configuration saved successfully (file rewritten)");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void appendDataToSD(const String &line) {
|
|
||||||
if (!sdReady) return;
|
|
||||||
String fname = safeFileNameForNow();
|
|
||||||
File f = SD.open(fname, FILE_WRITE);
|
|
||||||
if (!f) f = SD.open(fname, "a");
|
|
||||||
if (!f) return;
|
|
||||||
f.println(line);
|
|
||||||
f.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ----------------
|
|
||||||
void processRemote() {
|
|
||||||
uint8_t buf[32]; // حداکثر طول بسته
|
|
||||||
uint8_t buflen = sizeof(buf);
|
|
||||||
|
|
||||||
if (driver.recv(buf, &buflen)) {
|
|
||||||
buf[buflen] = '\0'; // تبدیل به رشته
|
|
||||||
String plain = String((char*)buf);
|
|
||||||
|
|
||||||
Serial.print("[RX] Received: ");
|
|
||||||
Serial.println(plain);
|
|
||||||
|
|
||||||
// 📌 فرض: فرمت مثل "dr142S0345G0123T0254H0456"
|
|
||||||
if (plain.length() >= 25) {
|
|
||||||
RemoteData _remoteData;
|
|
||||||
_remoteData.soil = plain.substring(6, 10).toInt();
|
|
||||||
_remoteData.gas = plain.substring(11, 15).toInt();
|
|
||||||
_remoteData.deviceId = plain.substring(0, 5);
|
|
||||||
_remoteData.temp = plain.substring(16, 20).toInt();
|
|
||||||
_remoteData.hum = plain.substring(21, 25).toInt();
|
|
||||||
_remoteData.timestamp = millis();
|
|
||||||
bool updated=false;
|
|
||||||
for (int i=0;i<deviceCount;i++) {
|
|
||||||
if (lastRemoteData[i].deviceId==_remoteData.deviceId) {
|
|
||||||
lastRemoteData[i]=_remoteData;
|
|
||||||
updated=true; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!updated && deviceCount<MAX_DEVICES) lastRemoteData[deviceCount++]=_remoteData;
|
|
||||||
|
|
||||||
if ((millis()-lastSaveMillis) >= saveIntervalMinutes*60000UL) {
|
|
||||||
if (sdReady) {
|
|
||||||
String j="{\"device\":\""+_remoteData.deviceId+"\",\"soil\":"+String(_remoteData.soil)+",\"gas\":"+String(_remoteData.gas)+
|
|
||||||
",\"temp\":"+String(_remoteData.temp)+",\"hum\":"+String(_remoteData.hum)+",\"time\":\""+_remoteData.timestamp+"\"}";
|
|
||||||
appendDataToSD(j);
|
|
||||||
Serial.println("[INFO] appended to SD");
|
|
||||||
}
|
|
||||||
lastSaveMillis=millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// String logLine = plain + "," + String(millis());
|
|
||||||
// saveToSD(logLine);
|
|
||||||
|
|
||||||
//digitalWrite(LED_GREEN, HIGH);
|
|
||||||
delay(50);
|
|
||||||
//digitalWrite(LED_GREEN, LOW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- 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(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("[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;
|
|
||||||
ConnectToModem=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\"}"; break;
|
|
||||||
case 2: response="{\"status\":\"ok\",\"sensor\":"+String(analogRead(A0))+"}"; break;
|
|
||||||
case 3: response=rtcReady?"{\"status\":\"ok\",\"datetime\":\""+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\",\"datetime\":\""+dateTimeToISO(rtc.now())+"\"}";
|
|
||||||
} else response="{\"status\":\"error\",\"message\":\"invalid iso\"}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 5: {
|
|
||||||
String arr="[";
|
|
||||||
for(int i=0;i<deviceCount;i++){ if(i) arr+=","; RemoteData 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: {
|
|
||||||
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: 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");
|
|
||||||
}
|
|
||||||
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 setup() {
|
|
||||||
Serial.begin(115200); delay(2000);
|
|
||||||
|
|
||||||
//led
|
|
||||||
// strip.begin();
|
|
||||||
// strip.show();
|
|
||||||
|
|
||||||
//چراغ اول روشن شود. یعنی دستگاه روشن است
|
|
||||||
// setLed(0, true, strip.Color(0,255,0), strip.Color(0,0,0));
|
|
||||||
// strip.show();
|
|
||||||
|
|
||||||
pinMode(buttonPin, INPUT_PULLUP);
|
|
||||||
Wire.begin(D2,D3);
|
|
||||||
rtcReady=rtc.begin();
|
|
||||||
if(rtcReady && rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__),F(__TIME__)));
|
|
||||||
|
|
||||||
if (!driver.init()) {
|
|
||||||
Serial.println("[ERR] RH_ASK init failed!");
|
|
||||||
} else {
|
|
||||||
Serial.println("[INFO] RH_ASK ready");
|
|
||||||
}
|
|
||||||
|
|
||||||
// rx.enableReceive(digitalPinToInterrupt(RX_PIN));
|
|
||||||
// rx.setProtocol(1);
|
|
||||||
// rx.setPulseLength(300);
|
|
||||||
Serial.println("[INFO] RCSwitch enabled on D5");
|
|
||||||
|
|
||||||
sdReady=SD.begin(SD_CS_PIN);
|
|
||||||
if(sdReady){ Serial.println("[INFO] SD ready"); loadConfigFromSD(); }
|
|
||||||
else Serial.println("[WARN] SD init failed");
|
|
||||||
|
|
||||||
if(wifiEnabled){ WiFi.softAP(apSSID,apPassword); server.begin(); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); }
|
|
||||||
|
|
||||||
if(staSSID.length()>0){ ConnectToModem=WiFi.begin(staSSID.c_str(),staPassword.c_str()); }
|
|
||||||
|
|
||||||
lastSaveMillis=millis(); lastCleanupMillis=millis(); lastSendMillis=millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// ---------------- 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);
|
|
||||||
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;
|
|
||||||
|
|
||||||
// ---------------- HTTP server handling ----------------
|
|
||||||
if (wifiEnabled) {
|
|
||||||
WiFiClient client = server.available();
|
|
||||||
if (client) handleClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- Process RF data ----------------
|
|
||||||
processRemote();
|
|
||||||
|
|
||||||
// ---------------- Periodic cleanup every 12 hours ----------------
|
|
||||||
if ((millis() - lastCleanupMillis) >= (12UL * 60UL * 60UL * 1000UL)) {
|
|
||||||
cleanupOldFilesOnSD();
|
|
||||||
lastCleanupMillis = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- Debug info before sending ----------------
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------- Periodic send to server ----------------
|
|
||||||
// ---------------- Periodic send to server ----------------
|
|
||||||
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 += ",";
|
|
||||||
RemoteData 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//دریافت کننده فعال است؟
|
|
||||||
// setLed(1, rtcReady, strip.Color(0,255,0), strip.Color(255,0,0));
|
|
||||||
// //کارتخوان فعال است؟
|
|
||||||
// setLed(2, sdReady, strip.Color(0,255,0), strip.Color(255,0,0));
|
|
||||||
// //wifi فعال است؟
|
|
||||||
// setLed(3, wifiEnabled, strip.Color(0,255,0), strip.Color(0,0,0));
|
|
||||||
// //مودم فعال است یا خیر و اگر فعال است متصل است؟
|
|
||||||
// if(staSSID.length()>0)
|
|
||||||
// setLed(4, ConnectToModem, strip.Color(0,255,0), strip.Color(255,0,0));
|
|
||||||
// else
|
|
||||||
// setLed(4, true, strip.Color(0,0,0), strip.Color(0,0,0));
|
|
||||||
|
|
||||||
// strip.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,716 +0,0 @@
|
|||||||
// 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---------------------------------
|
|
||||||
}
|
|
||||||
@@ -1,928 +0,0 @@
|
|||||||
// 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 <ESP8266WiFi.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include "RTClib.h"
|
|
||||||
#include <RCSwitch.h>
|
|
||||||
#include <SD.h>
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <ESP8266HTTPClient.h>
|
|
||||||
#include <WiFiClientSecureBearSSL.h>
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#include <TimeLib.h>
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------- 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<NUM_ALLOWED;i++) if (id == ALLOWED_DEVICES[i]) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetFrame() {
|
|
||||||
receivedKey = "";
|
|
||||||
receivedHex = "";
|
|
||||||
receivingPublicKey = true;
|
|
||||||
receivedChars = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ----------------
|
|
||||||
// -------------------- loadConfigFromSD --------------------
|
|
||||||
// -------------------- loadConfigFromSD --------------------
|
|
||||||
void loadConfigFromSD() {
|
|
||||||
if (!sdReady) {
|
|
||||||
Serial.println("[CONFIG] SD not ready, skipping load");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[CONFIG] Loading configuration from SD...");
|
|
||||||
File f = SD.open(CONFIG_FILE, "r");
|
|
||||||
if (!f) {
|
|
||||||
Serial.println("[CONFIG] Config file not found, using defaults");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (f.available()) {
|
|
||||||
String line = f.readStringUntil('\n');
|
|
||||||
line.trim();
|
|
||||||
if (line.length() == 0) continue;
|
|
||||||
|
|
||||||
Serial.print("[CONFIG] Line: ");
|
|
||||||
Serial.println(line);
|
|
||||||
|
|
||||||
if (line.startsWith("save_interval_minutes=")) {
|
|
||||||
saveIntervalMinutes = line.substring(strlen("save_interval_minutes=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded save_interval_minutes = ");
|
|
||||||
Serial.println(saveIntervalMinutes);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("retention_days=")) {
|
|
||||||
retentionDays = line.substring(strlen("retention_days=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded retention_days = ");
|
|
||||||
Serial.println(retentionDays);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("wifi_ssid=")) {
|
|
||||||
apSSID = line.substring(strlen("wifi_ssid="));
|
|
||||||
Serial.print("[CONFIG] Loaded wifi_ssid = ");
|
|
||||||
Serial.println(apSSID);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("wifi_password=")) {
|
|
||||||
apPassword = line.substring(strlen("wifi_password="));
|
|
||||||
Serial.print("[CONFIG] Loaded wifi_password = ");
|
|
||||||
Serial.println(apPassword);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sta_ssid=")) {
|
|
||||||
staSSID = line.substring(strlen("sta_ssid="));
|
|
||||||
Serial.print("[CONFIG] Loaded sta_ssid = ");
|
|
||||||
Serial.println(staSSID);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sta_password=")) {
|
|
||||||
staPassword = line.substring(strlen("sta_password="));
|
|
||||||
Serial.print("[CONFIG] Loaded sta_password = ");
|
|
||||||
Serial.println(staPassword);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("sendIntervalMinutes=")) {
|
|
||||||
sendIntervalMinutes = line.substring(strlen("sendIntervalMinutes=")).toInt();
|
|
||||||
Serial.print("[CONFIG] Loaded sendIntervalMinutes = ");
|
|
||||||
Serial.println(sendIntervalMinutes);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Serial.print("[CONFIG] Unknown line ignored: ");
|
|
||||||
Serial.println(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
Serial.println("[CONFIG] Finished loading configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- saveConfigToSD --------------------
|
|
||||||
void saveConfigToSD() {
|
|
||||||
if (!sdReady) {
|
|
||||||
Serial.println("[CONFIG] SD not ready, cannot save config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[CONFIG] Saving configuration to SD...");
|
|
||||||
|
|
||||||
// اول فایل قبلی رو پاک کن
|
|
||||||
if (SD.exists(CONFIG_FILE)) {
|
|
||||||
SD.remove(CONFIG_FILE);
|
|
||||||
Serial.println("[CONFIG] Old config file removed");
|
|
||||||
}
|
|
||||||
|
|
||||||
File f = SD.open(CONFIG_FILE, FILE_WRITE);
|
|
||||||
if (!f) {
|
|
||||||
Serial.println("[CONFIG] Failed to open config file for writing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// همه مقادیر رو یکجا مینویسیم
|
|
||||||
f.print("save_interval_minutes=");
|
|
||||||
f.println(saveIntervalMinutes);
|
|
||||||
|
|
||||||
f.print("retention_days=");
|
|
||||||
f.println(retentionDays);
|
|
||||||
|
|
||||||
f.print("sendIntervalMinutes=");
|
|
||||||
f.println(sendIntervalMinutes);
|
|
||||||
|
|
||||||
f.print("wifi_ssid=");
|
|
||||||
f.println(apSSID);
|
|
||||||
|
|
||||||
f.print("wifi_password=");
|
|
||||||
f.println(apPassword);
|
|
||||||
|
|
||||||
f.print("sta_ssid=");
|
|
||||||
f.println(staSSID);
|
|
||||||
|
|
||||||
f.print("sta_password=");
|
|
||||||
f.println(staPassword);
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
Serial.println("[CONFIG] Configuration saved successfully (file rewritten)");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void appendDataToSD(const String &line) {
|
|
||||||
if (!sdReady) return;
|
|
||||||
String fname = safeFileNameForNow();
|
|
||||||
File f = SD.open(fname, FILE_WRITE);
|
|
||||||
if (!f) f = SD.open(fname, "a");
|
|
||||||
if (!f) return;
|
|
||||||
f.println(line);
|
|
||||||
f.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ----------------
|
|
||||||
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<receivedHex.length(); i+=2) {
|
|
||||||
char hex2[3] = { receivedHex[i], receivedHex[i+1], '\0' };
|
|
||||||
decoded += (char)strtol(hex2,NULL,16);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decoded.length() != EXPECTED_PLAIN_LEN) { resetFrame(); return; }
|
|
||||||
String plain = decryptData(decoded, PRIVATE_KEY);
|
|
||||||
if (plain[5]!='S'||plain[10]!='G'||plain[15]!='T'||plain[20]!='H') { resetFrame(); return; }
|
|
||||||
|
|
||||||
String deviceId = plain.substring(0,5);
|
|
||||||
if (!isDeviceAllowed(deviceId)) { resetFrame(); return; }
|
|
||||||
|
|
||||||
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 = rtcReady?dateTimeToISO(rtc.now()):String(millis());
|
|
||||||
|
|
||||||
Serial.printf("[RX] %s soil=%d gas=%d temp=%.1f hum=%.1f time=%s\n", deviceId.c_str(),soil,gas,temp,hum,ts.c_str());
|
|
||||||
|
|
||||||
bool updated=false;
|
|
||||||
for (int i=0;i<deviceCount;i++) {
|
|
||||||
if (lastRemoteData[i].deviceId==deviceId) {
|
|
||||||
lastRemoteData[i]={deviceId,soil,gas,temp,hum,ts};
|
|
||||||
updated=true; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!updated && deviceCount<MAX_DEVICES) lastRemoteData[deviceCount++]={deviceId,soil,gas,temp,hum,ts};
|
|
||||||
checkDeviceData();
|
|
||||||
if ((millis()-lastSaveMillis) >= 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<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: {
|
|
||||||
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: 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<deviceCount)
|
|
||||||
{
|
|
||||||
if(lastRemoteData[i].deviceId==device_1)
|
|
||||||
device1Index=i;
|
|
||||||
if(lastRemoteData[i].deviceId==device_2)
|
|
||||||
device2Index=i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(device1Index==-1)
|
|
||||||
{
|
|
||||||
device1_recived=0;//دریافتی از دستگاه 1 نداشتیم
|
|
||||||
Serial.println(device_1 + " not exist");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int i=device1Index;
|
|
||||||
if (lastRemoteData[i].timestamp.length() > 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);
|
|
||||||
// }
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#include <RCSwitch.h>
|
|
||||||
|
|
||||||
RCSwitch rx = RCSwitch();
|
|
||||||
|
|
||||||
// باید با فرستنده یکسان باشد
|
|
||||||
const String PRIVATE_KEY = "as23f";
|
|
||||||
|
|
||||||
// دستگاههای مجاز
|
|
||||||
const String ALLOWED_DEVICES[] = {"dr142", "abcde", "fghij"};
|
|
||||||
const int NUM_ALLOWED_DEVICES = 3;
|
|
||||||
|
|
||||||
// بافرهای دریافت
|
|
||||||
String receivedKey = "";
|
|
||||||
String receivedHex = "";
|
|
||||||
|
|
||||||
// کنترل فریم
|
|
||||||
bool receivingPublicKey = true;
|
|
||||||
int receivedChars = 0;
|
|
||||||
unsigned long lastReceiveTime = 0;
|
|
||||||
const unsigned long TIMEOUT_MS = 500;
|
|
||||||
|
|
||||||
// طول متن اصلی انتظار میرود 25 باشد
|
|
||||||
const int EXPECTED_PLAIN_LEN = 25;
|
|
||||||
|
|
||||||
// XOR ساده
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isDeviceAllowed(const String &deviceId) {
|
|
||||||
for (int i = 0; i < NUM_ALLOWED_DEVICES; i++) {
|
|
||||||
if (deviceId == ALLOWED_DEVICES[i]) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool isHexChar(char c) {
|
|
||||||
return (c >= '0' && c <= '9') ||
|
|
||||||
(c >= 'A' && c <= 'F') ||
|
|
||||||
(c >= 'a' && c <= 'f');
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetFrame() {
|
|
||||||
receivedKey = "";
|
|
||||||
receivedHex = "";
|
|
||||||
receivingPublicKey = true;
|
|
||||||
receivedChars = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
rx.enableReceive(0); // interrupt 0 → پایه 2 در آردوینو UNO
|
|
||||||
rx.setProtocol(1);
|
|
||||||
rx.setPulseLength(300);
|
|
||||||
|
|
||||||
resetFrame();
|
|
||||||
|
|
||||||
Serial.println(F("=== Receiver Started ==="));
|
|
||||||
Serial.print(F("Private Key: ")); Serial.println(PRIVATE_KEY);
|
|
||||||
Serial.print(F("Allowed IDs: "));
|
|
||||||
for (int i = 0; i < NUM_ALLOWED_DEVICES; i++) { Serial.print(ALLOWED_DEVICES[i]); Serial.print(' '); }
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
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;
|
|
||||||
//Serial.print(F("Received key char: ")); Serial.println(c);
|
|
||||||
receivedChars++;
|
|
||||||
if (receivedKey.length() == 5) {
|
|
||||||
receivingPublicKey = false;
|
|
||||||
Serial.println(F("Now receiving encrypted HEX data..."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// فقط کاراکتر HEX معتبر ذخیره شود
|
|
||||||
if (isHexChar(c)) {
|
|
||||||
receivedHex += c;
|
|
||||||
//Serial.print(F("Received HEX char: ")); Serial.println(c);
|
|
||||||
receivedChars++;
|
|
||||||
} else {
|
|
||||||
// نویز را رد کن
|
|
||||||
Serial.print(F("Ignored non-HEX char: 0x"));
|
|
||||||
Serial.println((int)(uint8_t)c, HEX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastReceiveTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
rx.resetAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// اگر وقفه افتاد، پردازش کن
|
|
||||||
if (receivedChars >= 5 && (millis() - lastReceiveTime) > TIMEOUT_MS) {
|
|
||||||
Serial.println(F("=== Processing Data ==="));
|
|
||||||
Serial.print(F("Received Public Key: ")); Serial.println(receivedKey);
|
|
||||||
|
|
||||||
if (receivedKey != PRIVATE_KEY) {
|
|
||||||
Serial.println(F("Key verification: FAILED"));
|
|
||||||
resetFrame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Serial.println(F("Key verification: SUCCESS"));
|
|
||||||
|
|
||||||
// طول HEX باید زوج باشد (هر دو کاراکتر → یک بایت)
|
|
||||||
if ((receivedHex.length() % 2) != 0) {
|
|
||||||
Serial.print(F("Odd HEX length: ")); Serial.println(receivedHex.length());
|
|
||||||
resetFrame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// تبدیل HEX → بایتها
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print(F("Decoded length: ")); Serial.println(decoded.length());
|
|
||||||
Serial.print(F("Decoded (raw XORed) bytes: "));
|
|
||||||
for (int i = 0; i < decoded.length(); i++) {
|
|
||||||
Serial.print((int)(uint8_t)decoded[i]); Serial.print(' ');
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
// باید دقیقاً به طول 25 بایت باشد
|
|
||||||
if (decoded.length() != EXPECTED_PLAIN_LEN) {
|
|
||||||
Serial.println(F("Unexpected decoded length (should be 25)."));
|
|
||||||
resetFrame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// رمزگشایی
|
|
||||||
String plain = decryptData(decoded, PRIVATE_KEY);
|
|
||||||
Serial.print(F("Decrypted Data: ")); Serial.println(plain);
|
|
||||||
|
|
||||||
// اعتبارسنجی قالب
|
|
||||||
if (plain.length() != EXPECTED_PLAIN_LEN ||
|
|
||||||
plain[5] != 'S' || plain[10] != 'G' ||
|
|
||||||
plain[15] != 'T' || plain[20] != 'H') {
|
|
||||||
Serial.println(F("Format check failed."));
|
|
||||||
resetFrame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String deviceId = plain.substring(0, 5);
|
|
||||||
Serial.print(F("Device ID: ")); Serial.println(deviceId);
|
|
||||||
|
|
||||||
if (!isDeviceAllowed(deviceId)) {
|
|
||||||
Serial.println(F("Device authorization: FAILED"));
|
|
||||||
resetFrame();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println(F("Device authorization: SUCCESS"));
|
|
||||||
|
|
||||||
// پارس مقادیر
|
|
||||||
String sSoil = plain.substring(6, 10); // بین S و G
|
|
||||||
String sGas = plain.substring(11, 15); // بین G و T
|
|
||||||
String sTemp = plain.substring(16, 20); // بین T و H
|
|
||||||
String sHum = plain.substring(21, 25); // بعد از H
|
|
||||||
|
|
||||||
int soilMoisture = sSoil.toInt();
|
|
||||||
int gasValue = sGas.toInt();
|
|
||||||
float temperature = sTemp.toInt() / 10.0;
|
|
||||||
float humidity = sHum.toInt() / 10.0;
|
|
||||||
|
|
||||||
Serial.println(F("=== Authorized Data ==="));
|
|
||||||
Serial.print(F("Soil Moisture: ")); Serial.print(soilMoisture);
|
|
||||||
Serial.print(F(" - Gas Value : ")); Serial.print(gasValue);
|
|
||||||
Serial.print(F(" - Temperature : ")); Serial.print(temperature); Serial.print(F(" C"));
|
|
||||||
Serial.print(F(" - Humidity : ")); Serial.print(humidity); Serial.println(F(" %"));
|
|
||||||
Serial.println(F("======================="));
|
|
||||||
|
|
||||||
// آماده دریافت بعدی
|
|
||||||
resetFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
#include <RCSwitch.h>
|
|
||||||
#include <DHT.h>
|
|
||||||
|
|
||||||
// پینها
|
|
||||||
#define DHTPIN 2
|
|
||||||
#define DHTTYPE DHT11
|
|
||||||
#define SOIL_MOISTURE_PIN A3
|
|
||||||
#define GAS_SENSOR_PIN A2
|
|
||||||
#define RF_TRANSMIT_PIN 9
|
|
||||||
|
|
||||||
const float RL = 10.0; // مقاومت Load روی ماژول (کیلو اهم)
|
|
||||||
float R0 = 50; // بعد از کالیبراسیون مقدار واقعی جایگزین کنید
|
|
||||||
// متن ثابت و کلیدها
|
|
||||||
|
|
||||||
const String PUBLIC_KEY = "as23f";
|
|
||||||
const String DEVICE_ID = "dr142";
|
|
||||||
|
|
||||||
// اشیاء
|
|
||||||
DHT dht(DHTPIN, DHTTYPE);
|
|
||||||
RCSwitch tx = RCSwitch();
|
|
||||||
|
|
||||||
// زمانبندی
|
|
||||||
unsigned long lastSendTime = 0;
|
|
||||||
const unsigned long sendInterval = 10000;
|
|
||||||
|
|
||||||
// تاخیر بین ارسال هر کاراکتر (ms)
|
|
||||||
const unsigned int INTER_CHAR_DELAY_MS = 1;
|
|
||||||
|
|
||||||
// XOR ساده
|
|
||||||
String encryptData(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void sendChar(uint8_t c) {
|
|
||||||
tx.send((unsigned long)c, 8);
|
|
||||||
delay(INTER_CHAR_DELAY_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void sendHexByte(uint8_t b) {
|
|
||||||
char hex[3];
|
|
||||||
sprintf(hex, "%02X", b); // حروف بزرگ
|
|
||||||
sendChar(hex[0]);
|
|
||||||
sendChar(hex[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
dht.begin();
|
|
||||||
|
|
||||||
tx.enableTransmit(RF_TRANSMIT_PIN);
|
|
||||||
tx.setProtocol(1);
|
|
||||||
tx.setPulseLength(300);
|
|
||||||
tx.setRepeatTransmit(3);
|
|
||||||
|
|
||||||
Serial.println(F("=== Transmitter Started ==="));
|
|
||||||
Serial.print(F("Public Key: ")); Serial.println(PUBLIC_KEY);
|
|
||||||
Serial.print(F("Device ID : ")); Serial.println(DEVICE_ID);
|
|
||||||
}
|
|
||||||
int getSoilMoistureInt() {
|
|
||||||
int adcValue = analogRead(SOIL_MOISTURE_PIN);
|
|
||||||
// مقادیر کالیبراسیون: خاک خشک و خاک خیس
|
|
||||||
const int adcDry = 1023; // خاک کاملاً خشک
|
|
||||||
const int adcWet = 400; // خاک کاملاً خیس
|
|
||||||
|
|
||||||
// محاسبه درصد رطوبت با یک رقم اعشار
|
|
||||||
float soilPercent = (float)(adcDry - adcValue) / (adcDry - adcWet) * 100.0;
|
|
||||||
|
|
||||||
// محدود کردن بین 0 تا 100
|
|
||||||
if (soilPercent > 100.0) soilPercent = 100.0;
|
|
||||||
if (soilPercent < 0.0) soilPercent = 0.0;
|
|
||||||
|
|
||||||
// ضرب در 10 و تبدیل به عدد صحیح
|
|
||||||
int soilInt = (int)(soilPercent * 10 + 0.5); // +0.5 برای گرد کردن صحیح
|
|
||||||
return soilInt;
|
|
||||||
}
|
|
||||||
|
|
||||||
int readGas() {
|
|
||||||
long sum = 0;
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
sum += analogRead(GAS_SENSOR_PIN);
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
float adcValue = sum / 5.0; // میانگین ADC
|
|
||||||
|
|
||||||
float vrl = (adcValue / 1023.0) * 5.0; // ولتاژ خروجی
|
|
||||||
if (vrl < 0.001) vrl = 0.001; // جلوگیری از تقسیم بر صفر
|
|
||||||
|
|
||||||
float Rs = (5.0 - vrl) * RL / vrl; // مقاومت سنسور (kΩ)
|
|
||||||
float ratio = Rs / R0; // نسبت Rs/R0
|
|
||||||
|
|
||||||
// ثابتهای تقریبی از دیتاشیت MQ7 (CO curve)
|
|
||||||
float m = -0.77;
|
|
||||||
float b = 0.36;
|
|
||||||
|
|
||||||
float ppm_log = (log10(ratio) - b) / m;
|
|
||||||
int ppm = round(pow(10, ppm_log));
|
|
||||||
|
|
||||||
return ppm; // خروجی اعشاری
|
|
||||||
}
|
|
||||||
void loop() {
|
|
||||||
// خواندن سنسورها
|
|
||||||
float temperature = dht.readTemperature();
|
|
||||||
float humidity = dht.readHumidity();
|
|
||||||
int soilMoisture = getSoilMoistureInt();
|
|
||||||
int gasValue = readGas(); //analogRead(GAS_SENSOR_PIN);
|
|
||||||
|
|
||||||
if (isnan(temperature) || isnan(humidity)) {
|
|
||||||
temperature = 0; humidity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
soilMoisture = constrain(soilMoisture, 0, 9999);
|
|
||||||
gasValue = constrain(gasValue, 0, 9999);//1023
|
|
||||||
int tempInt = constrain((int)(temperature * 10), 0, 9999); // چهار رقمی
|
|
||||||
int humidityInt = constrain((int)(humidity * 10), 0, 9999); // چهار رقمی
|
|
||||||
|
|
||||||
// ⚠️ خیلی مهم: ۲۶ بایت برای نَل ترمینیتور
|
|
||||||
char dataString[26];
|
|
||||||
// قالب دقیقاً 25 کاراکتر تولید میکند: 5 + (S####) + (G####) + (T####) + (H####)
|
|
||||||
// 5 + 1+4 + 1+4 + 1+4 + 1+4 = 25
|
|
||||||
int n = snprintf(
|
|
||||||
dataString, sizeof(dataString),
|
|
||||||
"%sS%04dG%04dT%04dH%04d",
|
|
||||||
DEVICE_ID.c_str(), soilMoisture, gasValue, tempInt, humidityInt
|
|
||||||
);
|
|
||||||
|
|
||||||
// اگر بنا به هر دلیلی طول غیرمنتظره شد، همینجا خروج
|
|
||||||
if (n != 25) {
|
|
||||||
Serial.print(F("Format length unexpected: ")); Serial.println(n);
|
|
||||||
delay(500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String plain = String(dataString);
|
|
||||||
String enc = encryptData(plain, PUBLIC_KEY);
|
|
||||||
|
|
||||||
if (millis() - lastSendTime >= sendInterval) {
|
|
||||||
Serial.println(F("=== Data to Send ==="));
|
|
||||||
Serial.print(F("Original: ")); Serial.println(plain);
|
|
||||||
Serial.print(F("Length : ")); Serial.println(plain.length());
|
|
||||||
|
|
||||||
// 1) ارسال 5 کاراکتر کلید عمومی
|
|
||||||
for (int i = 0; i < PUBLIC_KEY.length(); i++) {
|
|
||||||
sendChar((uint8_t)PUBLIC_KEY[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) ارسال دیتای رمز شده به صورت HEX (هر بایت → دو کاراکتر)
|
|
||||||
Serial.print(F("Encrypted HEX: "));
|
|
||||||
for (int i = 0; i < enc.length(); i++) {
|
|
||||||
uint8_t b = (uint8_t)enc[i];
|
|
||||||
char hex[3]; sprintf(hex, "%02X", b);
|
|
||||||
Serial.print(hex); Serial.print(' ');
|
|
||||||
sendHexByte(b);
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
lastSendTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
#include <RCSwitch.h>
|
|
||||||
#include <DHT.h>
|
|
||||||
|
|
||||||
// پینها
|
|
||||||
#define DHTPIN 2
|
|
||||||
#define DHTTYPE DHT11
|
|
||||||
#define SOIL_MOISTURE_PIN A3
|
|
||||||
#define GAS_SENSOR_PIN A2
|
|
||||||
#define RF_TRANSMIT_PIN 9
|
|
||||||
|
|
||||||
// متن ثابت و کلیدها
|
|
||||||
const String PUBLIC_KEY = "as23f";
|
|
||||||
const String DEVICE_ID = "dr142";
|
|
||||||
|
|
||||||
// اشیاء
|
|
||||||
DHT dht(DHTPIN, DHTTYPE);
|
|
||||||
RCSwitch tx = RCSwitch();
|
|
||||||
|
|
||||||
// زمانبندی
|
|
||||||
unsigned long lastSendTime = 0;
|
|
||||||
const unsigned long sendInterval = 5000;
|
|
||||||
|
|
||||||
// تاخیر بین ارسال هر کاراکتر (ms)
|
|
||||||
const unsigned int INTER_CHAR_DELAY_MS = 15;
|
|
||||||
|
|
||||||
// شمارهٔ بسته (در صورت نیاز بعداً)
|
|
||||||
uint8_t seqNumber = 0;
|
|
||||||
|
|
||||||
// XOR ساده
|
|
||||||
String encryptData(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void sendChar(uint8_t c) {
|
|
||||||
tx.send((unsigned long)c, 8);
|
|
||||||
delay(INTER_CHAR_DELAY_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
dht.begin();
|
|
||||||
|
|
||||||
tx.enableTransmit(RF_TRANSMIT_PIN);
|
|
||||||
tx.setProtocol(1);
|
|
||||||
tx.setPulseLength(300);
|
|
||||||
tx.setRepeatTransmit(3); // کتابخانه هر بایت را 3 بار تکرار میکند
|
|
||||||
|
|
||||||
Serial.println(F("=== Transmitter Started (with 1-byte checksum) ==="));
|
|
||||||
Serial.print(F("Public Key: ")); Serial.println(PUBLIC_KEY);
|
|
||||||
Serial.print(F("Device ID : ")); Serial.println(DEVICE_ID);
|
|
||||||
|
|
||||||
randomSeed(analogRead(0)); // برای ایجاد تاخیر تصادفی احتمالی
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// خواندن سنسورها
|
|
||||||
float temperature = dht.readTemperature();
|
|
||||||
float humidity = dht.readHumidity();
|
|
||||||
int soilMoisture = analogRead(SOIL_MOISTURE_PIN);
|
|
||||||
int gasValue = analogRead(GAS_SENSOR_PIN);
|
|
||||||
|
|
||||||
if (isnan(temperature) || isnan(humidity)) {
|
|
||||||
temperature = 0; humidity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
soilMoisture = constrain(soilMoisture, 0, 1023);
|
|
||||||
gasValue = constrain(gasValue, 0, 1023);
|
|
||||||
int tempInt = constrain((int)(temperature * 10), 0, 9999); // چهار رقمی
|
|
||||||
int humidityInt = constrain((int)(humidity * 10), 0, 9999); // چهار رقمی
|
|
||||||
|
|
||||||
// قالب دقیقاً 25 کاراکتر تولید میکند: 5 + (S####) + (G####) + (T####) + (H####)
|
|
||||||
char dataString[26];
|
|
||||||
int n = snprintf(
|
|
||||||
dataString, sizeof(dataString),
|
|
||||||
"%sS%04dG%04dT%04dH%04d",
|
|
||||||
DEVICE_ID.c_str(), soilMoisture, gasValue, tempInt, humidityInt
|
|
||||||
);
|
|
||||||
|
|
||||||
if (n != 25) {
|
|
||||||
Serial.print(F("Format length unexpected: ")); Serial.println(n);
|
|
||||||
delay(500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String plain = String(dataString);
|
|
||||||
String enc = encryptData(plain, PUBLIC_KEY); // length 25, may contain non-printables
|
|
||||||
|
|
||||||
if (millis() - lastSendTime >= sendInterval) {
|
|
||||||
Serial.println(F("=== Data to Send ==="));
|
|
||||||
Serial.print(F("Plain : ")); Serial.println(plain);
|
|
||||||
Serial.print(F("Enc : "));
|
|
||||||
for (int i = 0; i < enc.length(); i++) {
|
|
||||||
uint8_t b = (uint8_t)enc[i];
|
|
||||||
if (b < 0x10) Serial.print('0');
|
|
||||||
Serial.print(b, HEX); Serial.print(' ');
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
// ساخت packet: [PUBLIC_KEY(5)] + [ENC(25)] + [CHK(1)] => مجموع 31 بایت
|
|
||||||
const int PACKET_LEN = 31;
|
|
||||||
uint8_t packet[PACKET_LEN];
|
|
||||||
|
|
||||||
// PUBLIC_KEY (5 bytes)
|
|
||||||
for (int i = 0; i < 5; i++) packet[i] = (uint8_t)PUBLIC_KEY[i];
|
|
||||||
|
|
||||||
// ENC (25 bytes)
|
|
||||||
for (int i = 0; i < 25; i++) packet[5 + i] = (uint8_t)enc[i];
|
|
||||||
|
|
||||||
// محاسبه چکسام ساده (sum of bytes 0..29) & 0xFF
|
|
||||||
uint16_t sum = 0;
|
|
||||||
for (int i = 0; i < 30; i++) sum += packet[i];
|
|
||||||
packet[30] = (uint8_t)(sum & 0xFF);
|
|
||||||
|
|
||||||
// ارسال تمام بایتها متوالی (فقط یک بار، بدون حلقهٔ 3تایی دستی)
|
|
||||||
for (int i = 0; i < PACKET_LEN; i++) {
|
|
||||||
sendChar(packet[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print(F("Checksum sent: 0x"));
|
|
||||||
if (packet[30] < 0x10) Serial.print('0');
|
|
||||||
Serial.println(packet[30], HEX);
|
|
||||||
|
|
||||||
lastSendTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
#include <Wire.h>
|
|
||||||
#include <Adafruit_GFX.h>
|
|
||||||
#include <Adafruit_SSD1306.h>
|
|
||||||
#include <Adafruit_SHT31.h>
|
|
||||||
#include <DHT.h>
|
|
||||||
|
|
||||||
// ==================== پینها ====================
|
|
||||||
#define SOIL_MOISTURE_PIN PA1
|
|
||||||
#define DHT11_PIN PA0
|
|
||||||
#define SHT31_SCL PB6
|
|
||||||
#define SHT31_SDA PB7
|
|
||||||
#define OLED_SCL PB10
|
|
||||||
#define OLED_SDA PB11
|
|
||||||
|
|
||||||
// ==================== OLED ====================
|
|
||||||
#define SCREEN_WIDTH 128
|
|
||||||
#define SCREEN_HEIGHT 64
|
|
||||||
#define OLED_ADDRESS 0x3C
|
|
||||||
|
|
||||||
TwoWire Wire2(OLED_SDA, OLED_SCL);
|
|
||||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire2, -1);
|
|
||||||
|
|
||||||
// ==================== سنسورها ====================
|
|
||||||
#define SHT31_ADDRESS 0x44
|
|
||||||
Adafruit_SHT31 sht31(&Wire);
|
|
||||||
#define DHTTYPE DHT11
|
|
||||||
DHT dht(DHT11_PIN, DHTTYPE);
|
|
||||||
|
|
||||||
// ==================== کالیبراسیون ====================
|
|
||||||
#define SOIL_WET_VALUE 520
|
|
||||||
#define SOIL_DRY_VALUE 800
|
|
||||||
|
|
||||||
// ==================== متغیرها ====================
|
|
||||||
int soilMoisturePercent = 0;
|
|
||||||
float dht11_temp = 0, dht11_humidity = 0;
|
|
||||||
float sht31_temp = 0, sht31_humidity = 0;
|
|
||||||
bool sht31Found = false;
|
|
||||||
unsigned long lastRead = 0;
|
|
||||||
|
|
||||||
// ==================== تنظیمات نمایش ====================
|
|
||||||
#define LARGE_FONT_SIZE 2 // فونت بزرگ برای اعداد
|
|
||||||
#define SMALL_FONT_SIZE 1 // فونت کوچک برای متن
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial1.begin(115200);
|
|
||||||
delay(100);
|
|
||||||
|
|
||||||
Serial1.println("========================================");
|
|
||||||
Serial1.println("STM32F103 - Three Sensor System");
|
|
||||||
Serial1.println("========================================");
|
|
||||||
|
|
||||||
// I2C1 برای SHT31
|
|
||||||
Wire.setSDA(SHT31_SDA);
|
|
||||||
Wire.setSCL(SHT31_SCL);
|
|
||||||
Wire.begin();
|
|
||||||
|
|
||||||
// I2C2 برای OLED
|
|
||||||
Wire2.begin();
|
|
||||||
Wire2.setClock(400000);
|
|
||||||
|
|
||||||
// سنسورها
|
|
||||||
pinMode(SOIL_MOISTURE_PIN, INPUT_ANALOG);
|
|
||||||
dht.begin();
|
|
||||||
|
|
||||||
// OLED
|
|
||||||
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
|
|
||||||
Serial1.println("OLED initialization failed!");
|
|
||||||
while (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial1.println("OLED initialized successfully");
|
|
||||||
|
|
||||||
// SHT31
|
|
||||||
sht31Found = sht31.begin(SHT31_ADDRESS);
|
|
||||||
if (!sht31Found) {
|
|
||||||
sht31Found = sht31.begin(0x45); // تست آدرس دیگر
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sht31Found) {
|
|
||||||
Serial1.println("SHT31 found!");
|
|
||||||
} else {
|
|
||||||
Serial1.println("SHT31 not found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
displayStartup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
if (millis() - lastRead >= 2000) {
|
|
||||||
lastRead = millis();
|
|
||||||
readSensors();
|
|
||||||
updateDisplay();
|
|
||||||
printSerial();
|
|
||||||
}
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void readSensors() {
|
|
||||||
// خاک - میانگینگیری برای دقت بیشتر
|
|
||||||
long soilSum = 0;
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
soilSum += analogRead(SOIL_MOISTURE_PIN);
|
|
||||||
delay(1);
|
|
||||||
}
|
|
||||||
int soilRaw = soilSum / 5;
|
|
||||||
|
|
||||||
if (soilRaw <= SOIL_WET_VALUE) {
|
|
||||||
soilMoisturePercent = 100;
|
|
||||||
} else if (soilRaw >= SOIL_DRY_VALUE) {
|
|
||||||
soilMoisturePercent = 0;
|
|
||||||
} else {
|
|
||||||
soilMoisturePercent = map(soilRaw, SOIL_DRY_VALUE, SOIL_WET_VALUE, 0, 100);
|
|
||||||
}
|
|
||||||
soilMoisturePercent = constrain(soilMoisturePercent, 0, 100);
|
|
||||||
|
|
||||||
// DHT11
|
|
||||||
dht11_temp = dht.readTemperature();
|
|
||||||
dht11_humidity = dht.readHumidity();
|
|
||||||
if (isnan(dht11_temp) || isnan(dht11_humidity)) {
|
|
||||||
dht11_temp = dht11_humidity = -999;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SHT31
|
|
||||||
if (sht31Found) {
|
|
||||||
sht31_temp = sht31.readTemperature();
|
|
||||||
sht31_humidity = sht31.readHumidity();
|
|
||||||
if (isnan(sht31_temp) || isnan(sht31_humidity)) {
|
|
||||||
sht31_temp = sht31_humidity = -999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateDisplay() {
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
|
|
||||||
// ==================== خط اول: خاک ====================
|
|
||||||
// عنوان خاک
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.print("Soil:");
|
|
||||||
|
|
||||||
// مقدار خاک (بزرگ)
|
|
||||||
display.setTextSize(LARGE_FONT_SIZE);
|
|
||||||
if (soilMoisturePercent < 10) {
|
|
||||||
display.setCursor(35, 0);
|
|
||||||
} else if (soilMoisturePercent < 100) {
|
|
||||||
display.setCursor(25, 0);
|
|
||||||
} else {
|
|
||||||
display.setCursor(15, 0);
|
|
||||||
}
|
|
||||||
display.print(soilMoisturePercent);
|
|
||||||
|
|
||||||
// علامت درصد
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.print("%");
|
|
||||||
|
|
||||||
// ==================== خط دوم: DHT11 ====================
|
|
||||||
// عنوان DHT11
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.setCursor(0, 20);
|
|
||||||
display.print("DHT:");
|
|
||||||
|
|
||||||
// دما DHT (بزرگ)
|
|
||||||
display.setTextSize(LARGE_FONT_SIZE);
|
|
||||||
if (dht11_temp != -999) {
|
|
||||||
if (dht11_temp >= 0 && dht11_temp < 10) {
|
|
||||||
display.setCursor(40, 20);
|
|
||||||
} else if (dht11_temp >= 10 && dht11_temp < 100) {
|
|
||||||
display.setCursor(30, 20);
|
|
||||||
} else {
|
|
||||||
display.setCursor(20, 20);
|
|
||||||
}
|
|
||||||
display.print(dht11_temp, 1);
|
|
||||||
} else {
|
|
||||||
display.setCursor(40, 20);
|
|
||||||
display.print("---");
|
|
||||||
}
|
|
||||||
|
|
||||||
// درجه سانتیگراد
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.print("C");
|
|
||||||
|
|
||||||
// رطوبت DHT (بزرگ)
|
|
||||||
display.setTextSize(LARGE_FONT_SIZE);
|
|
||||||
display.setCursor(80, 20);
|
|
||||||
if (dht11_humidity != -999) {
|
|
||||||
if (dht11_humidity < 10) {
|
|
||||||
display.setCursor(85, 20);
|
|
||||||
} else if (dht11_humidity < 100) {
|
|
||||||
display.setCursor(80, 20);
|
|
||||||
} else {
|
|
||||||
display.setCursor(75, 20);
|
|
||||||
}
|
|
||||||
display.print(dht11_humidity, 0);
|
|
||||||
} else {
|
|
||||||
display.print("---");
|
|
||||||
}
|
|
||||||
|
|
||||||
// علامت درصد
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.print("%");
|
|
||||||
|
|
||||||
// ==================== خط سوم: SHT31 ====================
|
|
||||||
// عنوان SHT31
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.setCursor(0, 40);
|
|
||||||
display.print("SHT:");
|
|
||||||
|
|
||||||
// دما SHT (بزرگ)
|
|
||||||
display.setTextSize(LARGE_FONT_SIZE);
|
|
||||||
if (sht31Found && sht31_temp != -999) {
|
|
||||||
if (sht31_temp >= 0 && sht31_temp < 10) {
|
|
||||||
display.setCursor(40, 40);
|
|
||||||
} else if (sht31_temp >= 10 && sht31_temp < 100) {
|
|
||||||
display.setCursor(30, 40);
|
|
||||||
} else {
|
|
||||||
display.setCursor(20, 40);
|
|
||||||
}
|
|
||||||
display.print(sht31_temp, 1);
|
|
||||||
} else {
|
|
||||||
display.setCursor(40, 40);
|
|
||||||
display.print("---");
|
|
||||||
}
|
|
||||||
|
|
||||||
// درجه سانتیگراد
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.print("C");
|
|
||||||
|
|
||||||
// رطوبت SHT (بزرگ)
|
|
||||||
display.setTextSize(LARGE_FONT_SIZE);
|
|
||||||
if (sht31Found && sht31_humidity != -999) {
|
|
||||||
if (sht31_humidity < 10) {
|
|
||||||
display.setCursor(85, 40);
|
|
||||||
} else if (sht31_humidity < 100) {
|
|
||||||
display.setCursor(80, 40);
|
|
||||||
} else {
|
|
||||||
display.setCursor(75, 40);
|
|
||||||
}
|
|
||||||
display.print(sht31_humidity, 0);
|
|
||||||
} else {
|
|
||||||
display.setCursor(80, 40);
|
|
||||||
display.print("---");
|
|
||||||
}
|
|
||||||
|
|
||||||
// علامت درصد
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.print("%");
|
|
||||||
|
|
||||||
// ==================== خط جداساز ====================
|
|
||||||
display.drawFastHLine(0, 15, 128, SSD1306_WHITE); // بین خاک و DHT
|
|
||||||
display.drawFastHLine(0, 35, 128, SSD1306_WHITE); // بین DHT و SHT
|
|
||||||
|
|
||||||
display.display();
|
|
||||||
}
|
|
||||||
|
|
||||||
void printSerial() {
|
|
||||||
Serial1.print("Time: ");
|
|
||||||
Serial1.print(millis() / 1000);
|
|
||||||
Serial1.print("s | ");
|
|
||||||
|
|
||||||
Serial1.print("Soil: ");
|
|
||||||
Serial1.print(soilMoisturePercent);
|
|
||||||
Serial1.print("% | ");
|
|
||||||
|
|
||||||
Serial1.print("DHT: ");
|
|
||||||
if (dht11_temp != -999) {
|
|
||||||
Serial1.print(dht11_temp, 1);
|
|
||||||
Serial1.print("C ");
|
|
||||||
Serial1.print(dht11_humidity, 0);
|
|
||||||
Serial1.print("%");
|
|
||||||
} else {
|
|
||||||
Serial1.print("---");
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial1.print(" | ");
|
|
||||||
|
|
||||||
Serial1.print("SHT: ");
|
|
||||||
if (sht31Found && sht31_temp != -999) {
|
|
||||||
Serial1.print(sht31_temp, 1);
|
|
||||||
Serial1.print("C ");
|
|
||||||
Serial1.print(sht31_humidity, 0);
|
|
||||||
Serial1.print("%");
|
|
||||||
} else {
|
|
||||||
Serial1.print("---");
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial1.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
void displayStartup() {
|
|
||||||
display.clearDisplay();
|
|
||||||
|
|
||||||
// نمایش عنوان
|
|
||||||
display.setTextSize(LARGE_FONT_SIZE);
|
|
||||||
display.setCursor(15, 10);
|
|
||||||
display.println("SENSOR");
|
|
||||||
display.setCursor(20, 30);
|
|
||||||
display.println("SYSTEM");
|
|
||||||
|
|
||||||
display.setTextSize(SMALL_FONT_SIZE);
|
|
||||||
display.setCursor(40, 50);
|
|
||||||
display.println("READY");
|
|
||||||
|
|
||||||
display.display();
|
|
||||||
delay(1500);
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
#include <RCSwitch.h>
|
|
||||||
#include <DHT.h>
|
|
||||||
|
|
||||||
// پینها
|
|
||||||
#define DHTPIN 2
|
|
||||||
#define DHTTYPE DHT11
|
|
||||||
#define SOIL_MOISTURE_PIN A0
|
|
||||||
#define GAS_SENSOR_PIN A1
|
|
||||||
#define RF_TRANSMIT_PIN 9
|
|
||||||
|
|
||||||
// متن ثابت و کلیدها
|
|
||||||
|
|
||||||
const String PUBLIC_KEY = "as23f";
|
|
||||||
const String DEVICE_ID = "dr142";
|
|
||||||
|
|
||||||
// اشیاء
|
|
||||||
DHT dht(DHTPIN, DHTTYPE);
|
|
||||||
RCSwitch tx = RCSwitch();
|
|
||||||
|
|
||||||
// زمانبندی
|
|
||||||
unsigned long lastSendTime = 0;
|
|
||||||
const unsigned long sendInterval = 5000;
|
|
||||||
|
|
||||||
// تاخیر بین ارسال هر کاراکتر (ms)
|
|
||||||
const unsigned int INTER_CHAR_DELAY_MS = 15;
|
|
||||||
|
|
||||||
// XOR ساده
|
|
||||||
String encryptData(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void sendChar(uint8_t c) {
|
|
||||||
tx.send((unsigned long)c, 8);
|
|
||||||
delay(INTER_CHAR_DELAY_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void sendHexByte(uint8_t b) {
|
|
||||||
char hex[3];
|
|
||||||
sprintf(hex, "%02X", b); // حروف بزرگ
|
|
||||||
sendChar(hex[0]);
|
|
||||||
sendChar(hex[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
dht.begin();
|
|
||||||
|
|
||||||
tx.enableTransmit(RF_TRANSMIT_PIN);
|
|
||||||
tx.setProtocol(1);
|
|
||||||
tx.setPulseLength(300);
|
|
||||||
tx.setRepeatTransmit(3);
|
|
||||||
|
|
||||||
Serial.println(F("=== Transmitter Started ==="));
|
|
||||||
Serial.print(F("Public Key: ")); Serial.println(PUBLIC_KEY);
|
|
||||||
Serial.print(F("Device ID : ")); Serial.println(DEVICE_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// خواندن سنسورها
|
|
||||||
float temperature = dht.readTemperature();
|
|
||||||
float humidity = dht.readHumidity();
|
|
||||||
int soilMoisture = analogRead(SOIL_MOISTURE_PIN);
|
|
||||||
int gasValue = analogRead(GAS_SENSOR_PIN);
|
|
||||||
|
|
||||||
if (isnan(temperature) || isnan(humidity)) {
|
|
||||||
temperature = 0; humidity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
soilMoisture = constrain(soilMoisture, 0, 1023);
|
|
||||||
gasValue = constrain(gasValue, 0, 1023);
|
|
||||||
int tempInt = constrain((int)(temperature * 10), 0, 9999); // چهار رقمی
|
|
||||||
int humidityInt = constrain((int)(humidity * 10), 0, 9999); // چهار رقمی
|
|
||||||
|
|
||||||
// ⚠️ خیلی مهم: ۲۶ بایت برای نَل ترمینیتور
|
|
||||||
char dataString[26];
|
|
||||||
// قالب دقیقاً 25 کاراکتر تولید میکند: 5 + (S####) + (G####) + (T####) + (H####)
|
|
||||||
// 5 + 1+4 + 1+4 + 1+4 + 1+4 = 25
|
|
||||||
int n = snprintf(
|
|
||||||
dataString, sizeof(dataString),
|
|
||||||
"%sS%04dG%04dT%04dH%04d",
|
|
||||||
DEVICE_ID.c_str(), soilMoisture, gasValue, tempInt, humidityInt
|
|
||||||
);
|
|
||||||
|
|
||||||
// اگر بنا به هر دلیلی طول غیرمنتظره شد، همینجا خروج
|
|
||||||
if (n != 25) {
|
|
||||||
Serial.print(F("Format length unexpected: ")); Serial.println(n);
|
|
||||||
delay(500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String plain = String(dataString);
|
|
||||||
String enc = encryptData(plain, PUBLIC_KEY);
|
|
||||||
|
|
||||||
if (millis() - lastSendTime >= sendInterval) {
|
|
||||||
Serial.println(F("=== Data to Send ==="));
|
|
||||||
Serial.print(F("Original: ")); Serial.println(plain);
|
|
||||||
Serial.print(F("Length : ")); Serial.println(plain.length());
|
|
||||||
|
|
||||||
// 1) ارسال 5 کاراکتر کلید عمومی
|
|
||||||
for (int i = 0; i < PUBLIC_KEY.length(); i++) {
|
|
||||||
sendChar((uint8_t)PUBLIC_KEY[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) ارسال دیتای رمز شده به صورت HEX (هر بایت → دو کاراکتر)
|
|
||||||
Serial.print(F("Encrypted HEX: "));
|
|
||||||
for (int i = 0; i < enc.length(); i++) {
|
|
||||||
uint8_t b = (uint8_t)enc[i];
|
|
||||||
char hex[3]; sprintf(hex, "%02X", b);
|
|
||||||
Serial.print(hex); Serial.print(' ');
|
|
||||||
sendHexByte(b);
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
lastSendTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
#include <SoftwareSerial.h>
|
|
||||||
#define RX_PIN D3 // RX NodeMCU -> TX M66
|
|
||||||
#define TX_PIN D4 // TX NodeMCU -> RX M66
|
|
||||||
|
|
||||||
SoftwareSerial M66(RX_PIN, TX_PIN);
|
|
||||||
|
|
||||||
const String url = "http://amlakmodaberan.ir/1.html";
|
|
||||||
// =========================
|
|
||||||
// Timeout ها
|
|
||||||
// =========================
|
|
||||||
const unsigned long AT_TIMEOUT = 15000;
|
|
||||||
const unsigned long HTTP_TIMEOUT = 30000;
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// ارسال دستور AT با انتظار پاسخ
|
|
||||||
// =========================
|
|
||||||
bool sendAT(String cmd, String expected = "OK", unsigned long timeout = AT_TIMEOUT) {
|
|
||||||
Serial.print("[TX] "); Serial.println(cmd);
|
|
||||||
|
|
||||||
while (M66.available()) M66.read(); // پاکسازی بافر
|
|
||||||
|
|
||||||
M66.println(cmd);
|
|
||||||
|
|
||||||
unsigned long start = millis();
|
|
||||||
String response = "";
|
|
||||||
|
|
||||||
while (millis() - start < timeout) {
|
|
||||||
yield();
|
|
||||||
while (M66.available()) {
|
|
||||||
char c = M66.read();
|
|
||||||
response += c;
|
|
||||||
Serial.write(c);
|
|
||||||
|
|
||||||
if (response.indexOf(expected) != -1) return true;
|
|
||||||
if (response.indexOf("ERROR") != -1 || response.indexOf("+CME ERROR") != -1) {
|
|
||||||
Serial.println("❌ Command failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("⏱ Timeout waiting for response");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// راهاندازی GPRS و HTTP با QIREGAPP
|
|
||||||
// =========================
|
|
||||||
bool initGPRS() {
|
|
||||||
Serial.println("=== Initializing GPRS ===");
|
|
||||||
delay(2000);
|
|
||||||
|
|
||||||
sendAT("ATE0");
|
|
||||||
sendAT("AT+CMEE=2");
|
|
||||||
sendAT("AT+CFUN=1");
|
|
||||||
delay(2000);
|
|
||||||
|
|
||||||
if (!sendAT("AT+CPIN?", "READY")) return false;
|
|
||||||
|
|
||||||
// شبکه ثبت شده
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
if (sendAT("AT+CREG?", "+CREG: 0,1")) break;
|
|
||||||
delay(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sendAT("AT+CGATT=1")) return false;
|
|
||||||
delay(5000);
|
|
||||||
|
|
||||||
// reset context
|
|
||||||
sendAT("AT+QIFGCNT=0");
|
|
||||||
|
|
||||||
if (!sendAT("AT+QICSGP=1,\"mcinet\",\"\",\"\"")) return false;
|
|
||||||
|
|
||||||
// فعال کردن QIREGAPP به جای QIACT
|
|
||||||
if (!sendAT("AT+QIREGAPP", "OK", 30000)) return false;
|
|
||||||
|
|
||||||
// بررسی IP state
|
|
||||||
sendAT("AT+QISTAT", "STATE: IP GPRSACT");
|
|
||||||
|
|
||||||
// پیکربندی HTTP
|
|
||||||
sendAT("AT+QHTTPCFG=\"contextid\",1");
|
|
||||||
sendAT("AT+QHTTPCFG=\"responseheader\",1");
|
|
||||||
sendAT("AT+QHTTPCFG=\"timeout\",30000");
|
|
||||||
sendAT("AT+QHTTPCFG=\"requestheader\",1");
|
|
||||||
|
|
||||||
Serial.println("✅ GPRS ready!");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// ارسال HTTP GET
|
|
||||||
// =========================
|
|
||||||
bool sendHTTPRequest() {
|
|
||||||
Serial.println("=== Sending HTTP Request ===");
|
|
||||||
|
|
||||||
int len = url.length();
|
|
||||||
|
|
||||||
if (!sendAT("AT+QHTTPURL=" + String(len) + ",60", "CONNECT", 15000)) {
|
|
||||||
Serial.println("❌ URL setup failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
M66.print(url);
|
|
||||||
delay(200);
|
|
||||||
M66.write(0x1A); // Ctrl+Z
|
|
||||||
delay(1500);
|
|
||||||
|
|
||||||
if (!sendAT("AT+QHTTPGET=80", "OK", HTTP_TIMEOUT)) {
|
|
||||||
Serial.println("❌ HTTP GET failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sendAT("AT+QHTTPREAD", "+QHTTPREAD:", HTTP_TIMEOUT)) {
|
|
||||||
Serial.println("⚠️ No HTTP response body");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("✅ HTTP request done!");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// setup
|
|
||||||
// =========================
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
M66.begin(9600);
|
|
||||||
delay(2000);
|
|
||||||
|
|
||||||
int retry = 0;
|
|
||||||
while (retry < 3) {
|
|
||||||
if (initGPRS()) {
|
|
||||||
if (sendHTTPRequest()) break;
|
|
||||||
else Serial.println("⚠️ HTTP failed, retrying...");
|
|
||||||
} else {
|
|
||||||
Serial.println("❌ GPRS init failed, retrying...");
|
|
||||||
}
|
|
||||||
retry++;
|
|
||||||
delay(5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================
|
|
||||||
// loop
|
|
||||||
// =========================
|
|
||||||
void loop() {
|
|
||||||
yield(); // جلوگیری از WDT
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
MQ-7 Module - با ضرایب صحیح
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
const uint8_t ADC_PIN = PA2;
|
|
||||||
const uint8_t LED_PIN = PC13;
|
|
||||||
|
|
||||||
const float ADC_REF = 3.3;
|
|
||||||
const uint32_t ADC_MAX = 4095;
|
|
||||||
const float RL = 10000.0;
|
|
||||||
float R0 = -1.0;
|
|
||||||
const float CO_THRESHOLD = 50.0;
|
|
||||||
|
|
||||||
// ضرایب صحیح برای MQ-7 و گاز CO
|
|
||||||
const float CO_A = 99.042;
|
|
||||||
const float CO_B = -1.518;
|
|
||||||
|
|
||||||
float readVoltage() {
|
|
||||||
const int N = 10;
|
|
||||||
uint32_t sum = 0;
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
sum += analogRead(ADC_PIN);
|
|
||||||
delay(3);
|
|
||||||
}
|
|
||||||
float adc_avg = sum / float(N);
|
|
||||||
return adc_avg * (ADC_REF / ADC_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
float calcRs(float Vout) {
|
|
||||||
if (Vout <= 0.001) return 1e6;
|
|
||||||
|
|
||||||
// فرمول اصلاح شده: ما Vout را از سنسور میخوانیم که نسبت به 5V داخلی ماژول است
|
|
||||||
// بنابراین باید از 5.0 در فرمول استفاده کنیم، نه ADC_REF
|
|
||||||
return RL * (5.0 - Vout) / Vout; // این فرمول صحیح است
|
|
||||||
}
|
|
||||||
|
|
||||||
float toRealPPM(float Rs) {
|
|
||||||
if (R0 <= 0) return -1;
|
|
||||||
float ratio = Rs / R0;
|
|
||||||
|
|
||||||
// فرمول صحیح براساس دیتاشیت MQ-7
|
|
||||||
float ppm = CO_A * pow(ratio, CO_B);
|
|
||||||
|
|
||||||
return ppm;
|
|
||||||
}
|
|
||||||
|
|
||||||
void calibrateR0() {
|
|
||||||
Serial1.println("=== Calibrating R0 in clean air ===");
|
|
||||||
const int N = 50;
|
|
||||||
float sum_R0 = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
float V = readVoltage();
|
|
||||||
float Rs = calcRs(V);
|
|
||||||
|
|
||||||
// در هوای پاک: نسبت Rs/R0 ≈ 27.5 (طبق دیتاشیت)
|
|
||||||
// بنابراین: R0 = Rs / 27.5
|
|
||||||
sum_R0 += Rs / 27.5;
|
|
||||||
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
R0 = sum_R0 / N;
|
|
||||||
Serial1.print("Calibration complete. R0 = ");
|
|
||||||
Serial1.println(R0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial1.begin(115200);
|
|
||||||
pinMode(LED_PIN, OUTPUT);
|
|
||||||
digitalWrite(LED_PIN, LOW);
|
|
||||||
|
|
||||||
Serial1.println("MQ-7 CO Monitor - Waiting for warm up...");
|
|
||||||
delay(30000); // 30 ثانیه پیشگرمایش
|
|
||||||
Serial1.println("Ready. Send 'c' to calibrate.");
|
|
||||||
calibrateR0();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
if (Serial1.available()) {
|
|
||||||
char c = Serial1.read();
|
|
||||||
if (c == 'c' || c == 'C') calibrateR0();
|
|
||||||
}
|
|
||||||
|
|
||||||
float V = readVoltage();
|
|
||||||
float Rs = calcRs(V);
|
|
||||||
float ppm = toRealPPM(Rs);
|
|
||||||
|
|
||||||
Serial1.print("Vout=");
|
|
||||||
Serial1.print(V, 3);
|
|
||||||
Serial1.print("V, Rs=");
|
|
||||||
Serial1.print(Rs, 1);
|
|
||||||
Serial1.print("Ω");
|
|
||||||
|
|
||||||
if (R0 > 0) {
|
|
||||||
Serial1.print(", Rs/R0=");
|
|
||||||
Serial1.print(Rs/R0, 3);
|
|
||||||
Serial1.print(", CO=");
|
|
||||||
Serial1.print(ppm, 1);
|
|
||||||
Serial1.println("ppm");
|
|
||||||
|
|
||||||
// هشدار
|
|
||||||
if (ppm > CO_THRESHOLD) {
|
|
||||||
digitalWrite(LED_PIN, HIGH);
|
|
||||||
Serial1.println("⚠️ WARNING: High CO level!");
|
|
||||||
} else {
|
|
||||||
digitalWrite(LED_PIN, LOW);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Serial1.println(" [Not calibrated]");
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(2000);
|
|
||||||
}
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
#include <SoftwareSerial.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include "RTClib.h"
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <SD.h>
|
|
||||||
|
|
||||||
// تعریف نام مستعار برای حل مشکل تداخل
|
|
||||||
using SDLibFile = File;
|
|
||||||
|
|
||||||
SoftwareSerial espSerial(4, 5);
|
|
||||||
const int chipSelect = 10;
|
|
||||||
RTC_DS3231 rtc;
|
|
||||||
|
|
||||||
// کاهش مصرف حافظه با استفاده از PROGMEM
|
|
||||||
const char daysOfTheWeek[7][4] PROGMEM = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
||||||
|
|
||||||
// پیشتعریف توابع
|
|
||||||
bool saveDataToFile(const char* filename, const char* data, bool append = true);
|
|
||||||
bool formatSDCard();
|
|
||||||
void sendCommand(const char* cmd, int delayTime);
|
|
||||||
void sendHTTPResponse(int connectionId, const char* content);
|
|
||||||
const char* readSensorData();
|
|
||||||
const char* handleSaveData(const char* query);
|
|
||||||
const char* handleReadData(const char* query);
|
|
||||||
const char* handleFormatCard();
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(9600);
|
|
||||||
|
|
||||||
// راه اندازی کارت SD
|
|
||||||
if (!SD.begin(chipSelect)) {
|
|
||||||
Serial.println(F("SD Card Error!"));
|
|
||||||
} else {
|
|
||||||
Serial.println(F("SD Card OK"));
|
|
||||||
}
|
|
||||||
|
|
||||||
espSerial.begin(9600);
|
|
||||||
|
|
||||||
// راه اندازی RTC
|
|
||||||
if (!rtc.begin()) {
|
|
||||||
Serial.println(F("RTC Error!"));
|
|
||||||
while (1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rtc.lostPower()) {
|
|
||||||
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(1000);
|
|
||||||
Serial.println(F("Initializing ESP..."));
|
|
||||||
|
|
||||||
// کاهش دستورات ESP
|
|
||||||
const char* commands[] = {
|
|
||||||
"AT", "AT+RST", "AT+CWMODE=2",
|
|
||||||
"AT+CWSAP=\"ESP_CTRL\",\"12345678\",1,3",
|
|
||||||
"AT+CIPMUX=1", "AT+CIPSERVER=1,80"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
sendCommand(commands[i], 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println(F("AP Ready!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
checkESP();
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendCommand(const char* cmd, int delayTime) {
|
|
||||||
Serial.print(F("Sending: "));
|
|
||||||
Serial.println(cmd);
|
|
||||||
espSerial.println(cmd);
|
|
||||||
|
|
||||||
delay(delayTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendHTTPResponse(int connectionId, const char* content) {
|
|
||||||
char response[512];
|
|
||||||
snprintf(response, sizeof(response),
|
|
||||||
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n%s",
|
|
||||||
content);
|
|
||||||
|
|
||||||
char cmd[50];
|
|
||||||
snprintf(cmd, sizeof(cmd), "AT+CIPSEND=%d,%d", connectionId, strlen(response));
|
|
||||||
|
|
||||||
sendCommand(cmd, 50);
|
|
||||||
sendCommand(response, 100);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof(cmd), "AT+CIPCLOSE=%d", connectionId);
|
|
||||||
sendCommand(cmd, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkESP() {
|
|
||||||
static char buffer[128];
|
|
||||||
static int index = 0;
|
|
||||||
|
|
||||||
while (espSerial.available()) {
|
|
||||||
char c = espSerial.read();
|
|
||||||
if (index < sizeof(buffer) - 1) {
|
|
||||||
buffer[index++] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '\n') {
|
|
||||||
buffer[index] = '\0';
|
|
||||||
|
|
||||||
if (strstr(buffer, "+IPD") != NULL) {
|
|
||||||
int connId = atoi(&buffer[5]);
|
|
||||||
handleRequest(connId, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleRequest(int connectionId, char* request) {
|
|
||||||
char* getStart = strstr(request, "GET /");
|
|
||||||
if (!getStart) return;
|
|
||||||
|
|
||||||
char* pathStart = getStart + 5;
|
|
||||||
char* pathEnd = strchr(pathStart, ' ');
|
|
||||||
if (!pathEnd) return;
|
|
||||||
|
|
||||||
char path[32] = {0};
|
|
||||||
strncpy(path, pathStart, min((int)(pathEnd - pathStart), 31));
|
|
||||||
|
|
||||||
char* query = strchr(path, '?');
|
|
||||||
if (query) {
|
|
||||||
*query = '\0';
|
|
||||||
query++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print(F("Path: "));
|
|
||||||
Serial.println(path);
|
|
||||||
|
|
||||||
const char* response;
|
|
||||||
|
|
||||||
if (strcmp(path, "se") == 0) {
|
|
||||||
response = readSensorData();
|
|
||||||
}
|
|
||||||
else if (strcmp(path, "save") == 0) {
|
|
||||||
response = handleSaveData(query);
|
|
||||||
}
|
|
||||||
else if (strcmp(path, "format") == 0) {
|
|
||||||
response = handleFormatCard();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
response = "Available endpoints: /se, /save, /format";
|
|
||||||
}
|
|
||||||
|
|
||||||
sendHTTPResponse(connectionId, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* readSensorData() {
|
|
||||||
static char buffer[64];
|
|
||||||
DateTime now = rtc.now();
|
|
||||||
|
|
||||||
snprintf(buffer, sizeof(buffer), "Temp: %.1fC, Time: %02d:%02d:%02d",
|
|
||||||
rtc.getTemperature(), now.hour(), now.minute(), now.second());
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* handleSaveData(const char* query) {
|
|
||||||
if (query && strlen(query) > 0) {
|
|
||||||
if (saveDataToFile("data.txt", query, true)) {
|
|
||||||
return "Data saved";
|
|
||||||
}
|
|
||||||
return "Save error";
|
|
||||||
}
|
|
||||||
return "No data";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* handleFormatCard() {
|
|
||||||
return formatSDCard() ? "Formatted" : "Format error";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool saveDataToFile(const char* filename, const char* data, bool append) {
|
|
||||||
SDLibFile dataFile;
|
|
||||||
|
|
||||||
if (append) {
|
|
||||||
dataFile = SD.open(filename, FILE_WRITE);
|
|
||||||
} else {
|
|
||||||
if (SD.exists(filename)) {
|
|
||||||
SD.remove(filename);
|
|
||||||
}
|
|
||||||
dataFile = SD.open(filename, FILE_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dataFile) return false;
|
|
||||||
|
|
||||||
bool result = dataFile.println(data);
|
|
||||||
dataFile.close();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool formatSDCard() {
|
|
||||||
// فقط حذف فایلهای اصلی
|
|
||||||
const char* files[] = {"data.txt", "test.txt"};
|
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
if (SD.exists(files[i])) {
|
|
||||||
SD.remove(files[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// تست نوشتن
|
|
||||||
return saveDataToFile("test.txt", "Formatted", false);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user