Compare commits

...

3 Commits

Author SHA1 Message Date
2712119071 backup 14041130 2026-02-19 06:17:44 +03:30
18b30d7c3f fix errot 2026-01-06 19:08:38 +03:30
abe1a64338 new code 2026-01-06 19:06:03 +03:30
49 changed files with 12229 additions and 5675 deletions

View File

@@ -0,0 +1,433 @@
#include <Wire.h>
// تعریف پین‌های I2C
#define I2C_SDA PB9
#define I2C_SCL PB8
// آدرس سنسورها
#define BH1750_ADDR 0x23
#define SHT31_ADDR 0x44
// حالتهای اندازه‌گیری BH1750
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20
// دستورات SHT31
#define SHT31_MEAS_HIGHREP 0x2400
#define SHT31_MEAS_MEDREP 0x240B
#define SHT31_MEAS_LOWREP 0x2416
#define SHT31_SOFTRESET 0x30A2
#define SHT31_HEATER_ENABLE 0x306D
#define SHT31_HEATER_DISABLE 0x3066
#define SHT31_STATUS_REG 0xF32D
#define SHT31_CLEAR_STATUS 0x3041
// متغیرهای ذخیره‌سازی مقادیر
float lastLightLevel = 0.0;
float lastTemperature = 0.0;
float lastHumidity = 0.0;
// متغیرهای تشخیص خطا
bool bh1750Available = false;
bool sht31Available = false;
uint8_t bh1750ErrorCount = 0;
uint8_t sht31ErrorCount = 0;
const uint8_t MAX_ERRORS = 5;
// زمان آخرین اندازه‌گیری موفق
unsigned long lastBh1750Success = 0;
unsigned long lastSht31Success = 0;
// تابع بازیابی نرم I2C (بدون ریست سخت‌افزاری)
void recoverI2C() {
Serial1.println("Attempting I2C recovery...");
// آزاد کردن خطوط
pinMode(I2C_SDA, INPUT_PULLUP);
pinMode(I2C_SCL, INPUT_PULLUP);
delay(1000);
// تولید پالس‌های ساعت برای آزادسازی هر دستگاه قفل شده
pinMode(I2C_SCL, OUTPUT);
for (int i = 0; i < 9; i++) {
digitalWrite(I2C_SCL, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL, HIGH);
delayMicroseconds(5);
}
// ایجاد شرط STOP
pinMode(I2C_SDA, OUTPUT);
digitalWrite(I2C_SDA, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL, HIGH);
delayMicroseconds(5);
digitalWrite(I2C_SDA, HIGH);
delayMicroseconds(5);
// بازگشت به حالت عادی
Wire.begin();
Serial1.println("I2C recovery completed");
}
// تابع بررسی وجود دستگاه روی باس I2C
bool checkDevice(uint8_t address) {
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
return (error == 0);
}
bool initBH1750() {
if (!checkDevice(BH1750_ADDR)) {
Serial1.println("BH1750 not found!");
bh1750Available = false;
return false;
}
bh1750Available = true;
bh1750ErrorCount = 0;
Serial1.println("BH1750 ready (One-Time mode)");
return true;
}
bool readBH1750(float &lightLevel) {
if (!bh1750Available && bh1750ErrorCount < MAX_ERRORS) {
if (!initBH1750()) return false;
}
if (!bh1750Available) return false;
// ارسال فرمان One-Time High Resolution
Wire.beginTransmission(BH1750_ADDR);
Wire.write(BH1750_ONE_TIME_HIGH_RES_MODE);
byte error = Wire.endTransmission();
if (error != 0) {
bh1750ErrorCount++;
Serial1.println("BH1750 command error");
return false;
}
// زمان تبدیل (حداکثر 180ms طبق دیتاشیت)
delay(180);
// درخواست 2 بایت داده
Wire.requestFrom(BH1750_ADDR, 2);
if (Wire.available() == 2) {
uint16_t value = Wire.read();
value <<= 8;
value |= Wire.read();
lightLevel = value / 1.2;
if (lightLevel >= 0 && lightLevel <= 65535) {
lastLightLevel = lightLevel;
bh1750ErrorCount = 0;
lastBh1750Success = millis();
return true;
}
}
// در صورت خطا
bh1750ErrorCount++;
Serial1.println("Error reading BH1750");
if (bh1750ErrorCount >= MAX_ERRORS) {
bh1750Available = false;
Serial1.println("BH1750 marked as unavailable");
}
return false;
}
// تابع مقداردهی اولیه BH1750
bool __initBH1750() {
if (!checkDevice(BH1750_ADDR)) {
Serial1.println("BH1750 not found!");
bh1750Available = false;
return false;
}
// ارسال دستور اندازه‌گیری پیوسته
Wire.beginTransmission(BH1750_ADDR);
Wire.write(BH1750_CONTINUOUS_HIGH_RES_MODE);
byte error = Wire.endTransmission();
if (error == 0) {
bh1750Available = true;
bh1750ErrorCount = 0;
Serial1.println("BH1750 initialized successfully");
return true;
} else {
bh1750Available = false;
Serial1.print("BH1750 init error: ");
Serial1.println(error);
return false;
}
}
// تابع خواندن نور از BH1750
bool __readBH1750(float &lightLevel) {
if (!bh1750Available && bh1750ErrorCount < MAX_ERRORS) {
if (initBH1750()) {
delay(180); // تأخیر برای اولین اندازه‌گیری
}
}
if (!bh1750Available) return false;
// درخواست 2 بایت داده
Wire.requestFrom(BH1750_ADDR, 2);
if (Wire.available() == 2) {
uint16_t value = Wire.read();
value <<= 8;
value |= Wire.read();
lightLevel = value / 1.2; // محاسبه لوکس
// اعتبارسنجی مقدار
if (lightLevel >= 0 && lightLevel <= 65535) {
lastLightLevel = lightLevel;
bh1750ErrorCount = 0;
lastBh1750Success = millis();
return true;
}
}
// در صورت خطا
bh1750ErrorCount++;
Serial1.println("Error reading BH1750");
if (bh1750ErrorCount >= MAX_ERRORS) {
bh1750Available = false;
Serial1.println("BH1750 marked as unavailable");
}
return false;
}
// تابع مقداردهی اولیه SHT31
bool initSHT31() {
if (!checkDevice(SHT31_ADDR)) {
Serial1.println("SHT31 not found!");
sht31Available = false;
return false;
}
// ارسال دستور ریست نرم
Wire.beginTransmission(SHT31_ADDR);
Wire.write(SHT31_SOFTRESET >> 8);
Wire.write(SHT31_SOFTRESET & 0xFF);
byte error = Wire.endTransmission();
if (error != 0) {
sht31Available = false;
Serial1.print("SHT31 reset error: ");
Serial1.println(error);
return false;
}
delay(10); // تأخیر بعد از ریست
// غیرفعال کردن هیتر (برای مصرف کمتر و عمر طولانی‌تر)
Wire.beginTransmission(SHT31_ADDR);
Wire.write(SHT31_HEATER_DISABLE >> 8);
Wire.write(SHT31_HEATER_DISABLE & 0xFF);
error = Wire.endTransmission();
if (error == 0) {
sht31Available = true;
sht31ErrorCount = 0;
Serial1.println("SHT31 initialized successfully");
return true;
} else {
sht31Available = false;
Serial1.print("SHT31 init error: ");
Serial1.println(error);
return false;
}
}
// تابع خواندن دما و رطوبت از SHT31
bool readSHT31(float &temperature, float &humidity) {
if (!sht31Available && sht31ErrorCount < MAX_ERRORS) {
initSHT31();
}
if (!sht31Available) return false;
// ارسال دستور اندازه‌گیری با دقت بالا
Wire.beginTransmission(SHT31_ADDR);
Wire.write(SHT31_MEAS_HIGHREP >> 8);
Wire.write(SHT31_MEAS_HIGHREP & 0xFF);
byte error = Wire.endTransmission();
if (error != 0) {
sht31ErrorCount++;
Serial1.println("Error sending measurement command to SHT31");
return false;
}
// تأخیر برای تبدیل (حداکثر 15ms برای حالت High Rep)
delay(15);
// خواندن 6 بایت داده
Wire.requestFrom(SHT31_ADDR, 6);
if (Wire.available() == 6) {
uint16_t tempRaw = Wire.read();
tempRaw <<= 8;
tempRaw |= Wire.read();
uint8_t tempCRC = Wire.read();
uint16_t humRaw = Wire.read();
humRaw <<= 8;
humRaw |= Wire.read();
uint8_t humCRC = Wire.read();
// تبدیل مقادیر
temperature = -45 + (175 * (float)tempRaw / 65535.0);
humidity = 100 * (float)humRaw / 65535.0;
// اعتبارسنجی مقادیر
if (temperature >= -40 && temperature <= 125 &&
humidity >= 0 && humidity <= 100) {
lastTemperature = temperature;
lastHumidity = humidity;
sht31ErrorCount = 0;
lastSht31Success = millis();
return true;
}
}
// در صورت خطا
sht31ErrorCount++;
Serial1.println("Error reading SHT31");
if (sht31ErrorCount >= MAX_ERRORS) {
sht31Available = false;
Serial1.println("SHT31 marked as unavailable");
}
return false;
}
// تابع بررسی دوره‌ای سلامت باس I2C
void checkI2CHealth() {
static unsigned long lastCheck = 0;
unsigned long currentMillis = millis();
// هر 30 ثانیه بررسی کن
if (currentMillis - lastCheck >= 30000) {
lastCheck = currentMillis;
bool bh1750WasAvailable = bh1750Available;
bool sht31WasAvailable = sht31Available;
// بررسی BH1750
if (checkDevice(BH1750_ADDR)) {
if (!bh1750WasAvailable) {
Serial1.println("BH1750 reappeared, reinitializing...");
initBH1750();
}
} else if (bh1750WasAvailable) {
Serial1.println("BH1750 disappeared!");
bh1750Available = false;
}
// بررسی SHT31
if (checkDevice(SHT31_ADDR)) {
if (!sht31WasAvailable) {
Serial1.println("SHT31 reappeared, reinitializing...");
initSHT31();
}
} else if (sht31WasAvailable) {
Serial1.println("SHT31 disappeared!");
sht31Available = false;
}
// اگر هر دو دستگاه از دست رفته‌اند، بازیابی باس I2C
if (!bh1750Available && !sht31Available) {
recoverI2C();
// تلاش مجدد برای مقداردهی اولیه
delay(100);
initBH1750();
initSHT31();
}
}
}
void setup() {
Serial1.begin(115200);
Serial1.println("Starting STM32 with BH1750 and SHT31...");
// مقداردهی اولیه I2C
Wire.setSDA(I2C_SDA);
Wire.setSCL(I2C_SCL);
Wire.begin();
Wire.setClock(100000); // 100kHz برای پایداری بهتر
// افزایش timeout برای جلوگیری از hang شدن
Wire.setTimeout(1000);
delay(1000); // تأخیر برای پایدار شدن
// مقداردهی اولیه سنسورها
initBH1750();
initSHT31();
Serial1.println("Setup completed");
}
void loop() {
static unsigned long lastRead = 0;
unsigned long currentMillis = millis();
// خواندن هر 2 ثانیه
if (currentMillis - lastRead >= 2000) {
lastRead = currentMillis;
// خواندن BH1750
float light;
if (readBH1750(light)) {
Serial1.print("Light: ");
Serial1.print(light);
Serial1.println(" lux");
} else {
Serial1.print("Using last BH1750 value: ");
Serial1.print(lastLightLevel);
Serial1.println(" lux");
}
delay(500);
// خواندن SHT31
float temp, hum;
if (readSHT31(temp, hum)) {
Serial1.print("Temperature: ");
Serial1.print(temp);
Serial1.print(" °C, Humidity: ");
Serial1.print(hum);
Serial1.println(" %");
} else {
Serial1.print("Using last SHT31 values - Temp: ");
Serial1.print(lastTemperature);
Serial1.print(" °C, Hum: ");
Serial1.print(lastHumidity);
Serial1.println(" %");
}
Serial1.println("---");
}
// بررسی سلامت دوره‌ای
checkI2CHealth();
// تأخیر کوتاه برای جلوگیری از overload
delay(10);
}

197
14041130/MQ7-1/MQ7-1.ino Normal file
View File

@@ -0,0 +1,197 @@
#include <U8g2lib.h>
#include <Wire.h>
#include "Voltage_Reader.h"
// تنظیمات OLED
#define I2C_SDA_PIN PB9
#define I2C_SCL_PIN PB8
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// ========== پارامترهای مدار MQ7 (مقادیر دقیق اندازه‌گیری‌شده) ==========
#define VREF_ADC 3.3f // ولتاژ مرجع ADC
#define ADC_RESOLUTION 4095.0f // رزولوشن ADC 12 بیت
#define R1 2190.0f // مقاومت سری اول (اهم)
#define R2 3270.0f // مقاومت به زمین (اهم)
#define RL_SENSOR 839.0f // مقاومت بار ماژول (مقاومت بین خروجی آنالوگ و GND)
#define ADC_PIN PA2 // پین ADC
#define NUM_SAMPLES 10 // تعداد نمونه برای میانگین‌گیری
// پارامترهای سنسور MQ7 برای تبدیل به ppm (از دیتاشیت)
#define CO_A 1.9f // ضریب A
#define CO_B -0.6f // ضریب B
// ============================ متغیرهای سراسری ============================
// متغیرهای جهانی
float VCC_SENSOR = 4.9f; // ولتاژ تغذیه سنسور MQ7
float MQ7_R0 = 21000.0; // مقدار R0 (پس از کالیبراسیون) تقریب اولیه ۲۰ کیلواهم
bool calibrationComplete = false;
unsigned long lastReadTime = 0;
const unsigned long readInterval = 3000;
float volage;
float readSensorVoltage() {
uint32_t sum = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
sum += analogRead(ADC_PIN);
delay(10);
}
float adcValue = sum / (float)NUM_SAMPLES;
float vAdc = (adcValue / ADC_RESOLUTION) * VREF_ADC;
// محاسبه ولتاژ خروجی سنسور با جبران تقسیم ولتاژ (R1 و R2)
return vAdc * (R1 + R2) / R2;
}
float calculateRs(float vOut) {
if (vOut <= 0.0f) return 0.0f;
return RL_SENSOR * (VCC_SENSOR / vOut - 1.0f);
}
float calculatePPM(float rs) {
float ratio = rs / MQ7_R0;
if (ratio <= 0.0f) return 0.0f;
return CO_A * pow(ratio, CO_B);
}
// ========== کالیبراسیون MQ7 ==========
void calibrateMQ7() {
Serial1.println("Starting calibration in clean air...");
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 30, "Calibrating ...");
u8g2.drawStr(0, 50, "Wait 120 seconds");
u8g2.sendBuffer();
// صبر برای پایدار شدن سنسور (توصیه: حداقل ۲ دقیقه، ولی برای نمونه ۳۰ ثانیه)
delay(120000);
float sumRs = 0;
const int samples = 50;
for (int i = 0; i < samples; i++) {
float vOut = readSensorVoltage();
float rs = calculateRs(vOut);
sumRs += rs;
delay(20);
}
//////////MQ7_R0 = sumRs / samples; // در هوای پاک R0 = Rs
calibrationComplete = true;
Serial1.print("[MQ7] Calibration complete. R0 = ");
Serial1.println(MQ7_R0, 1);
Serial1.println("You can update the #define R0 in code with this value for future use.");
}
// ========== خواندن CO با استفاده از پارامترهای جدید ==========
float readCO() {
if (!calibrationComplete) return -1.0f;
float vOut = readSensorVoltage();
float rs = calculateRs(vOut);
float ppm = calculatePPM(rs);
// محدود کردن به بازه معقول
ppm = constrain(ppm, 0.0f, 5000.0f);
return ppm;
}
// نمایش روی OLED
void displayCO(int ppm) {
if(ppm<0)
return;
u8g2.clearBuffer();
// انتخاب فونت بر اساس اندازه عدد
if (ppm < 1000) {
// برای اعداد تا ۳ رقمی از فونت 35 پیکسل
u8g2.setFont(u8g2_font_fub35_tr);
// موقعیت مرکزی
if (ppm < 10) {
u8g2.setCursor(50, 55);
} else if (ppm < 100) {
u8g2.setCursor(25, 55);
} else {
u8g2.setCursor(10, 55);
}
} else {
// برای اعداد ۴ رقمی از فونت 25 پیکسل
u8g2.setFont(u8g2_font_fub25_tr);
u8g2.setCursor(10, 60);
}
u8g2.print(ppm);
// نمایش واحد ppm در پایین
u8g2.setFont(u8g2_font_ncenB12_tr);
u8g2.setCursor(90, 60);
u8g2.print("ppm");
u8g2.sendBuffer();
}
void setup() {
Serial1.begin(115200);
delay(2000);
// تنظیم پین I2C
Wire.setSDA(I2C_SDA_PIN);
Wire.setSCL(I2C_SCL_PIN);
Wire.begin();
// راه‌اندازی OLED
u8g2.begin();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.clearBuffer();
u8g2.drawStr(0, 35, "Sensor Calibrating...");
u8g2.sendBuffer();
analogReadResolution(12);
bool voltageOK = voltageReader.testCircuit();
if (!voltageOK) {
Serial1.println("⚠️ Voltage issue detected!");
}
// 2. اطلاعات دیباگ ولتاژ
voltageReader.debugInfo();
volage=voltageReader.readVoltage();
VCC_SENSOR=volage;
// کالیبراسیون اولیه سنسور
calibrateMQ7();
// نمایش پیام راه‌اندازی
/*u8g2.clearBuffer();
u8g2.drawStr(0, 40, "MQ-7 Ready!");
u8g2.drawStr(0, 60, "Monitoring CO...");
u8g2.sendBuffer();*/
delay(2000);
}
void loop() {
if (millis() - lastReadTime >= readInterval) {
volage=voltageReader.readVoltage();
VCC_SENSOR=volage;
float ppm = readCO();
int ppm_rounded = round(ppm);
displayCO(ppm_rounded);
//voltageReader.debugInfo();
// نمایش در سریال مانیتور (اختیاری)
Serial1.print("CO Concentration: ");
Serial1.print(ppm);
Serial1.println(" ppm");
Serial1.print(" VCC_SENSOR:");
Serial1.println(VCC_SENSOR);
lastReadTime = millis();
}
delay(1000);
}

View File

@@ -0,0 +1,148 @@
#ifndef VOLTAGE_READER_H
#define VOLTAGE_READER_H
#include <Arduino.h>
#define VOLTAGE_DIVIDER_PIN PA0 // پین خواندن ولتاژ
#define BATTERY_VOLTAGE_PIN_A3 PA3 // پین جدید برای باتری
#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
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

View File

@@ -0,0 +1,98 @@
// BatteryReader.h
#ifndef BATTERY_READER_H
#define BATTERY_READER_H
#include <Arduino.h>
#include "Config.h"
class BatteryReader {
private:
uint8_t batteryPin;
float filteredVoltage = 0.0;
bool firstReading = true;
const float alpha = 0.3;
// پارامترهای مخصوص باتری لیتیومی
const float BATTERY_RATIO = (10000.0 + 10000.0) / 10000.0; // اگر مقسم ولتاژ مشابه است
const float BATTERY_FULL_VOLTAGE = 4.2; // ولتاژ کامل باتری لیتیومی
const float BATTERY_EMPTY_VOLTAGE = 3.0; // ولتاژ خالی
public:
BatteryReader(uint8_t pin) : batteryPin(pin) {
init();
}
void init() {
pinMode(batteryPin, INPUT_ANALOG);
analogReadResolution(12);
delay(50);
for (int i = 0; i < 5; i++) {
analogRead(batteryPin);
delay(1);
}
}
float readRawVoltage() {
int samples = 32;
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += analogRead(batteryPin);
delayMicroseconds(20);
}
int rawValue = sum / samples;
float voltageAtPin = (rawValue * ADC_REF_VOLTAGE) / ADC_RESOLUTION;
float batteryVoltage = voltageAtPin * BATTERY_RATIO;
return batteryVoltage;
}
float readVoltage() {
float currentVoltage = readRawVoltage();
if (firstReading) {
filteredVoltage = currentVoltage;
firstReading = false;
} else {
filteredVoltage = (alpha * currentVoltage) + ((1 - alpha) * filteredVoltage);
}
return currentVoltage;
}
// محاسبه درصد شارژ باتری
float getBatteryPercentage() {
float voltage = readVoltage();
// محدود کردن ولتاژ به محدوده تعریف شده
voltage = constrain(voltage, BATTERY_EMPTY_VOLTAGE, BATTERY_FULL_VOLTAGE);
// محاسبه درصد
float percentage = ((voltage - BATTERY_EMPTY_VOLTAGE) /
(BATTERY_FULL_VOLTAGE - BATTERY_EMPTY_VOLTAGE)) * 100.0;
return constrain(percentage, 0, 100);
}
void debugInfo() {
float rawVoltage = readRawVoltage();
int rawADC = analogRead(batteryPin);
float pinVoltage = rawVoltage / BATTERY_RATIO;
Serial1.println("\n🔋 Battery Debug Info:");
Serial1.print("Raw ADC value: ");
Serial1.println(rawADC);
Serial1.print("Voltage at pin: ");
Serial1.print(pinVoltage, 3);
Serial1.println("V");
Serial1.print("Battery voltage: ");
Serial1.print(rawVoltage, 3);
Serial1.println("V");
Serial1.print("Battery percentage: ");
Serial1.print(getBatteryPercentage(), 1);
Serial1.println("%");
}
};
#endif

157
14041130/TestLCD/Config.h Normal file
View File

@@ -0,0 +1,157 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
#define I2C_SDA_PIN PB9
#define I2C_SCL_PIN PB8
#define SHT31_ADDRESS 0x44
#define BH1750_ADDRESS 0x23
// ========== تنظیمات Timing (از کد اول) ==========
#define READ_INTERVAL 2000UL // ms - خواندن سنسورها
#define HEALTH_CHECK_INTERVAL 30000UL // ms - بررسی سلامت
#define RECOVERY_COOLDOWN 5000UL // ms - فاصله بین recoveryها
#define MAX_ERROR_COUNT 1000 // حداکثر خطاهای ثبت‌شده
unsigned long lastBh1750Success = 0;
unsigned long lastSht31Success = 0;
uint8_t bh1750ErrorCount = 0;
uint8_t sht31ErrorCount = 0;
const uint8_t MAX_ERRORS = 5;
float lastLightLevel = 0.0;
float lastTemperature = 0.0;
float lastHumidity = 0.0;
// -------------------- پیکربندی --------------------
#define FLASH_CS PB12
#define FLASH_MOSI PB15
#define FLASH_MISO PB14
#define FLASH_SCK PB13
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
//#define MQ7_PIN PA2
//#define VCC 4.63
#define RL 790.0 //10000.0
//#define VDIV_RATIO 0.03017 //0.681
//#define SDA_PIN PB9
//#define SCL_PIN PB8
#define PWRKEY_PIN PB5
#define SENSOR_READ_INTERVAL 5000
#define POWER_PIN PA1
#define TFT_WIDTH 240
#define TFT_HEIGHT 240
// #define TFT_CS PB0 // تغییر به پین‌های شما
// #define TFT_DC PA4
// #define TFT_RST PA6
// #define TFT_BL PB1
#define VOLTAGE_DIVIDER_PIN PA0 // پین خواندن ولتاژ
#define BATTERY_VOLTAGE_PIN_A3 PA3 // پین جدید برای باتری
#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;
int oldPower;
float batteryVoltage;
float batteryPercent;
// اضافه شدن برای نمایشگر
float tempPercent; // درصد دما (0-100)
float humPercent; // درصد رطوبت (0-100)
float lightPercent; // درصد نور (0-100)
float gasPercent; // درصد گاز (0-100)
};
// -------------------- توابع کمکی --------------------
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);
}
inline void calculatePercentages(SensorData& data) {
// دما: فرض 0-50 درجه سانتیگراد
data.tempPercent = (data.temperature * 100) / 50.0;
if (data.tempPercent > 100) data.tempPercent = 100;
if (data.tempPercent < 0) data.tempPercent = 0;
// رطوبت: 0-100%
data.humPercent = data.humidity;
if (data.humPercent > 100) data.humPercent = 100;
if (data.humPercent < 0) data.humPercent = 0;
// نور: فرض 0-10000 لوکس
data.lightPercent = (data.lightLux * 100) / 10000.0;
if (data.lightPercent > 100) data.lightPercent = 100;
if (data.lightPercent < 0) data.lightPercent = 0;
// گاز: فرض 0-200 ppm (خطای تایپی تصحیح شد)
data.gasPercent = (data.coPPM * 100) / 200.0;
if (data.gasPercent > 100) data.gasPercent = 100;
if (data.gasPercent < 0) data.gasPercent = 0;
}
#endif // CONFIG_H

View File

@@ -0,0 +1,342 @@
#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();
display.setCursor(0, 4);
if(currentData.power==1)
display.print("P:");
else
display.print("B:");
display.setCursor(15, 4);
display.print(String(currentData.volage, 1));
display.setCursor(45, 4);
display.print("VB:");
display.setCursor(65, 4);
display.print(String(currentData.batteryVoltage, 2));
display.setCursor(97, 4);
display.print("D:");
display.setCursor(107, 4);
display.print(String(currentData.batteryPercent,0)+"%");
display.setFont(&FreeSans9pt7b);
// خط جداکننده
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:
case 1:
case 2:
case 3:
case 5:
displayAllParameters();
break;
// case 1:
// displayTemperature();
// break;
// case 2:
// displayHumidity();
// break;
// case 3:
// displayCO();
// break;
// case 4:
// displayLight();
// break;
case 4:
displaySIMStatus();
break;
}
}
#endif // DISPLAY_H

503
14041130/TestLCD/Display.h Normal file
View File

@@ -0,0 +1,503 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include <math.h>
#include "Config.h"
// ==================== فونت‌ها ====================
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
// ==================== تنظیمات LCD ====================
#define TFT_CS PB0
#define TFT_DC PA4
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 240
#define LCD_CENTER_X 120
#define LCD_CENTER_Y 120
// ==================== رنگ‌های سفارشی ====================
#define COLOR_BG_DARK 0x0000 // مشکی
#define COLOR_TEMP 0xF800 // قرمز برای دما
#define COLOR_HUM 0x001F // آبی برای رطوبت
#define COLOR_LIGHT 0xFFE0 // زرد برای نور
#define COLOR_GAS 0xFD20 // نارنجی برای گاز
#define COLOR_BATTERY 0x07E0 // سبز برای باتری
#define COLOR_POWER 0x07FF // آبی فیروزه‌ای برای برق
#define COLOR_TEXT 0xFFFF // سفید برای متن
#define COLOR_TEXT_DIM 0x7BEF // خاکستری برای متن کمرنگ
#define COLOR_HEADER 0x8410 // خاکستری تیره برای هدر
// ==================== متغیرهای خارجی ====================
extern Arduino_GC9A01* tft;
extern DeviceConfig config;
extern SensorData currentData;
extern bool sht31Connected;
extern bool lightSensorConnected;
extern bool coSensorConnected;
extern bool calibrationComplete;
extern int signalStrength;
extern String lastMessage;
extern unsigned long lastDisplayChange;
// پیش‌اعلام توابع
void displayAllParameters();
void updateDisplay();
void initDisplay();
// تعریف توابع
// تابع نمایش مقدار با عنوان
inline void drawValueWithLabel(String label, String value, int x, int y, uint16_t valueColor) {
if (!tft) return;
// نمایش عنوان
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
tft->setCursor(x, y);
tft->print(label);
// نمایش مقدار
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(label + ":", 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(valueColor, COLOR_BG_DARK);
tft->setCursor(x + w + 5, y);
tft->print(value);
}
// تابع نمایش هدر
inline void drawHeader() {
if (!tft) return;
// پس زمینه هدر
tft->fillRect(0, 0, SCREEN_WIDTH, 40, COLOR_HEADER);
// نمایش عنوان دستگاه
tft->setFont(&FreeSansBold12pt7b);
tft->setTextColor(COLOR_TEXT, COLOR_HEADER);
String title = "Station " + String(config.deviceId);
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(title, 0, 0, &x1, &y1, &w, &h);
tft->setCursor((SCREEN_WIDTH - w) / 2, 30);
tft->print(title);
}
// تابع نمایش دمای بزرگ در مرکز
inline void drawLargeTemperature() {
if (!tft) return;
tft->setFont(&FreeSansBold18pt7b);
tft->setTextColor(COLOR_TEMP, COLOR_BG_DARK);
String tempText = String(currentData.temperature, 1) + "°C";
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(tempText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor((SCREEN_WIDTH - w) / 2, 90);
tft->print(tempText);
// زیرنویس
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
tft->setCursor((SCREEN_WIDTH - 60) / 2, 115);
tft->print("Temperature");
}
// تابع نمایش اطلاعات در دو ستون
inline void drawTwoColumnData() {
if (!tft) return;
int startY = 130;
int rowHeight = 25;
// ستون سمت چپ
drawValueWithLabel("Humidity:", String(currentData.humidity, 0) + "%", 20, startY, COLOR_HUM);
drawValueWithLabel("Light:", String((int)currentData.lightLux) + " lux", 20, startY + rowHeight, COLOR_LIGHT);
drawValueWithLabel("Battery:", String(currentData.batteryPercent) + "%", 20, startY + rowHeight * 2, COLOR_BATTERY);
// ستون سمت راست
drawValueWithLabel("CO:", String((int)currentData.coPPM) + " ppm", 140, startY, COLOR_GAS);
drawValueWithLabel("Voltage:", String(currentData.batteryVoltage, 1) + "V", 140, startY + rowHeight, COLOR_POWER);
// وضعیت برق
tft->setFont(&FreeSansBold9pt7b);
if (currentData.power == 1) {
tft->setTextColor(COLOR_POWER, COLOR_BG_DARK);
tft->setCursor(140, startY + rowHeight * 2);
tft->print("Power: ON");
}
}
// تابع نمایش وضعیت سنسورها
inline void drawSensorStatus() {
if (!tft) return;
int y = 210;
tft->setFont(&FreeSansBold9pt7b);
// نمایش وضعیت اتصال سنسورها
String status = "";
if (!sht31Connected) status += "TEMP ";
if (!lightSensorConnected) status += "LIGHT ";
if (!coSensorConnected) status += "GAS ";
if (status.length() > 0) {
tft->setTextColor(COLOR_TEMP, COLOR_BG_DARK);
tft->setCursor(10, y);
tft->print("Err: " + status);
} else {
tft->setTextColor(COLOR_BATTERY, COLOR_BG_DARK);
tft->setCursor(10, y);
tft->print("All Sensors OK");
}
// نمایش قدرت سیگنال
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
String signalText = "Sig: " + String(signalStrength);
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(signalText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(SCREEN_WIDTH - w - 10, y);
tft->print(signalText);
}
// تابع نمایش فاصله آپلود
inline void drawUploadInterval() {
if (!tft) return;
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
String intervalText = "Upload: " + String(config.uploadInterval) + "min";
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(intervalText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor((SCREEN_WIDTH - w) / 2, 230);
tft->print(intervalText);
}
inline void displaySimpleMode() {
if (!tft) {
Serial1.print("Not TFT!!");
return;
}
// پاک کردن صفحه
tft->fillScreen(COLOR_BG_DARK);
// ========== نمایش دما بزرگ در مرکز ==========
String tempText = String(currentData.temperature, 1) + "°C";
tft->setFont(&FreeSansBold18pt7b);
tft->setTextColor(COLOR_TEMP, COLOR_BG_DARK);
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(tempText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, LCD_CENTER_Y - 30);
tft->print(tempText);
// ========== نمایش رطوبت زیر دما ==========
tft->setFont(&FreeSansBold12pt7b);
String humText = "H:" + String(currentData.humidity, 0) + "%";
tft->getTextBounds(humText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_HUM, COLOR_BG_DARK);
tft->setCursor(LCD_CENTER_X - w/2, LCD_CENTER_Y + 20);
tft->print(humText);
// ========== نمایش باتری و نور در یک خط ==========
tft->setFont(&FreeSansBold9pt7b);
// باتری
String batText = "B:" + String(currentData.batteryPercent) + "%";
if (currentData.power == 1) {
batText = "PWR:ON";
}
tft->getTextBounds(batText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(currentData.power == 1 ? COLOR_POWER : COLOR_BATTERY, COLOR_BG_DARK);
tft->setCursor(30, 180); // سمت چپ
tft->print(batText);
// نور
String lightText = "L:" + String((int)currentData.lightLux);
if (currentData.lightLux > 9999) {
lightText = "L:" + String(currentData.lightLux / 1000, 0) + "K";
}
tft->getTextBounds(lightText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_LIGHT, COLOR_BG_DARK);
tft->setCursor(240 - w - 30, 180); // سمت راست
tft->print(lightText);
// ========== نمایش CO در پایین مرکز ==========
bool gasWarning = currentData.coPPM > 50;
String gasText = "CO:" + String((int)currentData.coPPM) + "ppm";
tft->getTextBounds(gasText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(gasWarning ? COLOR_TEMP : COLOR_GAS, COLOR_BG_DARK);
tft->setCursor(LCD_CENTER_X - w/2, 210);
tft->print(gasText);
// ========== نمایش ولتاژ و شناسه در بالا ==========
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
// ولتاژ در سمت چپ بالا
String voltageText = String(currentData.batteryVoltage, 1) + "V";
tft->setCursor(10, 20);
tft->print(voltageText);
// شناسه دستگاه در سمت راست بالا
String idText = "#" + String(config.deviceId);
tft->getTextBounds(idText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(240 - w - 10, 20);
tft->print(idText);
// نمایش وضعیت سنسورها در پایین
int errorCount = 0;
if (!sht31Connected) errorCount++;
if (!lightSensorConnected) errorCount++;
if (!coSensorConnected) errorCount++;
if (errorCount > 0) {
tft->setTextColor(COLOR_TEMP, COLOR_BG_DARK);
String statusText = String(errorCount) + " ERR";
tft->getTextBounds(statusText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, 230);
tft->print(statusText);
}
}
// ==================== طراحی اصلی ====================
inline void GdisplayAllParameters() {
if (!tft)
{
Serial1.print("Not TFT!!");
return;
}
// پاک کردن صفحه
tft->fillScreen(COLOR_BG_DARK);
// ========== نمایش دما بزرگ در مرکز ==========
String tempText = String(currentData.temperature, 1) + "°C";
tft->setFont(&FreeSansBold18pt7b);
tft->setTextColor(COLOR_TEMP, COLOR_BG_DARK);
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(tempText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, /*100*/ 115);
tft->print(tempText);
// زیرنویس دما
/*tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
String tempLabel = "Temperature";
tft->getTextBounds(tempLabel, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, 140);
tft->print(tempLabel);*/
// ========== نمایش پارامترها در چهار گوشه (داخل دایره) ==========
tft->setFont(&FreeSansBold12pt7b);
// گوشه بالا چپ: نور (L) - فاصله از لبه
String lightText = "L:" + String((int)currentData.lightLux);
if (currentData.lightLux > 9999) {
lightText = "L:" + String(currentData.lightLux / 1000, 0) + "K";
}
tft->getTextBounds(lightText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_LIGHT, COLOR_BG_DARK);
tft->setCursor(40, /*60*/ 140); // به سمت مرکز منتقل شد
tft->print(lightText);
// گوشه بالا راست: رطوبت (H)
String humText = "H:" + String(currentData.humidity, 0) + "%";
tft->getTextBounds(humText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_HUM, COLOR_BG_DARK);
tft->setCursor(240 - w - 40, 140/*60*/); // به سمت مرکز منتقل شد
tft->print(humText);
// گوشه پایین چپ: باتری (B)
String batText;
if (currentData.power == 1) {
batText = "P:ON";
tft->setTextColor(COLOR_POWER, COLOR_BG_DARK);
} else {
batText = "B:" + String(currentData.batteryPercent) + "%";
tft->setTextColor(COLOR_BATTERY, COLOR_BG_DARK);
}
tft->getTextBounds(batText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(40, 180); // به سمت مرکز منتقل شد
tft->print(batText);
// گوشه پایین راست: گاز CO (G)
bool gasWarning = currentData.coPPM > 50;
String gasText = "G:" + String((int)currentData.coPPM);
tft->getTextBounds(gasText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(gasWarning ? COLOR_TEMP : COLOR_GAS, COLOR_BG_DARK);
tft->setCursor(240 - w - 40, 180); // به سمت مرکز منتقل شد
tft->print(gasText);
// ========== نمایش ولتاژ باتری در پایین مرکز ==========
tft->setFont(&FreeSansBold9pt7b);
String voltageText = String(currentData.batteryVoltage, 1) + "V";
tft->getTextBounds(voltageText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
tft->setCursor(LCD_CENTER_X - w/2, 210); // به بالا منتقل شد
tft->print(voltageText);
// ========== نمایش شناسه دستگاه کوچک در گوشه بالا مرکز ==========
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
String idText = "#" + String(config.deviceId);
tft->getTextBounds(idText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, 20); // به پایین منتقل شد
tft->print(idText);
}
inline void displayAllParameters() {
if (!tft)
{
Serial1.print("Not TFT!!");
return;
}
// پاک کردن صفحه
tft->fillScreen(COLOR_BG_DARK);
// ========== نمایش دما بزرگ در مرکز ==========
String tempText = String(currentData.temperature, 1) + "°C";
tft->setFont(&FreeSansBold18pt7b);
tft->setTextColor(COLOR_TEMP, COLOR_BG_DARK);
int16_t x1, y1;
uint16_t w, h;
tft->getTextBounds(tempText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, 100);
tft->print(tempText);
// زیرنویس دما
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
String tempLabel = "Temperature";
tft->getTextBounds(tempLabel, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, 140);
tft->print(tempLabel);
// ========== نمایش پارامترها در چهار گوشه (داخل دایره) ==========
tft->setFont(&FreeSansBold12pt7b);
// گوشه بالا چپ: نور (L) - فاصله از لبه
String lightText = "L:" + String((int)currentData.lightLux);
if (currentData.lightLux > 9999) {
lightText = "L:" + String(currentData.lightLux / 1000, 0) + "K";
}
tft->getTextBounds(lightText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_LIGHT, COLOR_BG_DARK);
tft->setCursor(40, 60); // به سمت مرکز منتقل شد
tft->print(lightText);
// گوشه بالا راست: رطوبت (H)
String humText = "H:" + String(currentData.humidity, 0) + "%";
tft->getTextBounds(humText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_HUM, COLOR_BG_DARK);
tft->setCursor(240 - w - 40, 60); // به سمت مرکز منتقل شد
tft->print(humText);
// گوشه پایین چپ: باتری (B)
String batText;
if (currentData.power == 1) {
batText = "P:ON";
tft->setTextColor(COLOR_POWER, COLOR_BG_DARK);
} else {
batText = "B:" + String(currentData.batteryPercent) + "%";
tft->setTextColor(COLOR_BATTERY, COLOR_BG_DARK);
}
tft->getTextBounds(batText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(40, 180); // به سمت مرکز منتقل شد
tft->print(batText);
// گوشه پایین راست: گاز CO (G)
bool gasWarning = currentData.coPPM > 50;
String gasText = "G:" + String((int)currentData.coPPM);
tft->getTextBounds(gasText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(gasWarning ? COLOR_TEMP : COLOR_GAS, COLOR_BG_DARK);
tft->setCursor(240 - w - 40, 180); // به سمت مرکز منتقل شد
tft->print(gasText);
// ========== نمایش ولتاژ باتری در پایین مرکز ==========
tft->setFont(&FreeSansBold9pt7b);
String voltageText = String(currentData.batteryVoltage, 1) + "V";
tft->getTextBounds(voltageText, 0, 0, &x1, &y1, &w, &h);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
tft->setCursor(LCD_CENTER_X - w/2, 210); // به بالا منتقل شد
tft->print(voltageText);
// ========== نمایش شناسه دستگاه کوچک در گوشه بالا مرکز ==========
tft->setFont(&FreeSansBold9pt7b);
tft->setTextColor(COLOR_TEXT_DIM, COLOR_BG_DARK);
String idText = "#" + String(config.deviceId);
tft->getTextBounds(idText, 0, 0, &x1, &y1, &w, &h);
tft->setCursor(LCD_CENTER_X - w/2, 20); // به پایین منتقل شد
tft->print(idText);
}
// ==================== تابع آپدیت نمایشگر ====================
inline void updateDisplay() {
if (!tft) {
Serial1.print("Display not initialized!");
return;
}
// تغییر خودکار صفحه هر 15 ثانیه
static int displayMode = 0;
if (millis() - lastDisplayChange > 3000) {
displayMode = (displayMode + 1) % 7;
lastDisplayChange = millis();
}
// نمایش بر اساس حالت
if (displayMode < 6) {
displayAllParameters();
//GdisplayAllParameters();
}
else
{
displayAllParameters();
//GdisplayAllParameters();
//displaySimpleMode();
}
}
// ==================== تابع راه‌اندازی نمایشگر ====================
inline void initDisplay() {
// ایجاد شیء نمایشگر
Arduino_DataBus *bus = new Arduino_HWSPI(TFT_DC, TFT_CS, &SPI);
tft = new Arduino_GC9A01(bus, -1, 0, true); // بدون پین RST
// راه‌اندازی نمایشگر
if (tft) {
tft->begin();
tft->setRotation(1); // جهت نمایش
tft->fillScreen(COLOR_BG_DARK);
tft->setTextWrap(false);
// تنظیم فونت پیش‌فرض
tft->setFont(&FreeSansBold9pt7b);
Serial1.println("Display initialized successfully");
} else {
Serial1.println("Failed to initialize display");
}
}
#endif // DISPLAY_H

File diff suppressed because it is too large Load Diff

1509
14041130/TestLCD/EC200U.h Normal file

File diff suppressed because it is too large Load Diff

214
14041130/TestLCD/Icons.h Normal file
View File

@@ -0,0 +1,214 @@
#ifndef ICONS_H
#define ICONS_H
#include <Arduino.h>
#include <TFT_eSPI.h>
// ==================== کدهای آیکون‌های Unicode ====================
#define ICON_SUN 0x2600 // ☀️
#define ICON_THERMO 0x1F321 // 🌡️
#define ICON_DROP 0x1F4A7 // 💧
#define ICON_WARNING 0x26A0 // ⚠️
#define ICON_BATTERY 0x1F50B // 🔋
#define ICON_SIGNAL 0x1F4F6 // 📶
#define ICON_POWER 0x1F50C // 🔌
#define ICON_WIFI 0x1F4F6 // 📶
#define ICON_CLOCK 0x1F552 // 🕒
#define ICON_HOME 0x1F3E0 // 🏠
#define ICON_COG 0x2699 // ⚙️
#define ICON_DEGREE 0x00B0 // °
// ==================== توابع رسم آیکون‌های گرافیکی ====================
// تابع رسم آیکون خورشید (دایره‌ای شکل)
inline void drawSunIcon(TFT_eSPI& tft, int x, int y, int radius, uint16_t color) {
// دایره مرکزی
tft.fillCircle(x, y, radius, color);
tft.drawCircle(x, y, radius, TFT_WHITE);
// پرتوهای خورشید
for (int i = 0; i < 8; i++) {
float angle = i * 45 * 3.14159 / 180;
int x1 = x + (radius * cos(angle));
int y1 = y + (radius * sin(angle));
int x2 = x + (radius * 1.5 * cos(angle));
int y2 = y + (radius * 1.5 * sin(angle));
tft.drawLine(x1, y1, x2, y2, color);
}
}
// تابع رسم آیکون دماسنج (برای LCD دایره‌ای)
inline void drawThermometerIcon(TFT_eSPI& tft, int x, int y, int height, uint16_t color, float tempPercent) {
// بدنه دماسنج
tft.drawRoundRect(x - 3, y, 6, height, 3, color);
// حباب پایین
tft.fillCircle(x, y + height + 5, 8, color);
tft.drawCircle(x, y + height + 5, 8, TFT_WHITE);
// سطح جیوه
int mercuryHeight = (tempPercent * height) / 100;
tft.fillRect(x - 2, y + (height - mercuryHeight), 4, mercuryHeight, TFT_RED);
}
// تابع رسم آیکون قطره (رطوبت)
inline void drawDropIcon(TFT_eSPI& tft, int x, int y, int size, uint16_t color, float fillPercent) {
// بدنه قطره
int dropHeight = size;
int dropWidth = size / 2;
// قسمت دایره‌ای بالایی
tft.fillCircle(x, y + dropWidth/2, dropWidth/2, color);
tft.drawCircle(x, y + dropWidth/2, dropWidth/2, TFT_WHITE);
// قسمت مثلثی پایینی
tft.fillTriangle(x - dropWidth/2, y + dropWidth/2,
x + dropWidth/2, y + dropWidth/2,
x, y + dropHeight, color);
tft.drawTriangle(x - dropWidth/2, y + dropWidth/2,
x + dropWidth/2, y + dropWidth/2,
x, y + dropHeight, TFT_WHITE);
// سطح آب داخل قطره
if (fillPercent > 0) {
int waterHeight = (fillPercent * dropHeight) / 100;
int waterY = y + dropHeight - waterHeight;
// قسمت دایره‌ای آب
if (waterY < y + dropWidth/2) {
int waterRadius = dropWidth/2;
int waterLevel = (y + dropWidth/2) - waterY;
tft.fillCircle(x, y + dropWidth/2, waterRadius - waterLevel, TFT_BLUE);
}
// قسمت مثلثی آب
tft.fillTriangle(x - dropWidth/2, y + dropHeight - waterHeight,
x + dropWidth/2, y + dropHeight - waterHeight,
x, y + dropHeight, TFT_BLUE);
}
}
// تابع رسم آیکون گاز (هشدار)
inline void drawGasIcon(TFT_eSPI& tft, int x, int y, int size, uint16_t color, bool warning) {
// مثلث هشدار
tft.fillTriangle(x, y,
x - size/2, y + size,
x + size/2, y + size,
warning ? TFT_RED : color);
tft.drawTriangle(x, y,
x - size/2, y + size,
x + size/2, y + size,
TFT_WHITE);
// علامت تعجب
tft.fillRect(x - 1, y + size/3, 2, size/3, TFT_WHITE);
tft.fillCircle(x, y + size*2/3, 2, TFT_WHITE);
// امواج گاز (دایره‌های متحدالمرکز)
if (warning) {
for (int i = 1; i <= 3; i++) {
tft.drawCircle(x, y + size + 5, i * 4, TFT_YELLOW);
}
}
}
// تابع رسم آیکون باتری (برای LCD دایره‌ای)
inline void drawBatteryIcon(TFT_eSPI& tft, int x, int y, int width, int height, int percent, bool charging) {
// بدنه باتری
tft.drawRoundRect(x, y, width, height, 2, TFT_WHITE);
// قطب مثبت
tft.fillRoundRect(x + width, y + height/3, 2, height/3, 1, TFT_WHITE);
// سطح شارژ
int fillWidth = (percent * (width - 4)) / 100;
// رنگ باتری بر اساس درصد
uint16_t fillColor;
if (percent > 70) fillColor = TFT_GREEN;
else if (percent > 30) fillColor = TFT_YELLOW;
else fillColor = TFT_RED;
tft.fillRoundRect(x + 2, y + 2, fillWidth, height - 4, 1, fillColor);
// آیکون شارژ (صاعقه)
if (charging && percent < 95) {
tft.fillTriangle(x + width/2, y + 3,
x + width/2 - 3, y + height - 3,
x + width/2 + 3, y + height - 3, TFT_CYAN);
}
// نمایش درصد در وسط باتری (اگر جای کافی باشد)
if (width > 30) {
tft.setTextColor(TFT_WHITE, fillColor);
tft.setTextSize(1);
tft.setTextDatum(MC_DATUM);
tft.drawNumber(percent, x + width/2, y + height/2 + 1);
tft.setTextDatum(TL_DATUM); // بازگشت به حالت پیش‌فرض
}
}
// تابع رسم نوار سیگنال (دایره‌ای)
inline void drawSignalBars(TFT_eSPI& tft, int x, int y, int radius, int strength) {
// 4 میله سیگنال به شکل دایره‌ای
int bars = (strength * 4) / 31;
if (bars > 4) bars = 4;
for (int i = 0; i < 4; i++) {
int barRadius = radius - (i * 5);
if (barRadius > 0) {
if (i < bars) {
uint16_t barColor;
if (strength > 20) barColor = TFT_GREEN;
else if (strength > 10) barColor = TFT_YELLOW;
else barColor = TFT_RED;
// رسم قوس برای هر میله
int startAngle = 180 + (i * 15);
int endAngle = 180 - (i * 15);
tft.drawArc(x, y, barRadius, barRadius - 2, startAngle, endAngle, barColor, barColor);
} else {
//tft.drawArc(x, y, barRadius, barRadius - 2, 150, 210, TFT_DARKGREY, TFT_DARKGREY);
}
}
}
// نقطه مرکزی
tft.fillCircle(x, y, 2, TFT_WHITE);
}
// تابع رسم آیکون برق شهری
inline void drawPowerIcon(TFT_eSPI& tft, int x, int y, int size, uint16_t color) {
// دایره بیرونی
tft.drawCircle(x, y, size/2, color);
// علامت پاور (دایره با خط)
tft.drawLine(x, y - size/4, x, y + size/4, color);
tft.fillTriangle(x, y - size/4,
x - size/6, y,
x + size/6, y, color);
}
// تابع رسم آیکون وای‌فای (دایره‌ای)
inline void drawWifiIcon(TFT_eSPI& tft, int x, int y, int radius, uint16_t color, bool connected) {
if (!connected) {
tft.drawLine(x - radius/2, y - radius/2, x + radius/2, y + radius/2, TFT_RED);
tft.drawLine(x + radius/2, y - radius/2, x - radius/2, y + radius/2, TFT_RED);
return;
}
// حلقه‌های وای‌فای
for (int i = 1; i <= 3; i++) {
int r = radius - (i * 4);
if (r > 0) {
tft.drawCircle(x, y, r, color);
}
}
// نقطه مرکزی
tft.fillCircle(x, y, 2, color);
}
#endif // ICONS_H

433
14041130/TestLCD/Memory.h Normal file
View File

@@ -0,0 +1,433 @@
#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");
}
else
{
return;
}
// 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

View 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

View File

@@ -0,0 +1,472 @@
#ifndef SENSORS_H
#define SENSORS_H
#include <Arduino.h>
#include <Wire.h>
#include "Config.h"
#define VCC 4.63
#define RL 10000.0
#define VDIV_RATIO 0.681
#define I2C_TIMEOUT_MS 100
// تعریف پین‌ها
#define BH1750_SDA_PIN PB7
#define BH1750_SCL_PIN PB6
#define SHT31_SDA_PIN PB9
#define SHT31_SCL_PIN PB8
#define SHT31_ADDR 0x44
#define BH1750_ADDR 0x23
// متغیرهای خارجی
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;
extern TwoWire Wire1;
extern const long calibrationPeriod;
// توابع
void initSensors();
void readSensors();
void checkSensorStatus();
// -------------------- پاکسازی باس I2C --------------------
inline void i2cBusClear(uint8_t scl_pin, uint8_t sda_pin, bool isWire1 = false) {
Serial1.print("Clearing I2C bus on pins SCL:");
Serial1.print(scl_pin);
Serial1.print(" SDA:");
Serial1.println(sda_pin);
// آزاد کردن باس
if (isWire1) {
Wire1.end();
} else {
Wire.end();
}
delay(50);
// تنظیم پین‌ها به صورت دستی
pinMode(scl_pin, OUTPUT_OPEN_DRAIN);
pinMode(sda_pin, OUTPUT_OPEN_DRAIN);
digitalWrite(sda_pin, HIGH);
digitalWrite(scl_pin, HIGH);
delayMicroseconds(10);
// تولید پالس کلاک برای رهاسازی باس
for (int i = 0; i < 18; i++) {
digitalWrite(scl_pin, LOW);
delayMicroseconds(5);
digitalWrite(scl_pin, HIGH);
delayMicroseconds(5);
}
// ایجاد شرط STOP
digitalWrite(sda_pin, LOW);
delayMicroseconds(5);
digitalWrite(scl_pin, HIGH);
delayMicroseconds(5);
digitalWrite(sda_pin, HIGH);
delayMicroseconds(5);
delay(100);
// راه‌اندازی مجدد
if (isWire1) {
Wire1.setSDA(sda_pin);
Wire1.setSCL(scl_pin);
Wire1.begin();
Wire1.setClock(50000); // سرعت پایین برای پایداری
} else {
Wire.setSDA(sda_pin);
Wire.setSCL(scl_pin);
Wire.begin();
Wire.setClock(50000); // سرعت پایین برای پایداری
}
delay(100);
}
// -------------------- کالیبراسیون MQ7 --------------------
inline void calibrateMQ7() {
Serial1.println("Calibrating MQ7...");
float sumRS = 0;
for(int i = 0; i < 50; i++) { // کاهش از 100 به 50
int adc = analogRead(MQ7_PIN);
float v_adc = adc * (5.0 / 4095.0);
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(20); // کاهش تاخیر
}
MQ7_R0 = (sumRS / 50.0) / 9.8;
calibrationComplete = true;
Serial1.print("MQ7 R0 calibrated: ");
Serial1.println(MQ7_R0);
}
// -------------------- خواندن CO --------------------
inline float readCOImproved() {
float sumVoltage = 0;
for (int i = 0; i < 10; i++) { // کاهش از 20 به 10
int adc = analogRead(MQ7_PIN);
float v_adc = adc * (5.0 / 4095.0);
sumVoltage += v_adc;
delay(1);
}
float v_avg = sumVoltage / 10.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) {
ppm = 100.0 * pow(ratio, -1.53);
ppm = constrain(ppm, 0, 5000);
}
if (!calibrationComplete) {
ppm = -ppm;
}
return ppm;
}
// -------------------- خواندن SHT31 (ساده‌شده) --------------------
inline bool readSHT31(float &temperature, float &humidity) {
static uint8_t errorCount = 0;
static bool needsReset = false;
// اگر نیاز به بازنشانی داریم
if (needsReset) {
Serial1.println("Resetting SHT31 I2C bus...");
i2cBusClear(SHT31_SCL_PIN, SHT31_SDA_PIN, false);
needsReset = false;
delay(100);
}
// تست اتصال اولیه
Wire.beginTransmission(SHT31_ADDR);
uint8_t connError = Wire.endTransmission();
if (connError != 0) {
Serial1.print("SHT31 connection error: ");
Serial1.println(connError);
errorCount++;
if (errorCount >= 2) {
needsReset = true;
errorCount = 0;
}
return false;
}
// ارسال دستور اندازه‌گیری (کوتاه)
Wire.beginTransmission(SHT31_ADDR);
if (Wire.write(0x2C) != 1) { // دستور سریع
Serial1.println("SHT31 write failed");
return false;
}
if (Wire.write(0x06) != 1) { // تکرار بالا
Serial1.println("SHT31 write failed");
return false;
}
uint8_t writeError = Wire.endTransmission();
if (writeError != 0) {
Serial1.print("SHT31 write error: ");
Serial1.println(writeError);
errorCount++;
if (errorCount >= 2) {
needsReset = true;
errorCount = 0;
}
return false;
}
// تاخیر اندازه‌گیری
delay(20); // برای حالت High Repeatability
// خواندن داده
uint8_t bytesRead = Wire.requestFrom(SHT31_ADDR, (uint8_t)6, (uint8_t)true);
if (bytesRead != 6) {
Serial1.print("SHT31 read error, bytes: ");
Serial1.println(bytesRead);
// خالی کردن بافر
while (Wire.available()) {
Wire.read();
}
errorCount++;
if (errorCount >= 2) {
needsReset = true;
errorCount = 0;
}
return false;
}
// خواندن بایت‌ها
uint8_t data[6];
for (int i = 0; i < 6; i++) {
data[i] = Wire.read();
}
// بررسی CRC (ساده‌شده)
uint16_t tRaw = (data[0] << 8) | data[1];
uint16_t hRaw = (data[3] << 8) | data[4];
// تبدیل مقادیر
temperature = -45.0 + 175.0 * tRaw / 65535.0;
humidity = 100.0 * hRaw / 65535.0;
// اعتبارسنجی
if (isnan(temperature) || isnan(humidity) ||
temperature < -40 || temperature > 125 ||
humidity < 0 || humidity > 100) {
Serial1.println("SHT31 invalid data");
return false;
}
errorCount = 0; // ریست شمارنده خطا
return true;
}
// -------------------- خواندن BH1750 --------------------
inline bool readBH1750(float &lux) {
static bool initialized = false;
static uint8_t errorCount = 0;
// مقداردهی اولیه
if (!initialized) {
Serial1.println("Initializing BH1750...");
Wire1.setSDA(BH1750_SDA_PIN);
Wire1.setSCL(BH1750_SCL_PIN);
Wire1.begin();
Wire1.setClock(50000); // سرعت پایین
delay(100);
// پاور آن
Wire1.beginTransmission(BH1750_ADDR);
Wire1.write(0x01);
if (Wire1.endTransmission() != 0) {
Serial1.println("BH1750 power on failed");
return false;
}
delay(10);
// تنظیم حالت
Wire1.beginTransmission(BH1750_ADDR);
Wire1.write(0x13); // Continuous H-Resolution Mode 0.5lx
if (Wire1.endTransmission() != 0) {
Serial1.println("BH1750 mode set failed");
return false;
}
initialized = true;
delay(180); // زمان اولیه برای اندازه‌گیری
}
// خواندن داده
uint8_t bytesRead = Wire1.requestFrom(BH1750_ADDR, (uint8_t)2);
if (bytesRead != 2) {
Serial1.print("BH1750 read error, bytes: ");
Serial1.println(bytesRead);
errorCount++;
if (errorCount >= 3) {
initialized = false;
errorCount = 0;
}
return false;
}
// خواندن بایت‌ها
uint8_t highByte = Wire1.read();
uint8_t lowByte = Wire1.read();
uint16_t raw = (highByte << 8) | lowByte;
lux = raw / 1.2f;
errorCount = 0;
return true;
}
// -------------------- بررسی وضعیت سنسورها --------------------
inline void checkSensorStatus() {
// فقط چاپ وضعیت فعلی
Serial1.println("--- Sensor Status ---");
Serial1.print("SHT31: ");
Serial1.println(sht31Connected ? "OK" : "ERROR");
Serial1.print("BH1750: ");
Serial1.println(lightSensorConnected ? "OK" : "ERROR");
Serial1.print("MQ7: ");
Serial1.println(coSensorConnected ? "OK" : "ERROR");
}
// -------------------- خواندن همه سنسورها --------------------
inline void readSensors() {
Serial1.println("\n=== Reading Sensors ===");
// اول BH1750 را بخوان (چون کار می‌کند)
float lux;
if (readBH1750(lux)) {
currentData.lightLux = lux;
lightSensorConnected = true;
Serial1.print("BH1750: ");
Serial1.print(lux, 0);
Serial1.println(" lux");
} else {
currentData.lightLux = 0;
lightSensorConnected = false;
Serial1.println("BH1750: FAILED");
}
// سپس SHT31
float temperature = NAN, humidity = NAN;
bool sht31Read = readSHT31(temperature, humidity);
if (sht31Read && !isnan(temperature) && !isnan(humidity)) {
currentData.temperature = temperature;
currentData.humidity = humidity;
sht31Connected = true;
Serial1.print("SHT31: T=");
Serial1.print(temperature, 1);
Serial1.print("C, H=");
Serial1.print(humidity, 1);
Serial1.println("%");
} else {
currentData.temperature = NAN;
currentData.humidity = NAN;
sht31Connected = false;
Serial1.println("SHT31: FAILED");
}
// کالیبراسیون MQ7
if (!calibrationComplete && (millis() - systemStartTime >= calibrationPeriod)) {
calibrateMQ7();
calibrationComplete = true;
}
// خواندن CO
currentData.coPPM = readCOImproved();
int adc = analogRead(MQ7_PIN);
coSensorConnected = (adc > 10 && adc < 4085);
Serial1.print("MQ7: ");
Serial1.print(currentData.coPPM, 1);
Serial1.println(" ppm");
// محاسبه درصدها
calculatePercentages(currentData);
lastSensorRead = millis();
Serial1.println("=== Sensor Readings Complete ===\n");
// اگر SHT31 کار نمی‌کند، سیستم را مسدود نکن
if (!sht31Connected) {
Serial1.println("Warning: SHT31 not responding, continuing anyway...");
}
}
// -------------------- مقداردهی اولیه --------------------
inline void initSensors() {
Serial1.println("Initializing sensors...");
// راه‌اندازی I2C1 برای SHT31 (سرعت پایین)
Wire.setSDA(SHT31_SDA_PIN);
Wire.setSCL(SHT31_SCL_PIN);
Wire.begin();
Wire.setClock(50000); // 50kHz برای پایداری
Wire.setTimeout(100);
// راه‌اندازی I2C2 برای BH1750
Wire1.setSDA(BH1750_SDA_PIN);
Wire1.setSCL(BH1750_SCL_PIN);
Wire1.begin();
Wire1.setClock(50000); // 50kHz
Wire1.setTimeout(100);
// پیکربندی MQ7
pinMode(MQ7_PIN, INPUT);
analogReadResolution(12);
delay(1000);
// اسکن آدرس‌های I2C
Serial1.println("Scanning I2C1 (SHT31)...");
byte error;
int found1 = 0;
for(byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
error = Wire.endTransmission();
if (error == 0) {
Serial1.print("Found device at 0x");
if (addr < 16) Serial1.print("0");
Serial1.println(addr, HEX);
found1++;
}
}
Serial1.print("I2C1 devices: ");
Serial1.println(found1);
Serial1.println("Scanning I2C2 (BH1750)...");
int found2 = 0;
for(byte addr = 1; addr < 127; addr++) {
Wire1.beginTransmission(addr);
error = Wire1.endTransmission();
if (error == 0) {
Serial1.print("Found device at 0x");
if (addr < 16) Serial1.print("0");
Serial1.println(addr, HEX);
found2++;
}
}
Serial1.print("I2C2 devices: ");
Serial1.println(found2);
// تست اولیه سریع
Serial1.println("Quick sensor test...");
float lux;
if (readBH1750(lux)) {
lightSensorConnected = true;
Serial1.print("BH1750 OK: ");
Serial1.print(lux);
Serial1.println(" lux");
}
delay(100);
float temp, hum;
if (readSHT31(temp, hum)) {
sht31Connected = true;
Serial1.print("SHT31 OK: ");
Serial1.print(temp);
Serial1.print("C ");
Serial1.print(hum);
Serial1.println("%");
}
Serial1.println("Sensor init done");
}
#endif // SENSORS_H

924
14041130/TestLCD/Sensors.h Normal file
View File

@@ -0,0 +1,924 @@
#ifndef SENSORS_H
#define SENSORS_H
#include <Arduino.h>
#include <Wire.h>
#include "Config.h"
// ================== تعاریف و ماکروها از کد دوم ==================
// تعریف آدرس سنسورها
#define BH1750_ADDR 0x23
#define SHT31_ADDR 0x44
// حالتهای اندازه‌گیری BH1750
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20
#define BH1750_POWER_DOWN 0x00
#define BH1750_POWER_ON 0x01
#define BH1750_RESET 0x07
// دستورات SHT31
#define SHT31_MEAS_HIGHREP 0x2400
#define SHT31_MEAS_MEDREP 0x240B
#define SHT31_MEAS_LOWREP 0x2416
#define SHT31_SOFTRESET 0x30A2
#define SHT31_HEATER_ENABLE 0x306D
#define SHT31_HEATER_DISABLE 0x3066
#define SHT31_STATUS_REG 0xF32D
#define SHT31_CLEAR_STATUS 0x3041
#define VREF_ADC 3.3f // ولتاژ مرجع ADC
#define ADC_RESOLUTION 4095.0f // رزولوشن ADC 12 بیت
#define R1 2190.0f // مقاومت سری اول (اهم)
#define R2 3270.0f // مقاومت به زمین (اهم)
#define RL_SENSOR 839.0f // مقاومت بار ماژول (مقاومت بین خروجی آنالوگ و GND)
#define MQ7_PIN PA2 // پین ADC
#define NUM_SAMPLES 10 // تعداد نمونه برای میانگین‌گیری
// پارامترهای سنسور MQ7 برای تبدیل به ppm (از دیتاشیت)
#define CO_A 1.9f // ضریب A
#define CO_B -0.6f // ضریب B
#define PREHEAT_TIME_MS (2 * 60 * 1000) // 2 دقیقه پیش‌گرمایش
// متغیرهای جهانی
float VCC_SENSOR = 4.9f; // ولتاژ تغذیه سنسور MQ7
float MQ7_R0 = 24150.0; //g:14600,14500 //2:24150.0;
unsigned long deviceStartTime = 0;
bool MQ7sensorPreheated=false;
extern bool coSensorConnected;
/* ================== ساختار وضعیت سیستم (از کد اول) ================== */
struct SystemStatus {
bool sht31OK;
bool bh1750OK;
uint32_t lastRecovery;
uint32_t lastSuccess;
uint16_t errorCount;
};
extern SystemStatus i2cStatus;
/* ================== متغیرهای عمومی ================== */
extern SensorData currentData;
extern unsigned long lastSensorRead;
/* ================== متغیرهای اختصاصی از کد دوم ================== */
// متغیرهای ذخیره‌سازی مقادیر آخرین خوانش موفق
extern float lastLightLevel;
extern float lastTemperature;
extern float lastHumidity;
// متغیرهای تشخیص خطا از کد دوم
extern uint8_t bh1750ErrorCount;
extern uint8_t sht31ErrorCount;
extern const uint8_t MAX_ERRORS;
// زمان آخرین اندازه‌گیری موفق از کد دوم
extern unsigned long lastBh1750Success;
extern unsigned long lastSht31Success;
/* ================== توابع عمومی ================== */
void initSensors();
void readSensors();
void updateSensors(); // حلقه اصلی (جایگزین loop)
/* ================== توابع I2C مستقیم (از کد دوم) ================== */
// ──────────────────────────────────────────────────────
// تابع بررسی وجود دستگاه روی باس I2C (از کد دوم)
// ──────────────────────────────────────────────────────
inline bool checkDevice(uint8_t address) {
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
return (error == 0);
}
// ──────────────────────────────────────────────────────
// تابع مقداردهی اولیه BH1750 (از کد دوم)
// ──────────────────────────────────────────────────────
inline bool initBH1750Direct() {
if (!checkDevice(BH1750_ADDR)) {
Serial1.println("BH1750 not found!");
i2cStatus.bh1750OK = false;
return false;
}
i2cStatus.bh1750OK = true;
bh1750ErrorCount = 0;
Serial1.println("BH1750 ready (One-Time mode)");
return true;
}
bool bh1750SoftReset() {
Wire.beginTransmission(BH1750_ADDR);
Wire.write(BH1750_POWER_DOWN);
if (Wire.endTransmission() != 0) return false;
delay(5);
Wire.beginTransmission(BH1750_ADDR);
Wire.write(BH1750_POWER_ON);
if (Wire.endTransmission() != 0) return false;
delay(5);
// دستور Reset فقط بعد از Power On معتبر است
Wire.beginTransmission(BH1750_ADDR);
Wire.write(BH1750_RESET);
if (Wire.endTransmission() != 0) return false;
delay(5);
return true;
}
inline bool readBH1750Direct(float &lightLevel) {
if (!i2cStatus.bh1750OK) {
if (!initBH1750Direct()) return false;
}
// 👇 مهم: ریست داخلی قبل از هر خواندن
if (!bh1750SoftReset()) {
bh1750ErrorCount++;
return false;
}
// ارسال فرمان اندازه‌گیری
Wire.beginTransmission(BH1750_ADDR);
Wire.write(BH1750_ONE_TIME_HIGH_RES_MODE);
byte error = Wire.endTransmission();
if (error != 0) {
bh1750ErrorCount++;
return false;
}
delay(180);
Wire.requestFrom(BH1750_ADDR, 2);
if (Wire.available() == 2) {
uint16_t value = Wire.read();
value <<= 8;
value |= Wire.read();
lightLevel = value / 1.2;
if (lightLevel >= 0 && lightLevel <= 65535) {
lastLightLevel = lightLevel;
bh1750ErrorCount = 0;
i2cStatus.bh1750OK = true;
return true;
}
}
// اگر به اینجا رسید یعنی خواندن شکست خورده
bh1750ErrorCount++;
if (bh1750ErrorCount >= 3) {
// تلاش برای ریست کامل سنسور
bh1750SoftReset();
delay(50);
}
if (bh1750ErrorCount >= MAX_ERRORS) {
i2cStatus.bh1750OK = false;
}
return false;
}
// ──────────────────────────────────────────────────────
// تابع مقداردهی اولیه SHT31 (از کد دوم)
// ──────────────────────────────────────────────────────
inline bool initSHT31Direct() {
if (!checkDevice(SHT31_ADDR)) {
Serial1.println("SHT31 not found!");
i2cStatus.sht31OK = false;
return false;
}
// ارسال دستور ریست نرم
Wire.beginTransmission(SHT31_ADDR);
Wire.write(SHT31_SOFTRESET >> 8);
Wire.write(SHT31_SOFTRESET & 0xFF);
byte error = Wire.endTransmission();
if (error != 0) {
i2cStatus.sht31OK = false;
Serial1.print("SHT31 reset error: ");
Serial1.println(error);
return false;
}
delay(10); // تأخیر بعد از ریست
// غیرفعال کردن هیتر (برای مصرف کمتر و عمر طولانی‌تر)
Wire.beginTransmission(SHT31_ADDR);
Wire.write(SHT31_HEATER_DISABLE >> 8);
Wire.write(SHT31_HEATER_DISABLE & 0xFF);
error = Wire.endTransmission();
if (error == 0) {
i2cStatus.sht31OK = true;
sht31ErrorCount = 0;
Serial1.println("SHT31 initialized successfully");
return true;
} else {
i2cStatus.sht31OK = false;
Serial1.print("SHT31 init error: ");
Serial1.println(error);
return false;
}
}
// ──────────────────────────────────────────────────────
// تابع خواندن دما و رطوبت از SHT31 (از کد دوم)
// ──────────────────────────────────────────────────────
inline bool readSHT31Direct(float &temperature, float &humidity) {
// اگر سنسور غیرفعال است و هنوز خطاها کم است، سعی در راه‌اندازی مجدد
if (!i2cStatus.sht31OK && sht31ErrorCount < MAX_ERRORS) {
initSHT31Direct();
}
if (!i2cStatus.sht31OK) return false;
// ارسال دستور اندازه‌گیری با دقت بالا
Wire.beginTransmission(SHT31_ADDR);
Wire.write(SHT31_MEAS_HIGHREP >> 8);
Wire.write(SHT31_MEAS_HIGHREP & 0xFF);
byte error = Wire.endTransmission();
if (error != 0) {
sht31ErrorCount++;
Serial1.println("Error sending measurement command to SHT31");
return false;
}
// تأخیر برای تبدیل (حداکثر 15ms برای حالت High Rep)
delay(15);
// خواندن 6 بایت داده
Wire.requestFrom(SHT31_ADDR, 6);
if (Wire.available() == 6) {
uint16_t tempRaw = Wire.read();
tempRaw <<= 8;
tempRaw |= Wire.read();
uint8_t tempCRC = Wire.read();
uint16_t humRaw = Wire.read();
humRaw <<= 8;
humRaw |= Wire.read();
uint8_t humCRC = Wire.read();
// تبدیل مقادیر
temperature = -45 + (175 * (float)tempRaw / 65535.0);
humidity = 100 * (float)humRaw / 65535.0;
// اعتبارسنجی مقادیر
if (temperature >= -40 && temperature <= 125 &&
humidity >= 0 && humidity <= 100) {
lastTemperature = temperature;
lastHumidity = humidity;
sht31ErrorCount = 0;
lastSht31Success = millis();
return true;
}
}
// در صورت خطا
sht31ErrorCount++;
Serial1.println("Error reading SHT31");
if (sht31ErrorCount >= MAX_ERRORS) {
i2cStatus.sht31OK = false;
Serial1.println("SHT31 marked as unavailable");
}
return false;
}
/* ================== توابع I2C Recovery (از کد اول) ================== */
// ──────────────────────────────────────────────────────
// پاکسازی باس I2C با Clock Pulse
// ──────────────────────────────────────────────────────
inline void cleanI2CBus() {
pinMode(I2C_SCL_PIN, OUTPUT);
pinMode(I2C_SDA_PIN, OUTPUT);
digitalWrite(I2C_SCL_PIN, HIGH);
digitalWrite(I2C_SDA_PIN, HIGH);
delay(5);
// ارسال 16 clock pulse
for (uint8_t i = 0; i < 16; i++) {
digitalWrite(I2C_SCL_PIN, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL_PIN, HIGH);
delayMicroseconds(5);
}
// STOP condition
digitalWrite(I2C_SDA_PIN, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL_PIN, HIGH);
delayMicroseconds(5);
digitalWrite(I2C_SDA_PIN, HIGH);
delayMicroseconds(5);
pinMode(I2C_SCL_PIN, INPUT);
pinMode(I2C_SDA_PIN, INPUT);
}
// ──────────────────────────────────────────────────────
// بازیابی نرم I2C (از کد دوم)
// ──────────────────────────────────────────────────────
inline void recoverI2CSoft() {
Serial1.println("Attempting I2C recovery...");
// آزاد کردن خطوط
pinMode(I2C_SDA_PIN, INPUT_PULLUP);
pinMode(I2C_SCL_PIN, INPUT_PULLUP);
delay(10);
// تولید پالس‌های ساعت برای آزادسازی هر دستگاه قفل شده
pinMode(I2C_SCL_PIN, OUTPUT);
for (int i = 0; i < 9; i++) {
digitalWrite(I2C_SCL_PIN, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL_PIN, HIGH);
delayMicroseconds(5);
}
// ایجاد شرط STOP
pinMode(I2C_SDA_PIN, OUTPUT);
digitalWrite(I2C_SDA_PIN, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL_PIN, HIGH);
delayMicroseconds(5);
digitalWrite(I2C_SDA_PIN, HIGH);
delayMicroseconds(5);
// بازگشت به حالت عادی
Wire.begin();
Serial1.println("I2C recovery completed");
}
// ──────────────────────────────────────────────────────
// راه‌اندازی مجدد سنسورهای I2C (منطق کد اول با توابع مستقیم)
// ──────────────────────────────────────────────────────
inline void initI2CSensors() {
i2cStatus.sht31OK = false;
i2cStatus.bh1750OK = false;
bh1750SoftReset();
delay(10);
// ─── SHT31 ───
if (checkDevice(SHT31_ADDR)) {
if (initSHT31Direct()) {
Serial1.println("[I2C] SHT31 OK");
}
} else {
Serial1.println("[I2C] SHT31 NOT FOUND");
}
// ─── BH1750 ───
if (checkDevice(BH1750_ADDR)) {
if (initBH1750Direct()) {
Serial1.println("[I2C] BH1750 OK");
}
} else {
Serial1.println("[I2C] BH1750 NOT FOUND");
}
}
// ──────────────────────────────────────────────────────
// بازیابی کامل I2C (منطق کد اول با بازیابی نرم از کد دوم)
// ──────────────────────────────────────────────────────
inline void recoverI2C() {
uint32_t now = millis();
// جلوگیری از recovery مکرر
if (now - i2cStatus.lastRecovery < RECOVERY_COOLDOWN) return;
i2cStatus.lastRecovery = now;
i2cStatus.errorCount++;
if (i2cStatus.errorCount > MAX_ERROR_COUNT)
i2cStatus.errorCount = 100;
Serial1.println("\n[RECOVERY] Starting I2C recovery...");
recoverI2CSoft();
Wire.end();
delay(50);
Wire.setSDA(I2C_SDA_PIN);
Wire.setSCL(I2C_SCL_PIN);
Wire.begin();
Wire.setClock(100000); // 100kHz
Wire.setTimeout(1000); // افزایش timeout برای جلوگیری از hang شدن
delay(50);
bh1750SoftReset();
delay(20);
initI2CSensors();
Serial1.println("[RECOVERY] Complete\n");
}
// ──────────────────────────────────────────────────────
// بررسی سلامت سنسورها (منطق ترکیبی کد اول و دوم)
// ──────────────────────────────────────────────────────
inline void healthCheck() {
bool sht = checkDevice(SHT31_ADDR);
bool bh = checkDevice(BH1750_ADDR);
// اگر هیچکدام پاسخ ندادند → recovery کامل
if (!sht && !bh) {
Serial1.println("[HEALTH] Both sensors failed, starting recovery...");
recoverI2C();
return;
}
// منطق کد دوم: بررسی دوره‌ای سلامت سنسورها
bool shtWasOK = i2cStatus.sht31OK;
bool bhWasOK = i2cStatus.bh1750OK;
// بررسی SHT31
if (checkDevice(SHT31_ADDR)) {
if (!shtWasOK) {
Serial1.println("[HEALTH] SHT31 reappeared, reinitializing...");
initSHT31Direct();
}
} else if (shtWasOK) {
Serial1.println("[HEALTH] SHT31 disappeared!");
i2cStatus.sht31OK = false;
}
// بررسی BH1750
if (checkDevice(BH1750_ADDR)) {
if (!bhWasOK) {
Serial1.println("[HEALTH] BH1750 reappeared, reinitializing...");
initBH1750Direct();
}
} else if (bhWasOK) {
Serial1.println("[HEALTH] BH1750 disappeared!");
i2cStatus.bh1750OK = false;
}
// اگر هر دو دستگاه از دست رفته‌اند، بازیابی باس I2C
if (!i2cStatus.bh1750OK && !i2cStatus.sht31OK) {
recoverI2C();
// تلاش مجدد برای مقداردهی اولیه
delay(100);
initBH1750Direct();
initSHT31Direct();
}
}
/* ================== توابع MQ7 ================== */
// ──────────────────────────────────────────────────────
// کالیبراسیون MQ7
// ──────────────────────────────────────────────────────
/*inline void calibrateMQ7() {
if(currentData.volage < 1) return;
Serial1.println("======================================================================");
Serial1.println("[MQ7] Final calibration with optimized parameters...");
Serial1.print("VCC = ");
Serial1.println(currentData.volage, 2);
// محاسبه پارامترها
float correction_factor = V_AO_SENSOR / V_A2_MICRO; // 1.5
float VDIV_RATIO = V_AO_SENSOR / currentData.volage; // ~0.045
// RL تنظیم شده برای رسیدن به R0 ≈ 3kΩ
float optimized_RL = 1500.0; // از 790 به 1500 افزایش دادیم
float sumRS = 0;
for(int i = 0; i < 50; i++) {
int adc = analogRead(MQ7_PIN);
// تبدیل ADC به ولتاژ (فرض: مرجع ADC = VCC)
float v_a2 = adc * (currentData.volage / 4095.0);
// تصحیح برای بدست آوردن ولتاژ واقعی AO سنسور
float v_ao = v_a2 * correction_factor;
// اطمینان از محدوده منطقی
if (v_ao > currentData.volage * 0.8) {
v_ao = currentData.volage * 0.8;
}
if (v_ao < 0.01) v_ao = 0.01;
// محاسبه RS
float RS = optimized_RL * (currentData.volage / v_ao - 1.0);
sumRS += RS;
delay(20);
}
MQ7_R0 = (sumRS / 50.0) / 9.8;
calibrationComplete = true;
Serial1.print("Optimized R0 = ");
Serial1.print(MQ7_R0, 4);
Serial1.println("Ω");
// ارزیابی نتیجه
if (MQ7_R0 > 2000 && MQ7_R0 < 5000) {
Serial1.println("✓ Excellent! R0 in perfect range (2-5kΩ)");
} else if (MQ7_R0 > 1000 && MQ7_R0 < 10000) {
Serial1.println("✓ Good! R0 in acceptable range (1-10kΩ)");
} else {
Serial1.println("⚠ May need further adjustment");
}
// اطلاعات دیباگ
Serial1.print("Parameters used - RL: ");
Serial1.print(optimized_RL);
Serial1.print("Ω, Correction: ");
Serial1.print(correction_factor, 2);
Serial1.print(", VDIV_RATIO: ");
Serial1.println(VDIV_RATIO, 4);
Serial1.println("======================================================================");
}*/
// inline void calibrateMQ7_() {
// if(currentData.volage<1)
// return;
// Serial1.println("[MQ7] Starting calibration...");
// float sumRS = 0;
// for(int i = 0; i < 50; i++) {
// int adc = analogRead(MQ7_PIN);
// float v_adc = adc * (currentData.volage / 4095.0);//5.0 -> currentData.volage
// float v_ao = v_adc / VDIV_RATIO;
// if (v_ao < 0.01) v_ao = 0.01;
// float RS = RL * (currentData.volage / v_ao - 1.0);//VCC -> currentData.volage
// sumRS += RS;
// delay(20);
// }
// Serial1.print("currentData.volage In MQCalibrRead:");
// Serial1.print(currentData.volage);
// MQ7_R0 = (sumRS / 50.0) / 9.8;
// calibrationComplete = true;
// Serial1.print("======================================================================");
// Serial1.print("[MQ7] Calibration complete. R0 = ");
// Serial1.println(MQ7_R0, 4);
// }
//Serial1.print("======================================================================");
// ──────────────────────────────────────────────────────
// خواندن CO با میانگین‌گیری
// ──────────────────────────────────────────────────────
// ============================ خواندن CO (با مقادیر ثابت) ============================
// inline float readCOImproved__() {
// // اگر سنسور هنوز گرم نشده، مقدار -1 برگردان
// if (!isMQ7sensorPreheated()) {
// return -1.0;
// }
// // میانگین‌گیری برای کاهش نویز
// float sumADC = 0;
// for (int i = 0; i < 5; i++) {
// sumADC += analogRead(MQ7_PIN);
// delay(10);
// }
// int adc_avg = sumADC / 5.0;
// // تبدیل ADC به ولتاژ
// float v_a2 = adc_avg * (currentData.volage / 4095.0);
// // تصحیح با فاکتور ثابت
// float v_ao = v_a2 * CORRECTION_FACTOR_FIXED;
// // محدود کردن مقادیر غیرمنطقی
// if (v_ao < 0.01) v_ao = 0.01;
// if (v_ao > currentData.volage * 0.9) {
// v_ao = currentData.volage * 0.9;
// }
// // محاسبه مقاومت سنسور
// float RS = RL_FIXED * (currentData.volage / v_ao - 1.0);
// // محاسبه نسبت
// float ratio = RS / MQ7_R0;
// // فرمول CO برای MQ7
// float ppm = 10.0 * pow(ratio, -1.53);
// // فیلتر نویز
// if (ppm < 0.1) ppm = 0.0;
// ppm = constrain(ppm, 0, 5000);
// // چاپ دیباگ (هر 30 ثانیه)
// static unsigned long lastPrint = 0;
// if (millis() - lastPrint > 30000) {
// lastPrint = millis();
// Serial1.print("[CO] ");
// Serial1.print(ppm, 2);
// Serial1.print(" ppm (ADC:");
// Serial1.print(adc_avg);
// Serial1.print(" V_ao:");
// Serial1.print(v_ao, 3);
// Serial1.println("V)");
// }
// return ppm;
// }
bool CheckMQ7sensorPreheated() {
if (MQ7sensorPreheated) return true;
unsigned long uptime = millis() - deviceStartTime;
if (uptime >= PREHEAT_TIME_MS) {
MQ7sensorPreheated = true;
Serial1.println("[MQ7] Sensor preheated and ready");
return true;
}
int remaining = (PREHEAT_TIME_MS - uptime) / 60000; // به دقیقه
if (remaining % 5 == 0) { // هر 5 دقیقه گزارش
Serial1.print("[MQ7] Preheating... ");
Serial1.print(remaining);
Serial1.println(" minutes remaining");
}
return false;
}
void calibrateMQ7() {
Serial.println("[MQ7] Starting calibration in clean air...");
deviceStartTime = millis();
}
float readSensorVoltage() {
uint32_t sum = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
sum += analogRead(MQ7_PIN);
delay(10);
}
float adcValue = sum / (float)NUM_SAMPLES;
float vAdc = (adcValue / ADC_RESOLUTION) * VREF_ADC;
// محاسبه ولتاژ خروجی سنسور با جبران تقسیم ولتاژ (R1 و R2)
return vAdc * (R1 + R2) / R2;
}
float calculateRs(float vOut) {
if (vOut <= 0.0f) return 0.0f;
return RL_SENSOR * (VCC_SENSOR / vOut - 1.0f);
}
float calculatePPM(float rs) {
float ratio = rs / MQ7_R0;
if (ratio <= 0.0f) return 0.0f;
return CO_A * pow(ratio, CO_B);
}
float readCOImproved() {
if (!MQ7sensorPreheated)
{
CheckMQ7sensorPreheated();
return -1.0f;
}
float vOut = readSensorVoltage();
float rs = calculateRs(vOut);
float ppm = calculatePPM(rs);
// محدود کردن به بازه معقول
ppm = constrain(ppm, 0.0f, 5000.0f);
return ppm;
}
// inline float readCOImproved() {
// // میانگین‌گیری برای کاهش نویز
// Serial1.print("=======************===============================================================");
// float sumADC = 0;
// for (int i = 0; i < 10; i++) {
// sumADC += analogRead(MQ7_PIN);
// delay(2);
// }
// int adc_avg = sumADC / 10.0;
// // تبدیل ADC به ولتاژ پین A2 (با فرض VCC به عنوان مرجع ADC)
// float v_a2 = adc_avg * (currentData.volage / 4095.0);
// // تصحیح برای بدست آوردن ولتاژ واقعی AO سنسور (با دیود/مقاومت)
// float v_ao = v_a2 * 1.5; // Correction Factor = 1.5
// // محدود کردن مقادیر غیرمنطقی
// if (v_ao < 0.01) v_ao = 0.01;
// if (v_ao > currentData.volage * 0.9) v_ao = currentData.volage * 0.9;
// // محاسبه مقاومت سنسور در گاز
// float RS = 1500.0 * (currentData.volage / v_ao - 1.0); // RL = 1500Ω
// // اگر کالیبراسیون انجام نشده
// if (!calibrationComplete || MQ7_R0 <= 0) {
// return -1.0;
// }
// // محاسبه نسبت
// float ratio = RS / MQ7_R0;
// // دیباگ اطلاعات
// Serial1.print("[MQ7] ADC:");
// Serial1.print(adc_avg);
// Serial1.print(" v_a2:");
// Serial1.print(v_a2, 4);
// Serial1.print("V v_ao:");
// Serial1.print(v_ao, 4);
// Serial1.print("V RS:");
// Serial1.print(RS, 4);
// Serial1.print("Ω ratio:");
// Serial1.print(ratio, 4);
// // محاسبه ppm CO بر اساس دیتاشیت MQ7
// // فرمول: ppm = A * (RS/R0)^B
// // برای MQ7 و CO: A ≈ 10, B ≈ -1.53
// float ppm = 10.0 * pow(ratio, -1.53);
// // محدود کردن بازه خروجی
// ppm = constrain(ppm, 0, 5000);
// Serial1.print(" PPM:");
// Serial1.println(ppm, 4);
// return ppm;
// }
/* ================== خواندن سنسورها (با توابع مستقیم از کد دوم) ================== */
// ──────────────────────────────────────────────────────
// خواندن تمام سنسورها با توابع مستقیم
// ──────────────────────────────────────────────────────
inline void readSensors() {
bool success = false;
Serial1.println("\n=== Reading Sensors ===");
// ──── 1. خواندن SHT31 با تابع مستقیم ────
float temp, hum;
if (readSHT31Direct(temp, hum)) {
currentData.temperature = temp;
currentData.humidity = hum;
Serial1.print("[SHT31] T=");
Serial1.print(temp, 1);
Serial1.print("°C H=");
Serial1.print(hum, 1);
Serial1.println("%");
success = true;
} else {
// استفاده از آخرین مقدار موفق در صورت خطا
if (lastTemperature != 0 || lastHumidity != 0) {
currentData.temperature = lastTemperature;
currentData.humidity = lastHumidity;
Serial1.print("[SHT31] Using last values - T=");
Serial1.print(lastTemperature, 1);
Serial1.print("°C H=");
Serial1.print(lastHumidity, 1);
Serial1.println("%");
} else {
Serial1.println("[SHT31] Read error");
currentData.temperature = NAN;
currentData.humidity = NAN;
}
}
// ──── 2. خواندن BH1750 با تابع مستقیم ────
float lux;
if (readBH1750Direct(lux)) {
currentData.lightLux = lux;
Serial1.print("[BH1750] ");
Serial1.print(lux, 0);
Serial1.println(" lux");
success = true;
} else {
// استفاده از آخرین مقدار موفق در صورت خطا
if (lastLightLevel != 0) {
currentData.lightLux = lastLightLevel;
Serial1.print("[BH1750] Using last value: ");
Serial1.print(lastLightLevel, 0);
Serial1.println(" lux");
} else {
Serial1.println("[BH1750] Read error");
currentData.lightLux = 0;
}
}
// ──── 3. کالیبراسیون خودکار MQ7 ────
/*if (!calibrationComplete){// && (millis() - systemStartTime >= calibrationPeriod)) {
calibrateMQ7();
}*/
// ──── 4. خواندن CO ────
currentData.coPPM = readCOImproved();
int adc = analogRead(MQ7_PIN);
coSensorConnected = (adc > 10 && adc < 4085);
Serial1.print("[MQ7] ");
Serial1.print(currentData.coPPM, 1);
Serial1.println(" ppm");
// ──── 5. محاسبه درصدها ────
calculatePercentages(currentData);
// ──── 6. ثبت زمان آخرین موفقیت ────
if (success) {
i2cStatus.lastSuccess = millis();
}
lastSensorRead = millis();
Serial1.println("=== Reading Complete ===\n");
}
/* ================== راه‌اندازی اولیه ================== */
// ──────────────────────────────────────────────────────
// مقداردهی کامل سیستم
// ──────────────────────────────────────────────────────
inline void initSensors() {
delay(2000);
Serial1.println("\n=== Initializing Sensor System ===");
// ──── پاکسازی و راه‌اندازی I2C ────
cleanI2CBus();
Wire.setSDA(I2C_SDA_PIN);
Wire.setSCL(I2C_SCL_PIN);
Wire.begin();
Wire.setClock(100000); // 100kHz برای پایداری
Wire.setTimeout(1000); // افزایش timeout برای جلوگیری از hang شدن
delay(100);
bh1750SoftReset();
delay(20);
// ──── راه‌اندازی سنسورهای I2C ────
initI2CSensors();
// ──── راه‌اندازی MQ7 ────
pinMode(MQ7_PIN, INPUT);
analogReadResolution(12);
Serial1.println("[MQ7] Analog pin configured");
// ──── اسکن I2C (اختیاری - برای debug) ────
Serial1.println("\n[I2C] Scanning bus...");
byte deviceCount = 0;
for(byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial1.print(" Device found at 0x");
if (addr < 16) Serial1.print("0");
Serial1.println(addr, HEX);
deviceCount++;
}
}
Serial1.print("Total devices: ");
Serial1.println(deviceCount);
Serial1.println("\n=== System Ready ===\n");
}
/* ================== حلقه اصلی (جایگزین loop) ================== */
// ──────────────────────────────────────────────────────
// باید در loop() اصلی فراخوانی شود
// ──────────────────────────────────────────────────────
inline void updateSensors() {
static uint32_t lastRead = 0;
static uint32_t lastHealth = 0;
uint32_t now = millis();
// جلوگیری از overflow
if (now < lastRead) lastRead = 0;
if (now < lastHealth) lastHealth = 0;
// ──── خواندن دوره‌ای سنسورها ────
if (now - lastRead >= READ_INTERVAL) {
lastRead = now;
readSensors();
}
// ──── بررسی سلامت دوره‌ای (هر 30 ثانیه) ────
if (now - lastHealth >= HEALTH_CHECK_INTERVAL) {
lastHealth = now;
healthCheck();
}
}
#endif // SENSORS_H

View File

@@ -0,0 +1,130 @@
#include "SoftI2C.h"
SoftI2C::SoftI2C(uint8_t sda, uint8_t scl, uint32_t freq) {
sda_pin = sda;
scl_pin = scl;
delay_us = 1000000 / (2 * freq); // نیم پریود
}
void SoftI2C::begin() {
pinMode(sda_pin, INPUT_PULLUP);
pinMode(scl_pin, INPUT_PULLUP);
delay(10);
}
void SoftI2C::end() {
stop_condition();
pinMode(sda_pin, INPUT);
pinMode(scl_pin, INPUT);
}
void SoftI2C::start_condition() {
pinMode(sda_pin, OUTPUT);
sda_high();
scl_high();
i2c_delay();
sda_low();
i2c_delay();
scl_low();
i2c_delay();
}
void SoftI2C::stop_condition() {
pinMode(sda_pin, OUTPUT);
sda_low();
i2c_delay();
scl_high();
i2c_delay();
sda_high();
i2c_delay();
pinMode(sda_pin, INPUT_PULLUP);
pinMode(scl_pin, INPUT_PULLUP);
}
bool SoftI2C::write_byte(uint8_t data) {
pinMode(sda_pin, OUTPUT);
for(int i = 7; i >= 0; i--) {
if(data & (1 << i)) {
sda_high();
} else {
sda_low();
}
i2c_delay();
scl_high();
i2c_delay();
scl_low();
i2c_delay();
}
// خواندن ACK
pinMode(sda_pin, INPUT_PULLUP);
i2c_delay();
scl_high();
i2c_delay();
bool ack = !read_sda();
scl_low();
i2c_delay();
return ack;
}
uint8_t SoftI2C::read_byte(bool ack) {
pinMode(sda_pin, INPUT_PULLUP);
uint8_t data = 0;
for(int i = 7; i >= 0; i--) {
scl_high();
i2c_delay();
if(read_sda()) {
data |= (1 << i);
}
scl_low();
i2c_delay();
}
// ارسال ACK/NACK
pinMode(sda_pin, OUTPUT);
if(ack) {
sda_low();
} else {
sda_high();
}
i2c_delay();
scl_high();
i2c_delay();
scl_low();
i2c_delay();
return data;
}
bool SoftI2C::begin_transmission(uint8_t addr) {
start_condition();
return write_byte(addr << 1); // آدرس + بیت write
}
bool SoftI2C::write(uint8_t data) {
return write_byte(data);
}
bool SoftI2C::end_transmission() {
stop_condition();
return true;
}
uint8_t SoftI2C::request_from(uint8_t addr, uint8_t count) {
start_condition();
if(!write_byte((addr << 1) | 1)) { // آدرس + بیت read
return 0;
}
return count;
}
uint8_t SoftI2C::read() {
return read_byte(true);
}
bool SoftI2C::available() {
return true;
}

View File

@@ -0,0 +1,41 @@
#ifndef SOFTI2C_H
#define SOFTI2C_H
#include <Arduino.h>
class SoftI2C {
private:
uint8_t sda_pin;
uint8_t scl_pin;
uint32_t delay_us;
void sda_low() { digitalWrite(sda_pin, LOW); }
void sda_high() { digitalWrite(sda_pin, HIGH); }
void scl_low() { digitalWrite(scl_pin, LOW); }
void scl_high() { digitalWrite(scl_pin, HIGH); }
bool read_sda() { return digitalRead(sda_pin); }
void i2c_delay() { delayMicroseconds(delay_us); }
void start_condition();
void stop_condition();
bool write_byte(uint8_t data);
uint8_t read_byte(bool ack);
bool send_ack();
void send_nack();
public:
SoftI2C(uint8_t sda, uint8_t scl, uint32_t freq = 100000);
void begin();
void end();
bool begin_transmission(uint8_t addr);
bool write(uint8_t data);
bool end_transmission();
uint8_t request_from(uint8_t addr, uint8_t count);
uint8_t read();
bool available();
};
#endif

View File

@@ -0,0 +1,248 @@
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.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 <SPI.h>
// -------------------- فایل‌های پروژه --------------------
#include "Config.h"
#include "Memory.h"
#include "Sensors.h"
#include "EC200U.h"
#include "Display.h"
#include "Voltage_Reader.h"
#include "BatteryReader.h"
// -------------------- متغیرهای سراسری --------------------
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
BatteryReader batteryReader(BATTERY_VOLTAGE_PIN_A3); // برای باتری
//Adafruit_SHT31 sht31 = Adafruit_SHT31();
//BH1750 lightMeter;
SystemStatus i2cStatus = {false, false, 0, 0, 0};
HardwareSerial EC200U(USART3);
SPIClass flashSPI(FLASH_MOSI, FLASH_MISO, FLASH_SCK);
DeviceConfig config;
SensorData currentData;
// متغیرهای موقت برای پردازش پیامک‌ها
String tempPhoneNumber = "";
String tempTokenCode = "";
bool awaitingSMS2 = false;
unsigned long lastSensorRead = 0;
unsigned long lastUpload = 0;
unsigned long lastDisplayChange = 0;
unsigned long lastNetworkStatusUpdate = 0;
Arduino_GC9A01* tft = nullptr;
bool networkConnected = false;
int displayMode = 0;
String lastError = "";
String currentAPN = "";
String lastMessage = "";
// وضعیت سنسورها و اتصالات
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 دقیقه
//float MQ7_R0 = 10000.0;
bool isInCall = false;
bool powerState = false;
TwoWire Wire1(1); // I2C1
// -------------------- Setup --------------------
void setup() {
Serial1.begin(115200);
pinMode(PWRKEY_PIN, OUTPUT);
digitalWrite(PWRKEY_PIN, LOW);
pinMode(POWER_PIN, INPUT);
powerState = digitalRead(POWER_PIN) == HIGH;
delay(1000);
Serial1.println("\n\n🚀 IoT Device Starting...");
systemStartTime = millis();
pinMode(MQ7_PIN, INPUT);
initDisplay();
initSensors();
// Wire1.setSDA(PB9);
// Wire1.setSCL(PB8);
// Wire1.begin();
// //Wire1.setClock(100000);
// // I2C2 برای BH1750
// Wire.setSDA(PB7);
// Wire.setSCL(PB6);
// Wire.begin();
//Wire.setClock(100000);
// 1. ولتاژ را تست کن
bool voltageOK = voltageReader.testCircuit();
if (!voltageOK) {
Serial1.println("⚠️ Voltage issue detected!");
}
// 2. اطلاعات دیباگ ولتاژ
voltageReader.debugInfo();
// 3. خواندن اولیه ولتاژ
float initialVoltage = voltageReader.readVoltage();
Serial1.print("Initial voltage: ");
Serial1.print(initialVoltage, 2);
Serial1.println("V");
Serial1.println("1");
/*
// Wire = Wire1;
sht31Connected = sht31.begin(0x44,&Wire1);
Serial1.println("1");
Wire.beginTransmission(0x23);
uint8_t err = Wire.endTransmission();
Serial1.print("BH1750 ACK = ");
Serial1.println(err);
lightSensorConnected = lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23);
//lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
Serial1.println("2");
*/
flashInit();
readConfig();
currentData.batteryVoltage = batteryReader.readVoltage();
//calibrateMQ7();
//setupMQ7();
awaitingSMS2 = false;
tempPhoneNumber = "";
tempTokenCode = "";
readSensors();
displayAllParameters();
updateDisplay();
//--------------------------------
EC200U.begin(115200);
initEC200U();
if (config.verified) {
Serial1.println("Device ID: " + String(config.deviceId));
currentAPN = simTypeToAPN(config.simType);
//initEC200U();
} else {
Serial1.println("⚠️ Not configured - Waiting for SMS");
}
updateNetworkStatus();
lastNetworkStatusUpdate = millis();
Serial1.println("✅ Ready - CO calibration: " + String(MQ7sensorPreheated ? "Complete" : "In progress"));
if (config.verified && networkConnected) {
Serial1.println("\n🚀 First upload - sending data immediately...");
uploadData();
}
}
// -------------------- Loop --------------------
void loop() {
if (!isInCall) {
digitalWrite(PWRKEY_PIN, LOW);
}
checkSMS();
if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) {
//readSensors();
readSensors();
lastSensorRead=millis();
float batteryVoltage = batteryReader.readVoltage();
float batteryPercent = batteryReader.getBatteryPercentage();
currentData.batteryVoltage=batteryVoltage;
currentData.batteryPercent=batteryPercent;
/*Serial1.print("📊 Battery Voltage: ");
Serial1.print(batteryVoltage, 2);
Serial1.println("V");*/
/*
Serial1.print("📊 Battery Percentage: ");
Serial1.print(batteryPercent, 2);
Serial1.println("%");
*/
float voltage = voltageReader.readVoltage();
/**/
Serial1.print("📊 System Voltage: ");
Serial1.print(voltage, 2);
Serial1.println("V");
/**/
currentData.volage=voltage;
bool currentPower = digitalRead(POWER_PIN) == HIGH;
if(currentPower)
currentData.power=1;
else
currentData.power=0;
if(powerState)
currentData.oldPower=1;
else
currentData.oldPower=0;
if (currentPower != powerState) {
powerState = currentPower;
if (powerState) {
Serial.println("5V Connected");
} else {
Serial.println("5V Disconnected");
}
lastUpload -= 86400000L;
}
powerState = currentPower;
updateDisplay();
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();
}
}
delay(100);
}

View 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

View File

@@ -0,0 +1,290 @@
#pragma once
#include <Adafruit_GFX.h>
const uint8_t FreeSansBold12pt7bBitmaps[] PROGMEM = {
0xFF, 0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x60, 0xFF, 0xF0, 0xF3, 0xFC, 0xFF,
0x3F, 0xCF, 0x61, 0x98, 0x60, 0x0E, 0x70, 0x73, 0x83, 0x18, 0xFF, 0xF7,
0xFF, 0xBF, 0xFC, 0x73, 0x83, 0x18, 0x18, 0xC7, 0xFF, 0xBF, 0xFD, 0xFF,
0xE3, 0x18, 0x39, 0xC1, 0xCE, 0x0E, 0x70, 0x02, 0x00, 0x7E, 0x0F, 0xF8,
0x7F, 0xE7, 0xAF, 0xB9, 0x3D, 0xC8, 0x0F, 0x40, 0x3F, 0x00, 0xFF, 0x00,
0xFC, 0x05, 0xFF, 0x27, 0xF9, 0x3F, 0xEB, 0xEF, 0xFE, 0x3F, 0xE0, 0x7C,
0x00, 0x80, 0x04, 0x00, 0x3C, 0x06, 0x0F, 0xC1, 0x81, 0xFC, 0x30, 0x73,
0x8C, 0x0C, 0x31, 0x81, 0xCE, 0x60, 0x1F, 0xCC, 0x03, 0xF3, 0x00, 0x3C,
0x67, 0x80, 0x19, 0xF8, 0x02, 0x7F, 0x80, 0xCE, 0x70, 0x11, 0x86, 0x06,
0x39, 0xC1, 0x87, 0xF8, 0x30, 0x7E, 0x0C, 0x07, 0x80, 0x07, 0x80, 0x1F,
0xC0, 0x3F, 0xE0, 0x3C, 0xE0, 0x3C, 0xE0, 0x3E, 0xE0, 0x0F, 0xC0, 0x07,
0x00, 0x3F, 0x8C, 0x7F, 0xCC, 0xF1, 0xFC, 0xF0, 0xF8, 0xF0, 0x78, 0xF8,
0xF8, 0x7F, 0xFC, 0x3F, 0xDE, 0x1F, 0x8E, 0xFF, 0xFF, 0x66, 0x0C, 0x73,
0x8E, 0x71, 0xC7, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x1C, 0x71, 0xC3,
0x8E, 0x18, 0x70, 0xC3, 0x87, 0x1C, 0x38, 0xE3, 0x87, 0x1C, 0x71, 0xC7,
0x1C, 0x71, 0xCE, 0x38, 0xE7, 0x1C, 0x63, 0x80, 0x10, 0x23, 0x5F, 0xF3,
0x87, 0x1B, 0x14, 0x0E, 0x01, 0xC0, 0x38, 0x07, 0x0F, 0xFF, 0xFF, 0xFF,
0xF8, 0x70, 0x0E, 0x01, 0xC0, 0x38, 0x00, 0xFF, 0xF3, 0x36, 0xC0, 0xFF,
0xFF, 0xC0, 0xFF, 0xF0, 0x0C, 0x30, 0x86, 0x18, 0x61, 0x0C, 0x30, 0xC2,
0x18, 0x61, 0x84, 0x30, 0xC0, 0x1F, 0x83, 0xFC, 0x7F, 0xE7, 0x9E, 0xF0,
0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0,
0xF7, 0x9E, 0x7F, 0xE3, 0xFC, 0x0F, 0x00, 0x06, 0x1C, 0x7F, 0xFF, 0xE3,
0xC7, 0x8F, 0x1E, 0x3C, 0x78, 0xF1, 0xE3, 0xC7, 0x8F, 0x1E, 0x1F, 0x83,
0xFC, 0x7F, 0xEF, 0x9F, 0xF0, 0xFF, 0x0F, 0x00, 0xF0, 0x0F, 0x01, 0xE0,
0x3C, 0x0F, 0x81, 0xE0, 0x3C, 0x03, 0x80, 0x7F, 0xF7, 0xFF, 0x7F, 0xF0,
0x1F, 0x07, 0xFC, 0xFF, 0xEF, 0x1E, 0xF1, 0xE0, 0x1E, 0x03, 0xC0, 0x78,
0x07, 0xC0, 0x1E, 0x00, 0xF0, 0x0F, 0xF0, 0xFF, 0x1F, 0x7F, 0xE7, 0xFC,
0x1F, 0x80, 0x03, 0xC0, 0xF8, 0x1F, 0x07, 0xE1, 0xBC, 0x27, 0x8C, 0xF3,
0x1E, 0x63, 0xD8, 0x7B, 0xFF, 0xFF, 0xFF, 0xFE, 0x07, 0x80, 0xF0, 0x1E,
0x03, 0xC0, 0x3F, 0xE7, 0xFE, 0x7F, 0xE7, 0x00, 0x60, 0x06, 0xF8, 0x7F,
0xCF, 0xFE, 0xF1, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xFE, 0x1E, 0xFF,
0xE7, 0xFC, 0x3F, 0x00, 0x0F, 0x83, 0xFC, 0x7F, 0xE7, 0x9F, 0xF0, 0x0F,
0x78, 0xFF, 0xCF, 0xFE, 0xF9, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xF7,
0x9F, 0x7F, 0xE3, 0xFC, 0x0F, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xE0,
0x1C, 0x07, 0x01, 0xE0, 0x38, 0x0F, 0x01, 0xC0, 0x78, 0x0F, 0x01, 0xE0,
0x38, 0x0F, 0x01, 0xE0, 0x3C, 0x00, 0x0F, 0x03, 0xFC, 0x7F, 0xC7, 0x9E,
0x70, 0xE7, 0x0E, 0x39, 0xC1, 0xF8, 0x3F, 0xC7, 0x9E, 0xF0, 0xFF, 0x0F,
0xF0, 0xFF, 0x9F, 0x7F, 0xE3, 0xFC, 0x1F, 0x80, 0x1F, 0x03, 0xFC, 0x7F,
0xEF, 0x9E, 0xF0, 0xEF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF9, 0xF7, 0xFF, 0x3F,
0xF1, 0xEF, 0x00, 0xEF, 0x1E, 0x7F, 0xE7, 0xFC, 0x1F, 0x00, 0xFF, 0xF0,
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0x11, 0x6C,
0x00, 0x10, 0x07, 0x03, 0xF1, 0xFC, 0x7E, 0x0F, 0x80, 0xE0, 0x0F, 0xC0,
0x3F, 0x80, 0x7F, 0x00, 0xF0, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0E, 0x00, 0xFC,
0x07, 0xF0, 0x0F, 0xE0, 0x1F, 0x00, 0xF0, 0x7F, 0x1F, 0x8F, 0xE0, 0xF0,
0x08, 0x00, 0x1F, 0x07, 0xFC, 0x7F, 0xEF, 0x9F, 0xF0, 0xFF, 0x0F, 0x00,
0xF0, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x0E, 0x00, 0xE0, 0x00,
0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x00, 0xFE, 0x00, 0x1F, 0xFC, 0x03, 0xC0,
0xF0, 0x38, 0x01, 0xC3, 0x80, 0x07, 0x18, 0x3D, 0x99, 0x87, 0xEC, 0x6C,
0x71, 0xC3, 0xC3, 0x06, 0x1E, 0x18, 0x30, 0xF1, 0x81, 0x87, 0x8C, 0x18,
0x7C, 0x60, 0xC3, 0x63, 0x8E, 0x3B, 0x8F, 0xDF, 0x8C, 0x3C, 0xF0, 0x70,
0x00, 0x01, 0xC0, 0x00, 0x07, 0x80, 0x80, 0x1F, 0xFE, 0x00, 0x1F, 0xC0,
0x00, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x07, 0xF0, 0x07, 0xF0, 0x07,
0x70, 0x0F, 0x78, 0x0E, 0x78, 0x0E, 0x38, 0x1E, 0x3C, 0x1C, 0x3C, 0x3F,
0xFC, 0x3F, 0xFE, 0x3F, 0xFE, 0x78, 0x0E, 0x78, 0x0F, 0x70, 0x0F, 0xF0,
0x07, 0xFF, 0xC3, 0xFF, 0xCF, 0xFF, 0x3C, 0x3E, 0xF0, 0x7B, 0xC1, 0xEF,
0x0F, 0xBF, 0xFC, 0xFF, 0xE3, 0xFF, 0xCF, 0x07, 0xBC, 0x0F, 0xF0, 0x3F,
0xC0, 0xFF, 0x07, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, 0x80, 0x07, 0xE0, 0x1F,
0xF8, 0x3F, 0xFC, 0x7C, 0x3E, 0x78, 0x1F, 0xF8, 0x0F, 0xF0, 0x00, 0xF0,
0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF8, 0x0F, 0x78,
0x1F, 0x7C, 0x3E, 0x3F, 0xFE, 0x1F, 0xFC, 0x07, 0xF0, 0xFF, 0xE1, 0xFF,
0xE3, 0xFF, 0xE7, 0x83, 0xEF, 0x03, 0xDE, 0x07, 0xFC, 0x07, 0xF8, 0x0F,
0xF0, 0x1F, 0xE0, 0x3F, 0xC0, 0x7F, 0x80, 0xFF, 0x03, 0xFE, 0x07, 0xBC,
0x1F, 0x7F, 0xFC, 0xFF, 0xF1, 0xFF, 0x80, 0xFF, 0xF7, 0xFF, 0xBF, 0xFD,
0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xFC, 0xFF, 0xE7, 0xFF, 0x3C,
0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF,
0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F,
0xFE, 0xFF, 0xEF, 0xFE, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F,
0x00, 0xF0, 0x0F, 0x00, 0x03, 0xF0, 0x0F, 0xFC, 0x3F, 0xFE, 0x3E, 0x1F,
0x78, 0x07, 0x78, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x7F, 0xF0, 0x7F,
0xF0, 0x7F, 0xF0, 0x07, 0x78, 0x07, 0x7C, 0x0F, 0x3E, 0x1F, 0x3F, 0xFB,
0x0F, 0xFB, 0x03, 0xE3, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0,
0x3F, 0xC0, 0xFF, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFC,
0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xF0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xE0, 0x3C,
0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07,
0xF8, 0xFF, 0x1F, 0xE3, 0xFC, 0x7B, 0xFE, 0x7F, 0xC3, 0xE0, 0xF0, 0x3E,
0xF0, 0x3C, 0xF0, 0x78, 0xF0, 0xF0, 0xF1, 0xE0, 0xF3, 0xC0, 0xF7, 0x80,
0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0xFB, 0xC0, 0xF1, 0xE0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0x78, 0xF0, 0x3C, 0xF0, 0x3E, 0xF0, 0x1E, 0xF0, 0x1E,
0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1E, 0x03,
0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0xFF, 0xFF, 0xFF, 0xFC, 0xF8,
0x1F, 0xFE, 0x0F, 0xFF, 0x0F, 0xFF, 0x87, 0xFF, 0xC3, 0xFF, 0xE1, 0xFF,
0xF9, 0xFF, 0xFC, 0xEF, 0xFE, 0x77, 0xFB, 0x3B, 0xFD, 0xDD, 0xFE, 0xFC,
0xFF, 0x7E, 0x7F, 0x9F, 0x3F, 0xCF, 0x9F, 0xE7, 0x8F, 0xF3, 0xC7, 0xF8,
0xE3, 0xC0, 0xF0, 0x1F, 0xF0, 0x3F, 0xF0, 0x7F, 0xE0, 0xFF, 0xE1, 0xFF,
0xC3, 0xFD, 0xC7, 0xFB, 0x8F, 0xF3, 0x9F, 0xE7, 0x3F, 0xC7, 0x7F, 0x8F,
0xFF, 0x0F, 0xFE, 0x1F, 0xFC, 0x1F, 0xF8, 0x1F, 0xF0, 0x3F, 0xE0, 0x3C,
0x03, 0xE0, 0x0F, 0xFC, 0x0F, 0xFF, 0x87, 0xC7, 0xC7, 0x80, 0xF3, 0xC0,
0x7B, 0xC0, 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF8, 0x03, 0xFC, 0x01, 0xFE,
0x00, 0xF7, 0x80, 0xF3, 0xC0, 0x78, 0xF0, 0xF8, 0x7F, 0xFC, 0x1F, 0xFC,
0x03, 0xF8, 0x00, 0xFF, 0xE3, 0xFF, 0xEF, 0xFF, 0xBC, 0x1F, 0xF0, 0x3F,
0xC0, 0xFF, 0x03, 0xFC, 0x1F, 0xFF, 0xFB, 0xFF, 0xCF, 0xFE, 0x3C, 0x00,
0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x00, 0x03,
0xE0, 0x0F, 0xFC, 0x0F, 0xFF, 0x87, 0xC7, 0xC7, 0x80, 0xF3, 0xC0, 0x7B,
0xC0, 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF8, 0x03, 0xFC, 0x01, 0xFE, 0x04,
0xF7, 0x87, 0xF3, 0xC3, 0xF8, 0xF0, 0xF8, 0x7F, 0xFC, 0x1F, 0xFF, 0x83,
0xF1, 0x80, 0x00, 0x00, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xFC, 0xF0, 0x3E,
0xF0, 0x1E, 0xF0, 0x1E, 0xF0, 0x1E, 0xF0, 0x3C, 0xFF, 0xF8, 0xFF, 0xF0,
0xFF, 0xF8, 0xF0, 0x3C, 0xF0, 0x3C, 0xF0, 0x3C, 0xF0, 0x3C, 0xF0, 0x3C,
0xF0, 0x3C, 0xF0, 0x1F, 0x0F, 0xC0, 0x7F, 0xE1, 0xFF, 0xE7, 0xC3, 0xEF,
0x03, 0xDE, 0x00, 0x3C, 0x00, 0x7F, 0x00, 0x7F, 0xF0, 0x3F, 0xF8, 0x0F,
0xF8, 0x01, 0xF0, 0x01, 0xFE, 0x03, 0xDE, 0x0F, 0xBF, 0xFE, 0x3F, 0xF8,
0x1F, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0x0F, 0x00, 0xF0, 0x0F,
0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F,
0x00, 0xF0, 0x0F, 0x00, 0xF0, 0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F,
0xF0, 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xFF, 0x03,
0xFC, 0x0F, 0xF0, 0x3F, 0xC0, 0xF7, 0x87, 0x9F, 0xFE, 0x3F, 0xF0, 0x3F,
0x00, 0x70, 0x0E, 0xF0, 0x3D, 0xE0, 0x79, 0xC0, 0xE3, 0x81, 0xC7, 0x87,
0x87, 0x0E, 0x0E, 0x1C, 0x1E, 0x78, 0x1C, 0xE0, 0x39, 0xC0, 0x73, 0x80,
0x7E, 0x00, 0xFC, 0x01, 0xF8, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x70,
0x38, 0x1C, 0xE0, 0xF0, 0x79, 0xE1, 0xF0, 0xF3, 0xC3, 0xE1, 0xE3, 0x87,
0xC3, 0x87, 0x0F, 0x87, 0x0E, 0x3B, 0x9E, 0x1E, 0x77, 0x38, 0x1C, 0xEE,
0x70, 0x39, 0xCC, 0xE0, 0x73, 0x99, 0xC0, 0x6E, 0x3F, 0x00, 0xFC, 0x7E,
0x01, 0xF8, 0xFC, 0x03, 0xF0, 0xF8, 0x03, 0xE1, 0xE0, 0x07, 0x83, 0xC0,
0x0F, 0x07, 0x80, 0xF0, 0x3C, 0xF0, 0xF9, 0xE1, 0xE1, 0xE7, 0x83, 0xCF,
0x03, 0xFC, 0x03, 0xF0, 0x07, 0xE0, 0x07, 0x80, 0x0F, 0x00, 0x3F, 0x00,
0xFF, 0x01, 0xFE, 0x07, 0x9E, 0x0F, 0x1E, 0x3C, 0x3C, 0xF8, 0x3D, 0xE0,
0x78, 0xF0, 0x1E, 0x78, 0x1E, 0x78, 0x3C, 0x3C, 0x3C, 0x3C, 0x78, 0x1E,
0x78, 0x0E, 0x70, 0x0F, 0xF0, 0x07, 0xE0, 0x07, 0xE0, 0x03, 0xC0, 0x03,
0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03,
0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xF0, 0x0F, 0x00, 0xF0, 0x0F,
0x00, 0xF8, 0x07, 0x80, 0x78, 0x07, 0x80, 0x7C, 0x03, 0xC0, 0x3C, 0x03,
0xC0, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFC, 0xF3, 0xCF,
0x3C, 0xF3, 0xCF, 0x3C, 0xF3, 0xCF, 0x3C, 0xF3, 0xCF, 0x3C, 0xFF, 0xFF,
0xC0, 0xC1, 0x81, 0x03, 0x06, 0x04, 0x0C, 0x18, 0x10, 0x30, 0x60, 0x40,
0xC1, 0x81, 0x03, 0x06, 0xFF, 0xFF, 0xCF, 0x3C, 0xF3, 0xCF, 0x3C, 0xF3,
0xCF, 0x3C, 0xF3, 0xCF, 0x3C, 0xF3, 0xCF, 0xFF, 0xFF, 0xC0, 0x0F, 0x00,
0xF0, 0x0F, 0x01, 0xF8, 0x1B, 0x83, 0x9C, 0x39, 0xC3, 0x0C, 0x70, 0xE7,
0x0E, 0xE0, 0x70, 0xFF, 0xFF, 0xFF, 0xFC, 0xE6, 0x30, 0x1F, 0x83, 0xFF,
0x1F, 0xFD, 0xE1, 0xE0, 0x0F, 0x03, 0xF9, 0xFF, 0xDF, 0x1E, 0xF0, 0xF7,
0x8F, 0xBF, 0xFC, 0xFF, 0xE3, 0xCF, 0x80, 0xF0, 0x07, 0x80, 0x3C, 0x01,
0xE0, 0x0F, 0x00, 0x7B, 0xC3, 0xFF, 0x9F, 0xFE, 0xF8, 0xF7, 0x83, 0xFC,
0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0xE3, 0xDF, 0xFE, 0xFF, 0xE7, 0xBE,
0x00, 0x0F, 0x83, 0xFE, 0x7F, 0xF7, 0x8F, 0xF0, 0x7F, 0x00, 0xF0, 0x0F,
0x00, 0xF0, 0x77, 0x8F, 0x7F, 0xF3, 0xFE, 0x0F, 0x80, 0x00, 0x78, 0x03,
0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x8F, 0xBC, 0xFF, 0xEF, 0xFF, 0x78, 0xFF,
0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, 0x3D, 0xE3, 0xEF, 0xFF, 0x3F,
0xF8, 0xFB, 0xC0, 0x1F, 0x81, 0xFE, 0x1F, 0xF9, 0xF1, 0xCF, 0x07, 0x7F,
0xFB, 0xFF, 0xDE, 0x00, 0xF0, 0x03, 0xC3, 0x9F, 0xFC, 0x7F, 0xC0, 0xF8,
0x00, 0x3E, 0xFD, 0xFB, 0xC7, 0x9F, 0xBF, 0x3C, 0x78, 0xF1, 0xE3, 0xC7,
0x8F, 0x1E, 0x3C, 0x78, 0xF0, 0x1E, 0x79, 0xFB, 0xDF, 0xFE, 0xF1, 0xFF,
0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0xC7, 0xDF, 0xFE, 0x7F,
0xF1, 0xF7, 0x80, 0x3C, 0x01, 0xFF, 0x1E, 0x7F, 0xF0, 0xFE, 0x00, 0xF0,
0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x7C, 0xFF, 0xEF, 0xFF, 0xF9,
0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0,
0xFF, 0x0F, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3C,
0xF3, 0xC0, 0x00, 0xF3, 0xCF, 0x3C, 0xF3, 0xCF, 0x3C, 0xF3, 0xCF, 0x3C,
0xF3, 0xCF, 0xFF, 0xFF, 0x80, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0,
0x0F, 0x0F, 0xF1, 0xEF, 0x3C, 0xF7, 0x8F, 0xF0, 0xFF, 0x0F, 0xF8, 0xFF,
0x8F, 0x3C, 0xF1, 0xCF, 0x1E, 0xF0, 0xEF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x8F, 0x9F, 0xFB, 0xFB, 0xFF, 0xFF,
0xFC, 0xF8, 0xFF, 0x1E, 0x1F, 0xE3, 0xC3, 0xFC, 0x78, 0x7F, 0x8F, 0x0F,
0xF1, 0xE1, 0xFE, 0x3C, 0x3F, 0xC7, 0x87, 0xF8, 0xF0, 0xFF, 0x1E, 0x1E,
0xF7, 0xCF, 0xFE, 0xFF, 0xFF, 0x9F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F,
0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xF0, 0x0F, 0x81, 0xFF, 0x1F,
0xFC, 0xF1, 0xEF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7B, 0xC7,
0x9F, 0xFC, 0x7F, 0xC0, 0xF8, 0x00, 0xF7, 0xC7, 0xFF, 0x3F, 0xFD, 0xF1,
0xEF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0xC7, 0xBF, 0xFD,
0xFF, 0xCF, 0x78, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x00,
0x0F, 0x79, 0xFF, 0xDF, 0xFE, 0xF1, 0xFF, 0x07, 0xF8, 0x3F, 0xC1, 0xFE,
0x0F, 0xF0, 0x7B, 0xC7, 0xDF, 0xFE, 0x7F, 0xF1, 0xF7, 0x80, 0x3C, 0x01,
0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0xF3, 0xF7, 0xFF, 0xF8, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x1F, 0x87, 0xFC, 0xFF, 0xEF,
0x0F, 0xF8, 0x0F, 0xF0, 0x7F, 0xE0, 0xFF, 0x01, 0xFF, 0x0F, 0xFF, 0xE7,
0xFE, 0x1F, 0x80, 0x79, 0xE7, 0xBF, 0xFD, 0xE7, 0x9E, 0x79, 0xE7, 0x9E,
0x7D, 0xF3, 0xC0, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F,
0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x1F, 0xFF, 0xF7, 0xFF, 0x3E, 0xF0, 0xF0,
0x7B, 0x83, 0x9E, 0x1C, 0xF1, 0xE3, 0x8E, 0x1C, 0x70, 0x77, 0x83, 0xB8,
0x1D, 0xC0, 0x7E, 0x03, 0xE0, 0x1F, 0x00, 0x70, 0x00, 0xF0, 0xE1, 0xDC,
0x78, 0x77, 0x1F, 0x3D, 0xE7, 0xCF, 0x79, 0xB3, 0x8E, 0x6C, 0xE3, 0xBB,
0x38, 0xEE, 0xFC, 0x1F, 0x3F, 0x07, 0xC7, 0xC1, 0xF1, 0xF0, 0x7C, 0x78,
0x0E, 0x1E, 0x00, 0x78, 0xF3, 0xC7, 0x8F, 0x78, 0x3B, 0x81, 0xFC, 0x07,
0xC0, 0x1E, 0x01, 0xF0, 0x1F, 0xC0, 0xEF, 0x0F, 0x78, 0xF1, 0xE7, 0x87,
0x00, 0xF0, 0x7B, 0x83, 0x9E, 0x1C, 0x71, 0xE3, 0x8E, 0x1E, 0x70, 0x73,
0x83, 0xB8, 0x1F, 0xC0, 0x7E, 0x03, 0xE0, 0x0F, 0x00, 0x70, 0x03, 0x80,
0x3C, 0x07, 0xC0, 0x3E, 0x01, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F,
0x07, 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0,
0x1C, 0xF3, 0xCE, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0xBC, 0xF0, 0xE3, 0x8E,
0x38, 0xE3, 0x8E, 0x3C, 0xF1, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
0xE3, 0x8F, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x0F, 0x3D, 0xC7, 0x1C,
0x71, 0xC7, 0x1C, 0xF3, 0xCE, 0x00, 0x78, 0x0F, 0xE0, 0xCF, 0x30, 0x7F,
0x01, 0xE0};
const GFXglyph FreeSansBold12pt7bGlyphs[] PROGMEM = {
{0, 0, 0, 7, 0, 1}, // 0x20 ' '
{0, 4, 17, 8, 3, -16}, // 0x21 '!'
{9, 10, 6, 11, 1, -17}, // 0x22 '"'
{17, 13, 16, 13, 0, -15}, // 0x23 '#'
{43, 13, 20, 13, 0, -17}, // 0x24 '$'
{76, 19, 17, 21, 1, -16}, // 0x25 '%'
{117, 16, 17, 17, 1, -16}, // 0x26 '&'
{151, 4, 6, 6, 1, -17}, // 0x27 '''
{154, 6, 22, 8, 1, -17}, // 0x28 '('
{171, 6, 22, 8, 1, -17}, // 0x29 ')'
{188, 7, 8, 9, 1, -17}, // 0x2A '*'
{195, 11, 11, 14, 2, -10}, // 0x2B '+'
{211, 4, 7, 6, 1, -2}, // 0x2C ','
{215, 6, 3, 8, 1, -7}, // 0x2D '-'
{218, 4, 3, 6, 1, -2}, // 0x2E '.'
{220, 6, 17, 7, 0, -16}, // 0x2F '/'
{233, 12, 17, 13, 1, -16}, // 0x30 '0'
{259, 7, 17, 14, 3, -16}, // 0x31 '1'
{274, 12, 17, 13, 1, -16}, // 0x32 '2'
{300, 12, 17, 13, 1, -16}, // 0x33 '3'
{326, 11, 17, 13, 1, -16}, // 0x34 '4'
{350, 12, 17, 13, 1, -16}, // 0x35 '5'
{376, 12, 17, 13, 1, -16}, // 0x36 '6'
{402, 11, 17, 13, 1, -16}, // 0x37 '7'
{426, 12, 17, 13, 1, -16}, // 0x38 '8'
{452, 12, 17, 13, 1, -16}, // 0x39 '9'
{478, 4, 12, 6, 1, -11}, // 0x3A ':'
{484, 4, 16, 6, 1, -11}, // 0x3B ';'
{492, 12, 12, 14, 1, -11}, // 0x3C '<'
{510, 12, 9, 14, 1, -9}, // 0x3D '='
{524, 12, 12, 14, 1, -11}, // 0x3E '>'
{542, 12, 18, 15, 2, -17}, // 0x3F '?'
{569, 21, 21, 23, 1, -17}, // 0x40 '@'
{625, 16, 18, 17, 0, -17}, // 0x41 'A'
{661, 14, 18, 17, 2, -17}, // 0x42 'B'
{693, 16, 18, 17, 1, -17}, // 0x43 'C'
{729, 15, 18, 17, 2, -17}, // 0x44 'D'
{763, 13, 18, 16, 2, -17}, // 0x45 'E'
{793, 12, 18, 15, 2, -17}, // 0x46 'F'
{820, 16, 18, 18, 1, -17}, // 0x47 'G'
{856, 14, 18, 18, 2, -17}, // 0x48 'H'
{888, 4, 18, 7, 2, -17}, // 0x49 'I'
{897, 11, 18, 14, 1, -17}, // 0x4A 'J'
{922, 16, 18, 17, 2, -17}, // 0x4B 'K'
{958, 11, 18, 15, 2, -17}, // 0x4C 'L'
{983, 17, 18, 21, 2, -17}, // 0x4D 'M'
{1022, 15, 18, 18, 2, -17}, // 0x4E 'N'
{1056, 17, 18, 19, 1, -17}, // 0x4F 'O'
{1095, 14, 18, 16, 2, -17}, // 0x50 'P'
{1127, 17, 19, 19, 1, -17}, // 0x51 'Q'
{1168, 16, 18, 17, 2, -17}, // 0x52 'R'
{1204, 15, 18, 16, 1, -17}, // 0x53 'S'
{1238, 12, 18, 15, 2, -17}, // 0x54 'T'
{1265, 14, 18, 18, 2, -17}, // 0x55 'U'
{1297, 15, 18, 16, 0, -17}, // 0x56 'V'
{1331, 23, 18, 23, 0, -17}, // 0x57 'W'
{1383, 15, 18, 16, 1, -17}, // 0x58 'X'
{1417, 16, 18, 15, 0, -17}, // 0x59 'Y'
{1453, 13, 18, 15, 1, -17}, // 0x5A 'Z'
{1483, 6, 23, 8, 2, -17}, // 0x5B '['
{1501, 7, 17, 7, 0, -16}, // 0x5C '\'
{1516, 6, 23, 8, 0, -17}, // 0x5D ']'
{1534, 12, 11, 14, 1, -16}, // 0x5E '^'
{1551, 15, 2, 13, -1, 4}, // 0x5F '_'
{1555, 4, 3, 6, 0, -17}, // 0x60 '`'
{1557, 13, 13, 14, 1, -12}, // 0x61 'a'
{1579, 13, 18, 15, 2, -17}, // 0x62 'b'
{1609, 12, 13, 13, 1, -12}, // 0x63 'c'
{1629, 13, 18, 15, 1, -17}, // 0x64 'd'
{1659, 13, 13, 14, 1, -12}, // 0x65 'e'
{1681, 7, 18, 8, 1, -17}, // 0x66 'f'
{1697, 13, 18, 15, 1, -12}, // 0x67 'g'
{1727, 12, 18, 14, 2, -17}, // 0x68 'h'
{1754, 4, 18, 7, 2, -17}, // 0x69 'i'
{1763, 6, 23, 7, 0, -17}, // 0x6A 'j'
{1781, 12, 18, 14, 2, -17}, // 0x6B 'k'
{1808, 4, 18, 6, 2, -17}, // 0x6C 'l'
{1817, 19, 13, 21, 2, -12}, // 0x6D 'm'
{1848, 12, 13, 15, 2, -12}, // 0x6E 'n'
{1868, 13, 13, 15, 1, -12}, // 0x6F 'o'
{1890, 13, 18, 15, 2, -12}, // 0x70 'p'
{1920, 13, 18, 15, 1, -12}, // 0x71 'q'
{1950, 8, 13, 9, 2, -12}, // 0x72 'r'
{1963, 12, 13, 13, 1, -12}, // 0x73 's'
{1983, 6, 15, 8, 1, -14}, // 0x74 't'
{1995, 12, 13, 15, 2, -12}, // 0x75 'u'
{2015, 13, 13, 13, 0, -12}, // 0x76 'v'
{2037, 18, 13, 19, 0, -12}, // 0x77 'w'
{2067, 13, 13, 13, 0, -12}, // 0x78 'x'
{2089, 13, 18, 13, 0, -12}, // 0x79 'y'
{2119, 10, 13, 12, 1, -12}, // 0x7A 'z'
{2136, 6, 23, 9, 1, -17}, // 0x7B '{'
{2154, 2, 22, 7, 2, -17}, // 0x7C '|'
{2160, 6, 23, 9, 3, -17}, // 0x7D '}'
{2178, 12, 5, 12, 0, -7}}; // 0x7E '~'
const GFXfont FreeSansBold12pt7b PROGMEM = {
(uint8_t *)FreeSansBold12pt7bBitmaps, (GFXglyph *)FreeSansBold12pt7bGlyphs,
0x20, 0x7E, 29};
// Approx. 2858 bytes

View File

@@ -0,0 +1,210 @@
#pragma once
#include <Adafruit_GFX.h>
const uint8_t FreeSansBold9pt7bBitmaps[] PROGMEM = {
0xFF, 0xFF, 0xFE, 0x48, 0x7E, 0xEF, 0xDF, 0xBF, 0x74, 0x40, 0x19, 0x86,
0x67, 0xFD, 0xFF, 0x33, 0x0C, 0xC3, 0x33, 0xFE, 0xFF, 0x99, 0x86, 0x61,
0x90, 0x10, 0x1F, 0x1F, 0xDE, 0xFF, 0x3F, 0x83, 0xC0, 0xFC, 0x1F, 0x09,
0xFC, 0xFE, 0xF7, 0xF1, 0xE0, 0x40, 0x38, 0x10, 0x7C, 0x30, 0xC6, 0x20,
0xC6, 0x40, 0xC6, 0x40, 0x7C, 0x80, 0x39, 0x9C, 0x01, 0x3E, 0x03, 0x63,
0x02, 0x63, 0x04, 0x63, 0x0C, 0x3E, 0x08, 0x1C, 0x0E, 0x01, 0xF8, 0x3B,
0x83, 0xB8, 0x3F, 0x01, 0xE0, 0x3E, 0x67, 0x76, 0xE3, 0xEE, 0x1C, 0xF3,
0xC7, 0xFE, 0x3F, 0x70, 0xFF, 0xF4, 0x18, 0x63, 0x1C, 0x73, 0x8E, 0x38,
0xE3, 0x8E, 0x18, 0x70, 0xC3, 0x06, 0x08, 0x61, 0x83, 0x0E, 0x38, 0x71,
0xC7, 0x1C, 0x71, 0xC6, 0x38, 0xE3, 0x18, 0x40, 0x21, 0x3E, 0x45, 0x28,
0x38, 0x70, 0xE7, 0xFF, 0xE7, 0x0E, 0x1C, 0xFC, 0x9C, 0xFF, 0xC0, 0xFC,
0x08, 0xC4, 0x23, 0x10, 0x84, 0x62, 0x11, 0x88, 0x00, 0x3E, 0x3F, 0x9D,
0xDC, 0x7E, 0x3F, 0x1F, 0x8F, 0xC7, 0xE3, 0xF1, 0xDD, 0xCF, 0xE3, 0xE0,
0x08, 0xFF, 0xF3, 0x9C, 0xE7, 0x39, 0xCE, 0x73, 0x80, 0x3E, 0x3F, 0xB8,
0xFC, 0x70, 0x38, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x0F, 0xF7, 0xF8,
0x3C, 0x7F, 0xE7, 0xE7, 0x07, 0x0C, 0x0E, 0x07, 0x07, 0xE7, 0xE7, 0x7E,
0x3C, 0x0E, 0x1E, 0x1E, 0x2E, 0x2E, 0x4E, 0x4E, 0x8E, 0xFF, 0xFF, 0x0E,
0x0E, 0x0E, 0x7F, 0x3F, 0x90, 0x18, 0x0D, 0xE7, 0xFB, 0x9E, 0x07, 0x03,
0x81, 0xF1, 0xFF, 0xE7, 0xC0, 0x3E, 0x3F, 0x9C, 0xFC, 0x0E, 0xE7, 0xFB,
0xDF, 0xC7, 0xE3, 0xF1, 0xDD, 0xEF, 0xE3, 0xE0, 0xFF, 0xFF, 0xC0, 0xE0,
0xE0, 0x60, 0x70, 0x30, 0x38, 0x1C, 0x0C, 0x0E, 0x07, 0x03, 0x80, 0x3F,
0x1F, 0xEE, 0x3F, 0x87, 0xE3, 0xCF, 0xC7, 0xFB, 0xCF, 0xE1, 0xF8, 0x7F,
0x3D, 0xFE, 0x3F, 0x00, 0x3E, 0x3F, 0xBD, 0xDC, 0x7E, 0x3F, 0x1F, 0xDE,
0xFF, 0x3B, 0x81, 0xF9, 0xCF, 0xE3, 0xC0, 0xFC, 0x00, 0x07, 0xE0, 0xFC,
0x00, 0x07, 0xE5, 0xE0, 0x00, 0x83, 0xC7, 0xDF, 0x0C, 0x07, 0x80, 0xF8,
0x1F, 0x01, 0x80, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x70,
0x3F, 0x03, 0xE0, 0x38, 0x7D, 0xF1, 0xE0, 0x80, 0x00, 0x3E, 0x3F, 0xB8,
0xFC, 0x70, 0x38, 0x1C, 0x1C, 0x1C, 0x1C, 0x0E, 0x00, 0x03, 0x81, 0xC0,
0x03, 0xF0, 0x0F, 0xFC, 0x1E, 0x0E, 0x38, 0x02, 0x70, 0xE9, 0x63, 0x19,
0xC2, 0x19, 0xC6, 0x11, 0xC6, 0x33, 0xC6, 0x32, 0x63, 0xFE, 0x73, 0xDC,
0x3C, 0x00, 0x1F, 0xF8, 0x07, 0xF0, 0x07, 0x00, 0xF0, 0x0F, 0x80, 0xF8,
0x1D, 0x81, 0x9C, 0x19, 0xC3, 0x8C, 0x3F, 0xE7, 0xFE, 0x70, 0x66, 0x07,
0xE0, 0x70, 0xFF, 0x9F, 0xFB, 0x83, 0xF0, 0x7E, 0x0F, 0xFF, 0x3F, 0xF7,
0x06, 0xE0, 0xFC, 0x1F, 0x83, 0xFF, 0xEF, 0xF8, 0x1F, 0x83, 0xFE, 0x78,
0xE7, 0x07, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x07, 0x07, 0x78,
0xF3, 0xFE, 0x1F, 0x80, 0xFF, 0x8F, 0xFC, 0xE0, 0xEE, 0x0E, 0xE0, 0x7E,
0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x7E, 0x0E, 0xE0, 0xEF, 0xFC, 0xFF, 0x80,
0xFF, 0xFF, 0xF8, 0x1C, 0x0E, 0x07, 0xFB, 0xFD, 0xC0, 0xE0, 0x70, 0x38,
0x1F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF8, 0x1C, 0x0E, 0x07, 0xFB, 0xFD, 0xC0,
0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x00, 0x0F, 0x87, 0xF9, 0xE3, 0xB8, 0x3E,
0x01, 0xC0, 0x38, 0xFF, 0x1F, 0xE0, 0x6E, 0x0D, 0xE3, 0x9F, 0xD0, 0xF2,
0xE0, 0xFC, 0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xFF, 0xFF, 0xFF, 0x07, 0xE0,
0xFC, 0x1F, 0x83, 0xF0, 0x7E, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 0x7E, 0x3C,
0xE0, 0xEE, 0x1C, 0xE3, 0x8E, 0x70, 0xEE, 0x0F, 0xC0, 0xFE, 0x0F, 0x70,
0xE7, 0x0E, 0x38, 0xE1, 0xCE, 0x0E, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xFF, 0xFF, 0xF8, 0x7F, 0xE1,
0xFF, 0x87, 0xFE, 0x1F, 0xEC, 0x7F, 0xB3, 0x7E, 0xCD, 0xFB, 0x37, 0xEC,
0xDF, 0x9E, 0x7E, 0x79, 0xF9, 0xE7, 0xE7, 0x9C, 0xE0, 0xFE, 0x1F, 0xC3,
0xFC, 0x7F, 0xCF, 0xD9, 0xFB, 0xBF, 0x37, 0xE7, 0xFC, 0x7F, 0x87, 0xF0,
0xFE, 0x0E, 0x0F, 0x81, 0xFF, 0x1E, 0x3C, 0xE0, 0xEE, 0x03, 0xF0, 0x1F,
0x80, 0xFC, 0x07, 0xE0, 0x3B, 0x83, 0x9E, 0x3C, 0x7F, 0xC0, 0xF8, 0x00,
0xFF, 0x9F, 0xFB, 0x87, 0xF0, 0x7E, 0x0F, 0xC3, 0xFF, 0xF7, 0xFC, 0xE0,
0x1C, 0x03, 0x80, 0x70, 0x0E, 0x00, 0x0F, 0x81, 0xFF, 0x1E, 0x3C, 0xE0,
0xEE, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE1, 0xBB, 0x8F, 0x9E, 0x3C,
0x7F, 0xE0, 0xFB, 0x80, 0x08, 0xFF, 0x8F, 0xFC, 0xE0, 0xEE, 0x0E, 0xE0,
0xEE, 0x0E, 0xFF, 0xCF, 0xFC, 0xE0, 0xEE, 0x0E, 0xE0, 0xEE, 0x0E, 0xE0,
0xF0, 0x3F, 0x0F, 0xFB, 0xC7, 0xF0, 0x7E, 0x01, 0xFC, 0x1F, 0xF0, 0x3F,
0x00, 0xFC, 0x1D, 0xC7, 0xBF, 0xE1, 0xF8, 0xFF, 0xFF, 0xC7, 0x03, 0x81,
0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0, 0xE0, 0xFC,
0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xC1, 0xF8, 0x3F, 0x07, 0xE0, 0xFC, 0x1F,
0xC7, 0xBF, 0xE1, 0xF0, 0x60, 0x67, 0x0E, 0x70, 0xE3, 0x0C, 0x30, 0xC3,
0x9C, 0x19, 0x81, 0x98, 0x1F, 0x80, 0xF0, 0x0F, 0x00, 0xF0, 0x06, 0x00,
0x61, 0xC3, 0xB8, 0xE1, 0x9C, 0x70, 0xCE, 0x3C, 0xE3, 0x36, 0x71, 0x9B,
0x30, 0xED, 0x98, 0x36, 0x7C, 0x1B, 0x3C, 0x0F, 0x1E, 0x07, 0x8F, 0x01,
0xC3, 0x80, 0xE1, 0x80, 0x70, 0xE7, 0x8E, 0x39, 0xC1, 0xF8, 0x1F, 0x80,
0xF0, 0x07, 0x00, 0xF0, 0x1F, 0x81, 0x9C, 0x39, 0xC7, 0x0E, 0x70, 0xE0,
0xE0, 0xFC, 0x39, 0xC7, 0x18, 0xC3, 0xB8, 0x36, 0x07, 0xC0, 0x70, 0x0E,
0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0xFF, 0xFF, 0xC0, 0xE0, 0xE0, 0xF0,
0x70, 0x70, 0x70, 0x78, 0x38, 0x38, 0x1F, 0xFF, 0xF8, 0xFF, 0xEE, 0xEE,
0xEE, 0xEE, 0xEE, 0xEE, 0xEF, 0xF0, 0x86, 0x10, 0x86, 0x10, 0x84, 0x30,
0x84, 0x30, 0x80, 0xFF, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x7F, 0xF0,
0x18, 0x1C, 0x3C, 0x3E, 0x36, 0x66, 0x63, 0xC3, 0xFF, 0xC0, 0xCC, 0x3F,
0x1F, 0xEE, 0x38, 0x0E, 0x3F, 0x9E, 0xEE, 0x3B, 0x9E, 0xFF, 0x9E, 0xE0,
0xE0, 0x38, 0x0E, 0x03, 0xBC, 0xFF, 0xBC, 0xEE, 0x1F, 0x87, 0xE1, 0xF8,
0x7F, 0x3B, 0xFE, 0xEF, 0x00, 0x1F, 0x3F, 0xDC, 0x7C, 0x0E, 0x07, 0x03,
0x80, 0xE3, 0x7F, 0x8F, 0x00, 0x03, 0x81, 0xC0, 0xE7, 0x77, 0xFB, 0xBF,
0x8F, 0xC7, 0xE3, 0xF1, 0xFD, 0xEF, 0xF3, 0xB8, 0x3E, 0x3F, 0x9C, 0xDC,
0x3F, 0xFF, 0xFF, 0x81, 0xC3, 0x7F, 0x8F, 0x00, 0x3B, 0xDD, 0xFF, 0xB9,
0xCE, 0x73, 0x9C, 0xE7, 0x00, 0x3B, 0xBF, 0xDD, 0xFC, 0x7E, 0x3F, 0x1F,
0x8F, 0xEF, 0x7F, 0x9D, 0xC0, 0xFC, 0x77, 0xF1, 0xF0, 0xE0, 0x70, 0x38,
0x1D, 0xEF, 0xFF, 0x9F, 0x8F, 0xC7, 0xE3, 0xF1, 0xF8, 0xFC, 0x7E, 0x38,
0xFC, 0x7F, 0xFF, 0xFF, 0xFE, 0x77, 0x07, 0x77, 0x77, 0x77, 0x77, 0x77,
0x7F, 0xE0, 0xE0, 0x70, 0x38, 0x1C, 0x7E, 0x77, 0x73, 0xF1, 0xF8, 0xFE,
0x77, 0x39, 0xDC, 0x6E, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xEF, 0x7B,
0xFF, 0xFE, 0x39, 0xF8, 0xE7, 0xE3, 0x9F, 0x8E, 0x7E, 0x39, 0xF8, 0xE7,
0xE3, 0x9F, 0x8E, 0x70, 0xEF, 0x7F, 0xF8, 0xFC, 0x7E, 0x3F, 0x1F, 0x8F,
0xC7, 0xE3, 0xF1, 0xC0, 0x1E, 0x1F, 0xE7, 0x3B, 0x87, 0xE1, 0xF8, 0x7E,
0x1D, 0xCE, 0x7F, 0x87, 0x80, 0xEF, 0x3F, 0xEF, 0x3B, 0x87, 0xE1, 0xF8,
0x7E, 0x1F, 0xCE, 0xFF, 0xBB, 0xCE, 0x03, 0x80, 0xE0, 0x38, 0x00, 0x3B,
0xBF, 0xFD, 0xFC, 0x7E, 0x3F, 0x1F, 0x8F, 0xEF, 0x7F, 0x9D, 0xC0, 0xE0,
0x70, 0x38, 0x1C, 0xEF, 0xFF, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x80, 0x3E,
0x3F, 0xB8, 0xFC, 0x0F, 0xC3, 0xFC, 0x3F, 0xC7, 0xFF, 0x1F, 0x00, 0x73,
0xBF, 0xF7, 0x39, 0xCE, 0x73, 0x9E, 0x70, 0xE3, 0xF1, 0xF8, 0xFC, 0x7E,
0x3F, 0x1F, 0x8F, 0xC7, 0xFF, 0xBD, 0xC0, 0xE1, 0x98, 0x67, 0x39, 0xCC,
0x33, 0x0D, 0xC3, 0xE0, 0x78, 0x1E, 0x07, 0x00, 0xE3, 0x1D, 0x9E, 0x66,
0x79, 0x99, 0xE6, 0x77, 0xB8, 0xD2, 0xC3, 0xCF, 0x0F, 0x3C, 0x3C, 0xF0,
0x73, 0x80, 0x73, 0x9C, 0xE3, 0xF0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0xFC,
0x73, 0x9C, 0xE0, 0xE1, 0xD8, 0x67, 0x39, 0xCE, 0x33, 0x0E, 0xC3, 0xE0,
0x78, 0x1E, 0x03, 0x00, 0xC0, 0x70, 0x38, 0x0E, 0x00, 0xFE, 0xFE, 0x0E,
0x1C, 0x38, 0x38, 0x70, 0xE0, 0xFF, 0xFF, 0x37, 0x66, 0x66, 0x6E, 0xE6,
0x66, 0x66, 0x67, 0x30, 0xFF, 0xFF, 0x80, 0xCE, 0x66, 0x66, 0x67, 0x76,
0x66, 0x66, 0x6E, 0xC0, 0x71, 0x8E};
const GFXglyph FreeSansBold9pt7bGlyphs[] PROGMEM = {
{0, 0, 0, 5, 0, 1}, // 0x20 ' '
{0, 3, 13, 6, 2, -12}, // 0x21 '!'
{5, 7, 5, 9, 1, -12}, // 0x22 '"'
{10, 10, 12, 10, 0, -11}, // 0x23 '#'
{25, 9, 15, 10, 1, -13}, // 0x24 '$'
{42, 16, 13, 16, 0, -12}, // 0x25 '%'
{68, 12, 13, 13, 1, -12}, // 0x26 '&'
{88, 3, 5, 5, 1, -12}, // 0x27 '''
{90, 6, 17, 6, 1, -12}, // 0x28 '('
{103, 6, 17, 6, 0, -12}, // 0x29 ')'
{116, 5, 6, 7, 1, -12}, // 0x2A '*'
{120, 7, 8, 11, 2, -7}, // 0x2B '+'
{127, 3, 5, 4, 1, -1}, // 0x2C ','
{129, 5, 2, 6, 0, -5}, // 0x2D '-'
{131, 3, 2, 4, 1, -1}, // 0x2E '.'
{132, 5, 13, 5, 0, -12}, // 0x2F '/'
{141, 9, 13, 10, 1, -12}, // 0x30 '0'
{156, 5, 13, 10, 2, -12}, // 0x31 '1'
{165, 9, 13, 10, 1, -12}, // 0x32 '2'
{180, 8, 13, 10, 1, -12}, // 0x33 '3'
{193, 8, 13, 10, 2, -12}, // 0x34 '4'
{206, 9, 13, 10, 1, -12}, // 0x35 '5'
{221, 9, 13, 10, 1, -12}, // 0x36 '6'
{236, 9, 13, 10, 0, -12}, // 0x37 '7'
{251, 10, 13, 10, 0, -12}, // 0x38 '8'
{268, 9, 13, 10, 1, -12}, // 0x39 '9'
{283, 3, 9, 4, 1, -8}, // 0x3A ':'
{287, 3, 12, 4, 1, -8}, // 0x3B ';'
{292, 9, 9, 11, 1, -8}, // 0x3C '<'
{303, 9, 6, 11, 1, -6}, // 0x3D '='
{310, 9, 9, 11, 1, -8}, // 0x3E '>'
{321, 9, 13, 11, 1, -12}, // 0x3F '?'
{336, 16, 15, 18, 0, -12}, // 0x40 '@'
{366, 12, 13, 13, 0, -12}, // 0x41 'A'
{386, 11, 13, 13, 1, -12}, // 0x42 'B'
{404, 12, 13, 13, 1, -12}, // 0x43 'C'
{424, 12, 13, 13, 1, -12}, // 0x44 'D'
{444, 9, 13, 12, 1, -12}, // 0x45 'E'
{459, 9, 13, 11, 1, -12}, // 0x46 'F'
{474, 11, 13, 14, 1, -12}, // 0x47 'G'
{492, 11, 13, 13, 1, -12}, // 0x48 'H'
{510, 3, 13, 6, 1, -12}, // 0x49 'I'
{515, 8, 13, 10, 1, -12}, // 0x4A 'J'
{528, 12, 13, 13, 1, -12}, // 0x4B 'K'
{548, 8, 13, 11, 1, -12}, // 0x4C 'L'
{561, 14, 13, 16, 1, -12}, // 0x4D 'M'
{584, 11, 13, 14, 1, -12}, // 0x4E 'N'
{602, 13, 13, 14, 1, -12}, // 0x4F 'O'
{624, 11, 13, 12, 1, -12}, // 0x50 'P'
{642, 13, 14, 14, 1, -12}, // 0x51 'Q'
{665, 12, 13, 13, 1, -12}, // 0x52 'R'
{685, 11, 13, 12, 1, -12}, // 0x53 'S'
{703, 9, 13, 12, 2, -12}, // 0x54 'T'
{718, 11, 13, 13, 1, -12}, // 0x55 'U'
{736, 12, 13, 12, 0, -12}, // 0x56 'V'
{756, 17, 13, 17, 0, -12}, // 0x57 'W'
{784, 12, 13, 12, 0, -12}, // 0x58 'X'
{804, 11, 13, 12, 1, -12}, // 0x59 'Y'
{822, 9, 13, 11, 1, -12}, // 0x5A 'Z'
{837, 4, 17, 6, 1, -12}, // 0x5B '['
{846, 5, 13, 5, 0, -12}, // 0x5C '\'
{855, 4, 17, 6, 0, -12}, // 0x5D ']'
{864, 8, 8, 11, 1, -12}, // 0x5E '^'
{872, 10, 1, 10, 0, 4}, // 0x5F '_'
{874, 3, 2, 5, 0, -12}, // 0x60 '`'
{875, 10, 10, 10, 1, -9}, // 0x61 'a'
{888, 10, 13, 11, 1, -12}, // 0x62 'b'
{905, 9, 10, 10, 1, -9}, // 0x63 'c'
{917, 9, 13, 11, 1, -12}, // 0x64 'd'
{932, 9, 10, 10, 1, -9}, // 0x65 'e'
{944, 5, 13, 6, 1, -12}, // 0x66 'f'
{953, 9, 14, 11, 1, -9}, // 0x67 'g'
{969, 9, 13, 11, 1, -12}, // 0x68 'h'
{984, 3, 13, 5, 1, -12}, // 0x69 'i'
{989, 4, 17, 5, 0, -12}, // 0x6A 'j'
{998, 9, 13, 10, 1, -12}, // 0x6B 'k'
{1013, 3, 13, 5, 1, -12}, // 0x6C 'l'
{1018, 14, 10, 16, 1, -9}, // 0x6D 'm'
{1036, 9, 10, 11, 1, -9}, // 0x6E 'n'
{1048, 10, 10, 11, 1, -9}, // 0x6F 'o'
{1061, 10, 14, 11, 1, -9}, // 0x70 'p'
{1079, 9, 14, 11, 1, -9}, // 0x71 'q'
{1095, 6, 10, 7, 1, -9}, // 0x72 'r'
{1103, 9, 10, 10, 1, -9}, // 0x73 's'
{1115, 5, 12, 6, 1, -11}, // 0x74 't'
{1123, 9, 10, 11, 1, -9}, // 0x75 'u'
{1135, 10, 10, 10, 0, -9}, // 0x76 'v'
{1148, 14, 10, 14, 0, -9}, // 0x77 'w'
{1166, 10, 10, 10, 0, -9}, // 0x78 'x'
{1179, 10, 14, 10, 0, -9}, // 0x79 'y'
{1197, 8, 10, 9, 1, -9}, // 0x7A 'z'
{1207, 4, 17, 7, 1, -12}, // 0x7B '{'
{1216, 1, 17, 5, 2, -12}, // 0x7C '|'
{1219, 4, 17, 7, 2, -12}, // 0x7D '}'
{1228, 8, 2, 9, 0, -4}}; // 0x7E '~'
const GFXfont FreeSansBold9pt7b PROGMEM = {(uint8_t *)FreeSansBold9pt7bBitmaps,
(GFXglyph *)FreeSansBold9pt7bGlyphs,
0x20, 0x7E, 22};
// Approx. 1902 bytes

View File

@@ -0,0 +1,475 @@
#ifndef EC200U_AUDIOCALL_H
#define EC200U_AUDIOCALL_H
#include <Arduino.h>
// -------------------- تعریف USART3 --------------------
HardwareSerial EC200U(USART3);
// -------------------- تنظیمات --------------------
const char* AUDIO_FILE_PATH = "UFS:output.amr";
String APN = "mcinet"; // اگر نیاز است تنظیم شود
// -------------------- تابع ارسال AT --------------------
String sendATCommand(String cmd, uint16_t timeout = 5000, bool showCmd = false, bool showResponse = false) {
if (showCmd) {
Serial1.print(">> ");
Serial1.println(cmd);
}
EC200U.println(cmd);
String response = "";
unsigned long start = millis();
bool gotOk = false;
bool gotError = false;
while (millis() - start < timeout) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
if (showResponse) Serial1.write(c);
}
if (response.indexOf("OK") != -1) gotOk = true;
if (response.indexOf("ERROR") != -1) gotError = true;
// برای دستورات خاص، منتظر نشانه‌های دیگر هم باشیم
if (response.indexOf("CONNECT") != -1) break;
if (response.indexOf("+QHTTPGET:") != -1) break;
if (response.indexOf("+QHTTPREADFILE:") != -1) break;
if (gotOk || gotError) break;
}
return response;
}
// -------------------- راه‌اندازی اینترنت --------------------
bool setupInternet() {
Serial1.println("\n🌐 راه‌اندازی اتصال اینترنت");
Serial1.println("==========================");
// 1. تنظیم APN (اگر نیاز است)
if (APN.length() > 0) {
Serial1.print("[1] تنظیم APN... ");
String cmd = "AT+QICSGP=1,1,\"" + APN + "\",\"\",\"\",1";
if (sendATCommand(cmd, 5000, false).indexOf("OK") == -1) {
Serial1.println("FAIL");
return false;
}
Serial1.println("OK");
}
// 2. فعال‌سازی زمینه PDP
Serial1.print("[2] فعال‌سازی PDP... ");
if (sendATCommand("AT+QIACT=1", 30000, false).indexOf("OK") == -1) {
Serial1.println("FAIL - سعی می‌کنیم از حالت فعلی استفاده کنیم");
} else {
Serial1.println("OK");
}
// 3. بررسی وضعیت اتصال
Serial1.print("[3] بررسی اتصال... ");
String resp = sendATCommand("AT+QIACT?", 2000, false);
if (resp.indexOf("+QIACT:") != -1) {
Serial1.println("CONNECTED");
return true;
} else {
Serial1.println("NO CONNECTION");
return false;
}
}
// -------------------- دانلود فایل صوتی از URL --------------------
// -------------------- دانلود فایل صوتی از URL --------------------
bool downloadAudioFile(String url) {
Serial1.println("\n📥 دانلود فایل صوتی");
Serial1.println("===================");
// 1. پاک کردن فایل قدیمی اگر وجود دارد
Serial1.print("[1] پاک کردن فایل قدیمی... ");
sendATCommand("AT+QFDEL=\"UFS:audio.amr\"", 2000, false);
Serial1.println("OK");
delay(100);
// 2. تنظیم URL
Serial1.print("[2] تنظیم URL... ");
int urlLen = url.length();
String cmd = "AT+QHTTPURL=" + String(urlLen) + ",80";
EC200U.println(cmd);
String response = "";
unsigned long start = millis();
bool gotConnect = false;
while (millis() - start < 5000) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
}
if (response.indexOf("CONNECT") != -1) {
gotConnect = true;
break;
}
if (response.indexOf("ERROR") != -1) {
Serial1.println("FAIL - خطای QHTTPURL");
return false;
}
}
if (!gotConnect) {
Serial1.println("FAIL - CONNECT دریافت نشد");
return false;
}
// ارسال URL
EC200U.print(url);
delay(1000);
response = "";
start = millis();
while (millis() - start < 3000) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
}
if (response.indexOf("OK") != -1) break;
}
if (response.indexOf("OK") == -1) {
Serial1.println("FAIL - URL پذیرفته نشد");
return false;
}
Serial1.println("OK");
// 3. دانلود فایل
Serial1.print("[3] دانلود فایل... ");
EC200U.println("AT+QHTTPGET=60"); // 60 ثانیه تایم‌اوت
response = "";
start = millis();
bool httpSuccess = false;
int httpCode = 0;
while (millis() - start < 65000) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
Serial1.write(c);
}
int qhttpIdx = response.indexOf("+QHTTPGET:");
if (qhttpIdx != -1) {
String afterQhttp = response.substring(qhttpIdx);
int firstComma = afterQhttp.indexOf(',');
int secondComma = afterQhttp.indexOf(',', firstComma + 1);
if (firstComma != -1 && secondComma != -1) {
int errorCode = afterQhttp.substring(11, firstComma).toInt();
httpCode = afterQhttp.substring(firstComma + 1, secondComma).toInt();
Serial1.print("کد خطا: ");
Serial1.print(errorCode);
Serial1.print(", کد HTTP: ");
Serial1.print(httpCode);
if (errorCode == 0 && httpCode >= 200 && httpCode < 300) {
httpSuccess = true;
}
break;
}
}
if (response.indexOf("ERROR") != -1) {
Serial1.println("\n❌ HTTP GET ناموفق");
return false;
}
}
if (!httpSuccess) {
Serial1.println("FAIL");
Serial1.print(" کد HTTP: ");
Serial1.println(httpCode);
return false;
}
Serial1.println("OK");
delay(500);
// پاک کردن بافر
while (EC200U.available()) EC200U.read();
// 4. ذخیره فایل
Serial1.print("[4] ذخیره فایل... ");
EC200U.println("AT+QHTTPREADFILE=\"UFS:audio.amr\",80");
response = "";
start = millis();
bool saveSuccess = false;
while (millis() - start < 60000) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
Serial1.write(c);
}
if (response.indexOf("+QHTTPREADFILE: 0") != -1) {
saveSuccess = true;
break;
}
if (response.indexOf("ERROR") != -1) {
break;
}
}
if (saveSuccess) {
Serial1.println("OK");
Serial1.println("✅ فایل صوتی دانلود و ذخیره شد");
return true;
} else {
Serial1.println("FAIL");
Serial1.println("❌ ذخیره فایل ناموفق");
return false;
}
}
// -------------------- بررسی وضعیت تماس --------------------
int getCallState() {
EC200U.println("AT+CLCC");
delay(300);
String response = "";
unsigned long start = millis();
while (millis() - start < 2000) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
}
}
if (response.indexOf("+CLCC: 0,0,0,0,0") != -1) return 0; // Active
if (response.indexOf("+CLCC: 0,0,2,0,0") != -1) return 2; // Dialing
if (response.indexOf("+CLCC: 0,0,3,0,0") != -1) return 3; // Ringing
if (response.indexOf("NO CARRIER") != -1) return -2; // Disconnected
if (response.indexOf("BUSY") != -1) return -3; // Busy
return -1; // Unknown
}
// -------------------- تابع اصلی تماس و پخش --------------------
bool callAndPlayAudio(String phoneNumber) {
Serial1.println("\n📞 تماس و پخش فایل صوتی");
Serial1.println("========================");
// 1. برقراری تماس
Serial1.print("[1] برقراری تماس... ");
String dialCmd = "ATD" + phoneNumber + ";";
String resp = sendATCommand(dialCmd, 10000, false, true);
if (resp.indexOf("OK") == -1 && resp.indexOf("ERROR") != -1) {
Serial1.println("FAIL");
return false;
}
Serial1.println("OK");
// 2. منتظر پاسخ
Serial1.print("[2] منتظر پاسخ تماس... ");
unsigned long start = millis();
bool callConnected = false;
while (millis() - start < 45000) { // 45 ثانیه منتظر بمان
int callState = getCallState();
switch (callState) {
case 0: // Active
callConnected = true;
Serial1.println("CONNECTED");
break;
case 2: // Dialing
Serial1.print(".");
break;
case 3: // Ringing
Serial1.print("R");
break;
case -2: // Disconnected
Serial1.println("DISCONNECTED");
return false;
case -3: // Busy
Serial1.println("BUSY");
return false;
}
if (callConnected) break;
delay(2000);
}
if (!callConnected) {
Serial1.println("NO ANSWER");
sendATCommand("ATH", 2000, false);
return false;
}
delay(1000); // کمی صبر بعد از اتصال
// 3. پخش فایل صوتی
Serial1.print("[3] شروع پخش صدا... ");
// ابتدا حالت پخش را تنظیم کن
resp = sendATCommand("AT+QVTCMD=1,0,\"UFS:output.amr\"", 5000, false, true);
if (resp.indexOf("OK") == -1) {
Serial1.println("FAIL - تنظیم پخش");
Serial1.println(" پاسخ: " + resp);
sendATCommand("ATH", 2000, false);
return false;
}
// شروع پخش
resp = sendATCommand("AT+QVTCMD=3,0", 3000, false, true);
if (resp.indexOf("OK") == -1) {
Serial1.println("FAIL - شروع پخش");
sendATCommand("ATH", 2000, false);
return false;
}
Serial1.println("OK");
// 4. منتظر اتمام پخش
Serial1.println("[4] در حال پخش... ");
bool playbackComplete = false;
start = millis();
while (millis() - start < 120000) { // حداکثر 2 دقیقه
// بررسی وضعیت پخش
EC200U.println("AT+QVTSTAT?");
delay(500);
String statusResp = "";
unsigned long statusStart = millis();
while (millis() - statusStart < 2000) {
while (EC200U.available()) {
char c = EC200U.read();
statusResp += c;
}
}
// اگر پخش تمام شده
if (statusResp.indexOf("+QVTSTAT: 0,0") != -1) {
playbackComplete = true;
break;
}
// بررسی وضعیت تماس
if (getCallState() != 0) {
Serial1.println(" تماس قطع شد");
break;
}
Serial1.print(".");
delay(1000);
}
if (playbackComplete) {
Serial1.println(" COMPLETE");
} else {
Serial1.println(" INCOMPLETE");
}
// 5. قطع تماس
Serial1.print("[5] پایان تماس... ");
resp = sendATCommand("ATH", 3000, false, true);
if (resp.indexOf("OK") != -1) {
Serial1.println("OK");
Serial1.println("✅ عملیات کامل شد");
return true;
} else {
Serial1.println("FAIL");
return false;
}
}
// -------------------- تابع اصلی یکپارچه --------------------
bool downloadAndCall(String phoneNumber, String audioUrl) {
Serial1.println("\n🎯 شروع عملیات کامل");
Serial1.println("====================");
Serial1.println("📞 شماره: " + phoneNumber);
Serial1.println("🔗 URL: " + audioUrl);
// 1. راه‌اندازی اینترنت
if (!setupInternet()) {
Serial1.println("❌ اتصال اینترنت ناموفق");
return false;
}
// 2. دانلود فایل صوتی
if (!downloadAudioFile(audioUrl)) {
Serial1.println("❌ دانلود فایل ناموفق");
return false;
}
delay(2000);
// 3. تماس و پخش
return callAndPlayAudio(phoneNumber);
}
// -------------------- تابع تست سریع --------------------
void testAudioCall() {
Serial1.println("\n🧪 تست سریع عملیات تماس صوتی");
Serial1.println("==============================");
// اطلاعات تست (تغییر دهید)
String testPhone = "09192530212";
String testUrl = "http://ghback.nabaksoft.ir/My_staticFiles/output.amr"; // URL فایل صوتی AMR
Serial1.println("⚠️ توجه: لطفاً اطلاعات زیر را تنظیم کنید:");
Serial1.println(" 1. شماره تلفن: " + testPhone);
Serial1.println(" 2. URL فایل صوتی: " + testUrl);
Serial1.println(" 3. APN (اگر نیاز است) در کد تنظیم شود");
// در اینجا می‌توانید تست را فعال کنید
// bool result = downloadAndCall(testPhone, testUrl);
Serial1.println("\n📋 دستورات تست دستی:");
Serial1.println(" 1. initModule() - راه‌اندازی ماژول");
Serial1.println(" 2. setupInternet() - اتصال اینترنت");
Serial1.println(" 3. downloadAudioFile(\"URL\") - دانلود فایل");
Serial1.println(" 4. callAndPlayAudio(\"PHONE\") - تماس و پخش");
}
// -------------------- راه‌اندازی اولیه ماژول --------------------
void initModule() {
Serial1.begin(115200);
EC200U.begin(115200);
delay(3000);
Serial1.println("\n🔧 راه‌اندازی ماژول EC200U");
Serial1.println("==========================");
// تست AT
Serial1.print("AT... ");
if (sendATCommand("AT", 2000, false).indexOf("OK") != -1) {
Serial1.println("OK");
} else {
Serial1.println("FAIL");
return;
}
// غیرفعال کردن اکو
sendATCommand("ATE0", 1000, false);
// تنظیمات صدا
sendATCommand("AT+CLVL=5", 1000, false); // سطح صدا
sendATCommand("AT+CMUT=0", 1000, false); // غیرفعال کردن mute
Serial1.println("✅ ماژول آماده است");
}
#endif // EC200U_AUDIOCALL_H

View File

@@ -0,0 +1,434 @@
#ifndef EC200U_FINAL_H
#define EC200U_FINAL_H
#include <Arduino.h>
// -------------------- تعریف USART3 --------------------
HardwareSerial EC200U(USART3);
// -------------------- تابع ارسال AT --------------------
bool sendAT(String cmd, uint16_t timeout = 2000, bool showResponse = false) {
EC200U.println(cmd);
String response = "";
unsigned long start = millis();
while (millis() - start < timeout) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
if (showResponse) Serial1.write(c);
}
if (response.indexOf("OK") != -1) return true;
if (response.indexOf("ERROR") != -1) return false;
}
return false;
}
// -------------------- تابع ارسال AT با دریافت پاسخ --------------------
String sendATWithResponse(String cmd, uint16_t timeout = 2000) {
if (cmd.length() > 0) {
EC200U.println(cmd);
}
String response = "";
unsigned long start = millis();
while (millis() - start < timeout) {
while (EC200U.available()) {
char c = EC200U.read();
response += c;
}
}
return response;
}
// -------------------- منتظر ماندن برای پرامپت > --------------------
bool waitForPrompt(uint16_t timeout = 5000) {
unsigned long startTime = millis();
while (millis() - startTime < timeout) {
while (EC200U.available()) {
if (EC200U.read() == '>') {
return true;
}
}
}
return false;
}
// -------------------- تابع اصلی ارسال SMS (نسخه بهینه) --------------------
bool sendSMS(String phoneNumber, String message) {
Serial1.println("\n📱 ارسال پیامک");
Serial1.println("==============");
// 1. تنظیم Text Mode
if (!sendAT("AT+CMGF=1", 2000)) {
Serial1.println("❌ خطا: Text Mode تنظیم نشد");
return false;
}
// 2. تنظیم charset به UTF-8
if (!sendAT("AT+CSCS=\"UTF-8\"", 2000)) {
Serial1.println("❌ خطا: UTF-8 پشتیبانی نمی‌شود");
Serial1.println("تلاش با UCS2...");
if (!sendAT("AT+CSCS=\"UCS2\"", 2000)) {
return false;
}
}
// 3. تنظیم پارامترهای SMS با مقدار موفق (17,167,0,8)
if (!sendAT("AT+CSMP=17,167,0,8", 2000)) {
Serial1.println("⚠️ هشدار: تنظیم CSMP ناموفق، ادامه می‌دهیم...");
}
// 4. ارسال دستور CMGS
EC200U.print("AT+CMGS=\"");
EC200U.print(phoneNumber);
EC200U.println("\"");
// 5. منتظر prompt >
if (!waitForPrompt(5000)) {
Serial1.println("❌ خطا: Prompt دریافت نشد");
return false;
}
// 6. ارسال متن (بدون println که خط جدید اضافه کند)
EC200U.print(message);
// 7. ارسال Ctrl+Z (بدون تاخیر اضافی)
EC200U.write(26);
// 8. دریافت پاسخ با تایم‌اوت کوتاه‌تر
unsigned long startTime = millis();
String response = "";
bool success = false;
while (millis() - startTime < 10000) { // 10 ثانیه کافی است
while (EC200U.available()) {
char c = EC200U.read();
response += c;
// بررسی سریع‌تر برای موفقیت
if (response.indexOf("+CMGS:") != -1) {
success = true;
// ادامه می‌دهیم تا OK هم دریافت شود
}
if (response.indexOf("OK") != -1 && success) {
Serial1.println("\n✅ پیامک با موفقیت ارسال شد");
return true;
}
if (response.indexOf("ERROR") != -1 || response.indexOf("CMS ERROR") != -1) {
Serial1.println("\n❌ خطا در ارسال");
return false;
}
}
}
if (success) {
Serial1.println("\n✅ پیامک ارسال شد (بدون دریافت OK)");
return true;
}
Serial1.println("\n❌ ارسال ناموفق - زمان تمام شد");
return false;
}
// -------------------- نسخه بهینه شده با حذف خط خالی --------------------
bool sendSMS_Optimized(String phoneNumber, String message) {
Serial1.println("\n📱 ارسال پیامک (نسخه بهینه)");
Serial1.println("========================");
// یکجا تنظیمات را ارسال می‌کنیم
EC200U.println("AT+CMGF=1");
delay(100);
EC200U.println("AT+CSCS=\"UTF-8\"");
delay(100);
EC200U.println("AT+CSMP=17,167,0,8");
delay(100);
// پاک کردن بافر
while (EC200U.available()) EC200U.read();
// ارسال دستور اصلی
EC200U.print("AT+CMGS=\"");
EC200U.print(phoneNumber);
EC200U.println("\"");
// منتظر پرامپت
unsigned long startTime = millis();
bool gotPrompt = false;
while (millis() - startTime < 5000) {
if (EC200U.available()) {
if (EC200U.read() == '>') {
gotPrompt = true;
break;
}
}
}
if (!gotPrompt) {
Serial1.println("❌ خطا: Prompt دریافت نشد");
return false;
}
// ارسال متن و Ctrl+Z بدون فاصله اضافی
EC200U.print(message);
delay(10); // تاخیر بسیار کم
EC200U.write(26);
// دریافت پاسخ
startTime = millis();
String response = "";
while (millis() - startTime < 8000) {
if (EC200U.available()) {
char c = EC200U.read();
Serial1.write(c); // نمایش لحظه‌ای پاسخ
if (response.indexOf("+CMGS:") != -1 && response.indexOf("OK") != -1) {
Serial1.println("\n✅ ارسال موفق");
return true;
}
if (response.indexOf("ERROR") != -1) {
Serial1.println("\n❌ ارسال ناموفق");
return false;
}
}
}
Serial1.println("\n⚠️ نتیجه نامشخص، اما احتمالاً ارسال شده");
return true;
}
// -------------------- نسخه نهایی و ساده --------------------
bool sendSMS_Final(String phoneNumber, String message) {
// پاکسازی بافر
while (EC200U.available()) EC200U.read();
// تنظیمات سریع
EC200U.println("AT+CMGF=1");
delay(50);
EC200U.println("AT+CSMP=17,167,0,8");
delay(50);
EC200U.println("AT+CSCS=\"UTF-8\"");
delay(50);
// پاکسازی مجدد
while (EC200U.available()) EC200U.read();
// ارسال دستور
EC200U.print("AT+CMGS=\"");
EC200U.print(phoneNumber);
EC200U.println("\"");
// انتظار برای پرامپت
int timeout = 0;
while (timeout < 100) {
if (EC200U.available() && EC200U.read() == '>') break;
delay(50);
timeout++;
}
if (timeout >= 100) {
Serial1.println("❌ خطا: Prompt دریافت نشد");
return false;
}
// ارسال متن (بدون خط جدید)
for (int i = 0; i < message.length(); i++) {
EC200U.write(message.charAt(i));
}
// ارسال Ctrl+Z
EC200U.write(26);
// بررسی سریع نتیجه
timeout = 0;
while (timeout < 50) {
if (EC200U.available()) {
String resp = EC200U.readString();
if (resp.indexOf("+CMGS:") != -1 || resp.indexOf("OK") != -1) {
Serial1.println("✅ ارسال موفق");
return true;
}
}
delay(100);
timeout++;
}
Serial1.println("✅ ارسال انجام شد (نتیجه نامشخص)");
return true;
}
// -------------------- راه‌اندازی ماژول --------------------
void initEC200U(int baud = 115200) {
Serial1.begin(115200);
EC200U.begin(baud);
delay(2000);
Serial1.println("\n🔧 راه‌اندازی ماژول EC200U");
Serial1.println("==========================");
// بررسی ارتباط
for (int i = 0; i < 3; i++) {
EC200U.println("AT");
delay(100);
if (EC200U.find("OK")) {
Serial1.println("✅ ماژول پاسخ می‌دهد");
break;
}
delay(500);
}
// غیرفعال کردن اکو
EC200U.println("ATE0");
delay(100);
// فعال کردن گزارش خطا
EC200U.println("AT+CMEE=1");
delay(100);
// بررسی سیم کارت
EC200U.println("AT+CPIN?");
delay(100);
Serial1.println("✅ آماده به کار\n");
}
#endif // EC200U_FINAL_H
// #ifndef EC200U_FINAL_H
// #define EC200U_FINAL_H
// #include <Arduino.h>
// // -------------------- تعریف USART3 --------------------
// HardwareSerial EC200U(USART3);
// // -------------------- تابع ارسال AT --------------------
// bool sendAT(String cmd, uint16_t timeout = 2000, bool showResponse = false) {
// EC200U.println(cmd);
// String response = "";
// unsigned long start = millis();
// while (millis() - start < timeout) {
// while (EC200U.available()) {
// char c = EC200U.read();
// response += c;
// if (showResponse) Serial1.write(c);
// }
// if (response.indexOf("OK") != -1) return true;
// if (response.indexOf("ERROR") != -1) return false;
// }
// return false;
// }
// // -------------------- تابع اصلی ارسال SMS --------------------
// bool sendSMS(String phoneNumber, String message) {
// Serial1.println("\n📱 ارسال پیامک");
// Serial1.println("==============");
// // 1. تنظیم Text Mode
// if (!sendAT("AT+CMGF=1")) {
// Serial1.println("❌ خطا: Text Mode تنظیم نشد");
// return false;
// }
// // 2. تنظیم charset به UTF-8
// if (!sendAT("AT+CSCS=\"UTF-8\"")) {
// Serial1.println("❌ خطا: UTF-8 پشتیبانی نمی‌شود");
// return false;
// }
// // 3. تنظیم پارامترهای SMS (اختیاری)
// sendAT("AT+CSMP=1,167,0,8");
// // 4. ارسال دستور CMGS
// String cmd = "AT+CMGS=\"" + phoneNumber + "\"";
// EC200U.println(cmd);
// // منتظر prompt >
// delay(100);
// unsigned long startTime = millis();
// bool gotPrompt = false;
// while (millis() - startTime < 5000) {
// while (EC200U.available()) {
// if (EC200U.read() == '>') {
// gotPrompt = true;
// break;
// }
// }
// if (gotPrompt) break;
// }
// if (!gotPrompt) {
// Serial1.println("❌ خطا: Prompt دریافت نشد");
// return false;
// }
// // 5. ارسال متن UTF-8
// EC200U.println(message);
// delay(100);
// // 6. ارسال Ctrl+Z
// EC200U.write(26);
// // 7. دریافت پاسخ
// startTime = millis();
// String response = "";
// bool success = false;
// while (millis() - startTime < 15000) {
// while (EC200U.available()) {
// char c = EC200U.read();
// response += c;
// if (response.indexOf("+CMGS:") != -1) success = true;
// if (response.indexOf("+CMS ERROR") != -1) return false;
// }
// if (response.indexOf("OK") != -1) break;
// if (response.indexOf("ERROR") != -1) break;
// }
// if (success) {
// Serial1.println("✅ پیامک ارسال شد");
// return true;
// } else {
// Serial1.println("❌ ارسال ناموفق");
// return false;
// }
// }
// // -------------------- راه‌اندازی ماژول --------------------
// void initEC200U(int baud = 115200) {
// Serial1.begin(115200);
// EC200U.begin(baud);
// delay(2000);
// Serial1.println("\n🔧 راه‌اندازی ماژول EC200U");
// Serial1.println("==========================");
// if (!sendAT("AT")) {
// Serial1.println("❌ ماژول پاسخ نمی‌دهد");
// return;
// }
// sendAT("ATE0");
// if (sendAT("AT+CPIN?")) {
// Serial1.println("✅ SIM آماده");
// }
// Serial1.println("✅ راه‌اندازی کامل\n");
// }
// #endif // EC200U_FINAL_H

View File

@@ -0,0 +1,72 @@
#include "EC200U-sms.h"
// void setup() {
// راه‌اندازی اولیه ماژول
// initModule();
// تنظیم APN (اگر نیاز است)
// APN = "mtnirancell"; // مثال: برای همراه اول
// APN = "mci"; // مثال: برای ایرانسل
// delay(2000);
// اجرای عملیات کامل
// bool success = downloadAndCall(
// "09192530212", // شماره تلفن مقصد
// "https://ghback.nabaksoft.ir/My_staticFiles/output.amr" // URL فایل صوتی AMR
// );
// if (success) {
// Serial1.println("\n🎉 عملیات با موفقیت انجام شد!");
// } else {
// Serial1.println("\n❌ عملیات ناموفق بود");
// }
// }
// void loop() {
// می‌توانید کدهای تکرارشونده را اینجا قرار دهید
// delay(1000);
// }
// #include "EC200U-Call.h"
void setup() {
// شماره تلفن مقصد را اینجا تنظیم کن
//String TARGET_PHONE = "09192530212"; // شماره خودت را اینجا قرار بده
// راه‌اندازی ماژول
initEC200U(115200);
// تاخیر برای اتصال شبکه
delay(5000);
Serial1.println("شروع");
sendSMS("09192530212", "سلام1");
Serial1.println("----------");
delay(2000);
//sendSMS_Optimized("09192530212", "سلام2");
//delay(5000);
Serial1.println("----------");
sendSMS_Final("09192530212", "سلام3");
Serial1.println("----------");
delay(2000);
Serial1.println("----------");
sendSMS("09212087553", "سلام\nاین یک پیام فارسی است.");
Serial1.println("----------");
//delay(5000);
//sendSMS("09192530212", "سلام\nاین یک پیام فارسی است.");
//delay(5000);
// sendSMS("09354600904", "سلام\nاین یک پیام فارسی است.");
}
void loop() {
}
// // می‌توانید در شرایط خاص پیامک ارسال کنید
// // مثال:
// // static unsigned long lastSend = 0;
// // if (millis() - lastSend > 300000) { // هر 5 دقیقه
// // sendPersianSMS("گزارش سیستم: فعال\nزمان: " + String(millis()/1000) + " ثانیه");
// // lastSend = millis();
// // }
// delay(1000);
// }

View File

@@ -0,0 +1,148 @@
#ifndef VOLTAGE_READER_H
#define VOLTAGE_READER_H
#include <Arduino.h>
#define VOLTAGE_DIVIDER_PIN PA0 // پین خواندن ولتاژ
#define BATTERY_VOLTAGE_PIN_A3 PA3 // پین جدید برای باتری
#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
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

View File

@@ -0,0 +1,127 @@
#include <Arduino.h>
#include "Voltage_Reader.h"
// ========== پارامترهای مدار (مقادیر دقیق اندازه‌گیری‌شده) ==========
#define VREF_ADC 3.3f // ولتاژ مرجع ADC (معمولاً 3.3V)
#define ADC_RESOLUTION 4095.0f // رزولوشن ADC (12 بیت)
#define R1 2190.0f // مقاومت سری اول (اهم) - اندازه‌گیری شده
#define R2 3270.0f // مقاومت به زمین (اهم) - اندازه‌گیری شده
#define RSERIES 98.0f // مقاومت سری ۱۰۰ اهم (تأثیر ناچیز، می‌توان صفر گذاشت)
//#define VCC_SENSOR 4.67f // ولتاژ تغذیه سنسور MQ7
#define RL_SENSOR 990.0f // مقاومت بار ماژول (معمولاً ۱۰kΩ)
#define ADC_PIN A2 // پین ADC (PA2 در STM32)
#define NUM_SAMPLES 10 // تعداد نمونه برای میانگین‌گیری
float VCC_SENSOR = 4.67f; // ولتاژ تغذیه سنسور MQ7
float MQ7_R0 = 21000.0; // مقدار R0 (پس از کالیبراسیون) تقریب اولیه ۲۰ کیلواهم
bool calibrationComplete = false;
unsigned long lastReadTime = 0;
const unsigned long readInterval = 3000;
// ================================================================
float readSensorVoltage() {
uint32_t sum = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
sum += analogRead(ADC_PIN);
delay(10);
}
float adcValue = sum / (float)NUM_SAMPLES;
float vAdc = (adcValue / ADC_RESOLUTION) * VREF_ADC;
// محاسبه ولتاژ خروجی سنسور با جبران تقسیم ولتاژ (R1 و R2)
float vOut = vAdc * (R1 + R2) / R2;
return vOut;
}
float calculateRs(float vOut) {
if (vOut <= 0.0f) return 0.0f;
return RL_SENSOR * (VCC_SENSOR / vOut - 1.0f);
}
void setup() {
Serial1.begin(115200); // راه‌اندازی Serial1 با نرخ ۱۱۵۲۰۰
analogReadResolution(12); // تنظیم ADC روی ۱۲ بیت
pinMode(ADC_PIN, INPUT);
delay(2000);
voltageReader.debugInfo();
VCC_SENSOR=voltageReader.readVoltage();
Serial1.println("MQ7 Calibration Mode");
Serial1.print("R1 = "); Serial1.print(R1); Serial1.println(" ohms");
Serial1.print("R2 = "); Serial1.print(R2); Serial1.println(" ohms");
Serial1.print("VCC_SENSOR = "); Serial1.print(VCC_SENSOR); Serial1.println(" V");
Serial1.print("RL_SENSOR = "); Serial1.print(RL_SENSOR); Serial1.println(" ohms");
Serial1.println("\nPlace sensor in clean air and wait 2 minutes to stabilize...");
delay(120000); // ۲ دقیقه زمان برای پایدار شدن سنسور
Serial1.println("Now measuring. Record Rs value as R0 when stable.\n");
}
void loop() {
VCC_SENSOR=voltageReader.readVoltage();
float vOut = readSensorVoltage();
float rs = calculateRs(vOut);
Serial1.print("Vout_sensor = "); Serial1.print(vOut, 4); Serial1.print(" V, Rs = ");
Serial1.print(rs, 1); Serial1.println(" ohms");
delay(5000);
}
// #include <Arduino.h>
// // ========== پارامترهای مدار (بر اساس آخرین اندازه‌گیری) ==========
// #define VREF_ADC 3.3f // ولتاژ مرجع ADC (معمولاً 3.3V)
// #define ADC_RESOLUTION 4095.0f // رزولوشن ADC (12 بیت)
// #define R1 2190.0f // مقاومت سری اول (اهم) - اندازه‌گیری شده
// #define R2 3270.0f // مقاومت به زمین (اهم) - اندازه‌گیری شده
// // مقاومت سری ۹۹ اهم تأثیر ناچیزی دارد، در محاسبات لحاظ نمی‌شود
// #define VCC_SENSOR 4.9f // ولتاژ تغذیه سنسور MQ7
// #define RL_SENSOR 839.0f // مقاومت بار ماژول (مقاومت بین خروجی آنالوگ و GND)
// #define R0 20000.0f // مقدار R0 از کالیبراسیون (بر حسب اهم) حدود ۲۰ کیلو اهم
// #define CO_A 1.9f // ضریب A در فرمول ppm = A * (Rs/R0)^B
// #define CO_B -0.6f // ضریب B (معمولاً منفی)
// #define ADC_PIN PA2 // پین ADC (PA2 در STM32)
// #define NUM_SAMPLES 10 // تعداد نمونه برای میانگین‌گیری
// // ================================================================
// float readSensorVoltage() {
// uint32_t sum = 0;
// for (int i = 0; i < NUM_SAMPLES; i++) {
// sum += analogRead(ADC_PIN);
// delay(10);
// }
// float adcValue = sum / (float)NUM_SAMPLES;
// float vAdc = (adcValue / ADC_RESOLUTION) * VREF_ADC;
// // بازگشت ولتاژ خروجی سنسور با جبران تقسیم ولتاژ (R1 و R2)
// return vAdc * (R1 + R2) / R2;
// }
// float calculateRs(float vOut) {
// if (vOut <= 0.0f) return 0.0f;
// return RL_SENSOR * (VCC_SENSOR / vOut - 1.0f);
// }
// float calculatePPM(float rs) {
// float ratio = rs / R0;
// if (ratio <= 0.0f) return 0.0f;
// return CO_A * pow(ratio, CO_B);
// }
// void setup() {
// Serial1.begin(115200);
// analogReadResolution(12);
// pinMode(ADC_PIN, INPUT);
// Serial1.println("MQ7 CO Measurement");
// Serial1.print("R0 = "); Serial1.print(R0); Serial1.println(" ohms");
// Serial1.println("Starting...\n");
// delay(3000);
// }
// void loop() {
// float vOut = readSensorVoltage();
// float rs = calculateRs(vOut);
// float ppm = calculatePPM(rs);
// Serial1.print("CO = "); Serial1.print(ppm, 1); Serial1.println(" ppm");
// delay(2000);
// }

57
14041130/mq/ppm/ppm.ino Normal file
View File

@@ -0,0 +1,57 @@
#include <Arduino.h>
// ========== پارامترهای مدار ==========
#define VREF_ADC 3.3f
#define ADC_RESOLUTION 4095.0f
#define R1 1448.0f
#define R2 1566.0f
#define VCC_SENSOR 4.67f
#define RL_SENSOR 10000.0f
#define R0 139160.0f // ← مقدار R0 بدست‌آمده از کالیبراسیون (بر حسب اهم)
#define CO_A 1.9f // ضریب A در فرمول ppm = A * (Rs/R0)^B
#define CO_B -0.6f // ضریب B (معمولاً منفی)
#define ADC_PIN A2
#define NUM_SAMPLES 10
// =====================================
float readSensorVoltage() {
uint32_t sum = 0;
for (int i = 0; i < NUM_SAMPLES; i++) {
sum += analogRead(ADC_PIN);
delay(10);
}
float adcValue = sum / (float)NUM_SAMPLES;
float vAdc = (adcValue / ADC_RESOLUTION) * VREF_ADC;
return vAdc * (R1 + R2) / R2;
}
float calculateRs(float vOut) {
if (vOut <= 0.0f) return 0.0f;
return RL_SENSOR * (VCC_SENSOR / vOut - 1.0f);
}
float calculatePPM(float rs) {
float ratio = rs / R0;
if (ratio <= 0.0f) return 0.0f;
return CO_A * pow(ratio, CO_B);
}
void setup() {
Serial1.begin(115200);
analogReadResolution(12);
pinMode(ADC_PIN, INPUT);
Serial1.println("MQ7 CO Measurement");
Serial1.print("R0 = "); Serial1.print(R0); Serial1.println(" ohms");
Serial1.println("Starting...\n");
delay(3000);
}
void loop() {
float vOut = readSensorVoltage();
float rs = calculateRs(vOut);
float ppm = calculatePPM(rs);
Serial1.print("CO = "); Serial1.print(ppm, 1); Serial1.println(" ppm");
delay(2000);
}

172
Config.h Normal file
View 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
View 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

1363
EC200U.h Normal file

File diff suppressed because it is too large Load Diff

429
Memory.h Normal file
View 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
View 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
View 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

View File

@@ -1,265 +1,196 @@
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_SHT31.h>
#include <Adafruit_GFX.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 <SPI.h>
// تعریف پین‌ها
#define ADC_PIN PA2 // یا A2 اگر تعریف شده
#define LED_PIN PC13
#define OLED_ADDR 0x3C
// -------------------- فایل‌های پروژه --------------------
#include "Config.h"
#include "Memory.h"
#include "Sensors.h"
#include "EC200U.h"
#include "Display.h"
#include "Voltage_Reader.h"
// پین‌های I2C
#define I2C_SDA PB9
#define I2C_SCL PB8
// آبجکت‌ها
Adafruit_SHT31 sht31;
// -------------------- متغیرهای سراسری --------------------
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
BH1750 lightMeter;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
Adafruit_SHT31 sht31;
HardwareSerial EC200U(USART3);
SPIClass flashSPI(FLASH_MOSI, FLASH_MISO, FLASH_SCK);
// متغیرها
float temperature, humidity, light;
int raw_adc = 0;
float voltage = 0;
float co_ppm = 0;
DeviceConfig config;
SensorData currentData;
// تابع دیباگ برای تست ADC
void testADC() {
Serial1.print("ADC Pin: PA2 (Pin ");
Serial1.print(ADC_PIN);
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);
}
}
// متغیرهای موقت برای پردازش پیامک‌ها
String tempPhoneNumber = "";
String tempTokenCode = "";
bool awaitingSMS2 = false;
// تابع برای بررسی اتصال MQ-7
void checkMQ7Connection() {
Serial1.println("\n=== Testing MQ-7 Connection ===");
// حالت 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 ===");
}
unsigned long lastSensorRead = 0;
unsigned long lastUpload = 0;
unsigned long lastDisplayChange = 0;
unsigned long lastNetworkStatusUpdate = 0;
float readCO() {
// خواندن مستقیم ADC
raw_adc = analogRead(ADC_PIN);
voltage = raw_adc * 3.3 / 4095.0;
// اگر ولتاژ خیلی کم یا زیاد باشد
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 networkConnected = false;
int displayMode = 0;
String lastError = "";
String currentAPN = "";
String lastMessage = "";
// وضعیت سنسورها و اتصالات
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() {
Serial1.begin(115200);
Serial1.println("\n=== STM32 4-Sensor Monitor ===");
// تنظیم پین‌ها
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH); // روشن برای نشان دادن فعالیت
pinMode(PWRKEY_PIN, OUTPUT);
digitalWrite(PWRKEY_PIN, LOW);
pinMode(POWER_PIN, INPUT);
powerState = digitalRead(POWER_PIN) == HIGH;
delay(1000);
// تنظیم ADC
analogReadResolution(12); // برای STM32 مهم است!
Serial1.println("\n\n🚀 IoT Device Starting...");
// تنظیم I2C
Wire.setSDA(I2C_SDA);
Wire.setSCL(I2C_SCL);
systemStartTime = millis();
pinMode(MQ7_PIN, INPUT);
Wire.setSDA(SDA_PIN);
Wire.setSCL(SCL_PIN);
Wire.begin();
// OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial1.println("OLED not found!");
while(1);
// 1. ولتاژ را تست کن
bool voltageOK = voltageReader.testCircuit();
if (!voltageOK) {
Serial1.println("⚠️ Voltage issue detected!");
}
// SHT31
if (!sht31.begin(0x44)) {
Serial1.println("SHT31 not found!");
// 2. اطلاعات دیباگ ولتاژ
voltageReader.debugInfo();
// 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
if (!lightMeter.begin()) {
Serial1.println("BH1750 not found!");
sht31.begin(0x44);
lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
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 {
lightMeter.configure(BH1750::CONTINUOUS_HIGH_RES_MODE);
Serial1.println("⚠️ Not configured - Waiting for SMS");
}
// تست اولیه ADC
testADC();
readSensors();
// نمایش صفحه شروع
display.clearDisplay();
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();
updateNetworkStatus();
lastNetworkStatusUpdate = millis();
Serial1.println("\nSend 't' to test MQ-7 connection");
Serial1.println("Send 'r' to read normal values");
Serial1.println("✅ Ready - CO calibration: " + String(calibrationComplete ? "Complete" : "In progress"));
if (config.verified && networkConnected) {
Serial1.println("\n🚀 First upload - sending data immediately...");
uploadData();
}
}
// -------------------- Loop --------------------
void loop() {
// بررسی دستورات سریال
if (Serial1.available()) {
char cmd = Serial1.read();
if (!isInCall) {
digitalWrite(PWRKEY_PIN, LOW);
}
bool currentPower = digitalRead(POWER_PIN) == HIGH;
if (currentPower != powerState) {
if (cmd == 't' || cmd == 'T') {
checkMQ7Connection();
powerState = currentPower;
if (powerState) {
Serial.println("5V Connected");
} else {
Serial.println("5V Disconnected");
}
if (cmd == 'r' || cmd == 'R') {
// خواندن حالت عادی
temperature = sht31.readTemperature();
humidity = sht31.readHumidity();
light = lightMeter.readLightLevel();
co_ppm = readCO();
Serial1.print("\nTemp: ");
Serial1.print(temperature, 1);
Serial1.print("C, Hum: ");
Serial1.print(humidity, 1);
Serial1.print("%, Light: ");
Serial1.print(light, 0);
Serial1.print("lx, CO: ");
Serial1.print(co_ppm, 1);
Serial1.print("ppm, ADC: ");
Serial1.print(raw_adc);
Serial1.print(" (");
Serial1.print(voltage, 3);
Serial1.println("V)");
lastUpload -= 86400000L;
}
checkSMS();
if (!calibrationComplete && (millis() - systemStartTime >= calibrationPeriod)) {
calibrationComplete = true;
Serial1.println("✅ CO calibration period completed!");
}
if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) {
readSensors();
float voltage = voltageReader.readVoltage();
Serial1.print("📊 System Voltage: ");
Serial1.print(voltage, 2);
Serial1.println("V");
currentData.volage=voltage;
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();
}
}
// خواندن و نمایش معمول
temperature = sht31.readTemperature();
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);
}
updateDisplay();
delay(100);
}

142
Voltage_Reader.h Normal file
View 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

View File

@@ -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*)&currentData, 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);
}

View File

@@ -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; // 50140ppm
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);
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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---------------------------------
}

View File

@@ -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);
// }

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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);
}