diff --git a/14041130/I2C-best/I2C-best.ino b/14041130/I2C-best/I2C-best.ino new file mode 100644 index 0000000..255d8bb --- /dev/null +++ b/14041130/I2C-best/I2C-best.ino @@ -0,0 +1,433 @@ +#include + +// تعریف پین‌های 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); +} \ No newline at end of file diff --git a/14041130/MQ7-1/MQ7-1.ino b/14041130/MQ7-1/MQ7-1.ino new file mode 100644 index 0000000..969d8c7 --- /dev/null +++ b/14041130/MQ7-1/MQ7-1.ino @@ -0,0 +1,197 @@ +#include +#include +#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); +} \ No newline at end of file diff --git a/14041130/MQ7-1/Voltage_Reader.h b/14041130/MQ7-1/Voltage_Reader.h new file mode 100644 index 0000000..22d0797 --- /dev/null +++ b/14041130/MQ7-1/Voltage_Reader.h @@ -0,0 +1,148 @@ +#ifndef VOLTAGE_READER_H +#define VOLTAGE_READER_H + +#include + +#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 \ No newline at end of file diff --git a/14041130/TestLCD/BatteryReader.h b/14041130/TestLCD/BatteryReader.h new file mode 100644 index 0000000..7cc151b --- /dev/null +++ b/14041130/TestLCD/BatteryReader.h @@ -0,0 +1,98 @@ +// BatteryReader.h +#ifndef BATTERY_READER_H +#define BATTERY_READER_H + +#include +#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 \ No newline at end of file diff --git a/14041130/TestLCD/Config.h b/14041130/TestLCD/Config.h new file mode 100644 index 0000000..8e642f8 --- /dev/null +++ b/14041130/TestLCD/Config.h @@ -0,0 +1,157 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +#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 \ No newline at end of file diff --git a/14041130/TestLCD/Display - Copy__old b/14041130/TestLCD/Display - Copy__old new file mode 100644 index 0000000..1662126 --- /dev/null +++ b/14041130/TestLCD/Display - Copy__old @@ -0,0 +1,342 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include +#include +#include +#include +#include +#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 + diff --git a/14041130/TestLCD/Display.h b/14041130/TestLCD/Display.h new file mode 100644 index 0000000..1f3ab03 --- /dev/null +++ b/14041130/TestLCD/Display.h @@ -0,0 +1,503 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include +#include +#include "Config.h" + +// ==================== فونت‌ها ==================== +#include +#include +#include + +// ==================== تنظیمات 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 \ No newline at end of file diff --git a/14041130/TestLCD/EC200U-old.aaaaa b/14041130/TestLCD/EC200U-old.aaaaa new file mode 100644 index 0000000..f8111aa --- /dev/null +++ b/14041130/TestLCD/EC200U-old.aaaaa @@ -0,0 +1,1612 @@ +#ifndef EC200U_H +#define EC200U_H + +#include +#include "Config.h" +#include "Memory.h" + +// -------------------- متغیرهای خارجی -------------------- +extern HardwareSerial EC200U; +extern DeviceConfig config; +extern SensorData currentData; +extern bool networkConnected; +extern bool simConnected; +extern bool networkRegistered; +extern int signalStrength; +extern String lastMessage; +extern String currentAPN; +extern String tempPhoneNumber; +extern String tempTokenCode; +extern bool awaitingSMS2; +extern bool isInCall; +extern bool calibrationComplete; +extern unsigned long lastUpload; + + + +// Forward declarations +void initEC200U(); +void updateNetworkStatus(); +// -------------------- توابع پایه EC200U -------------------- +inline bool sendAT(String cmd, uint16_t wait = 2000, bool showResponse = false) { + if (showResponse) { + 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; + if (showResponse) Serial1.write(c); + } + if (response.indexOf("OK") != -1) return true; + if (response.indexOf("ERROR") != -1) return false; + } + return false; +} + +// جدول تبدیل کاراکترهای فارسی به UCS2 (بهینه شده) +inline String convertFarsiToUCS2(String text) { + String ucs2 = ""; + for (int i = 0; i < text.length(); i++) { + unsigned char c = text.charAt(i); + + // تشخیص کاراکترهای فارسی UTF-8 (دو بایتی) + if (c >= 0xD8 && c <= 0xDF && i + 1 < text.length()) { + unsigned char c2 = text.charAt(i + 1); + + // محاسبه کد Unicode از UTF-8 + uint16_t unicode = ((c - 0xD8) << 6) + (c2 - 0x80) + 0x0600; + + // تبدیل به HEX + char hex[5]; + sprintf(hex, "%04X", unicode); + ucs2 += hex; + i++; // یک کاراکتر اضافه مصرف شد + } + // کاراکترهای ASCII + else if (c < 0x80) { + char hex[5]; + sprintf(hex, "%04X", c); + ucs2 += hex; + } + // سایر کاراکترها + else { + char hex[5]; + sprintf(hex, "%04X", c); + ucs2 += hex; + } + } + return ucs2; +} + +// تبدیل شماره به UCS2 (اعداد) +inline String convertNumberToUCS2(String number) { + String ucs2 = ""; + for (int i = 0; i < number.length(); i++) { + char c = number.charAt(i); + if (c == '+') { + ucs2 += "002B"; + } else if (c >= '0' && c <= '9') { + char hex[5]; + sprintf(hex, "%04X", c); + ucs2 += hex; + } + } + return ucs2; +} + +// تابع اصلی ارسال SMS فارسی +// تابع اصلاح شده برای ارسال SMS فارسی +inline bool sendSMS(String numbers, String messages) { + Serial1.println("\n📱 SENDING SMS - PDU MODE"); + + // تقسیم شماره‌ها + int phoneCount = 0; + String phoneNumbers[10]; + String numbersCopy = numbers; + + numbersCopy.replace(" ", ""); // حذف فاصله‌ها + + int startIdx = 0; + int commaIdx = numbersCopy.indexOf(','); + + while (commaIdx != -1 && phoneCount < 9) { + phoneNumbers[phoneCount] = numbersCopy.substring(startIdx, commaIdx); + phoneCount++; + startIdx = commaIdx + 1; + commaIdx = numbersCopy.indexOf(',', startIdx); + } + + if (startIdx < numbersCopy.length()) { + phoneNumbers[phoneCount] = numbersCopy.substring(startIdx); + phoneCount++; + } + + if (phoneCount == 0) { + Serial1.println("❌ No phone numbers"); + return false; + } + + // تقسیم پیام‌ها + int messageCount = 0; + String messageTexts[10]; + String messagesCopy = messages; + + startIdx = 0; + int atIdx = messagesCopy.indexOf('@'); + + while (atIdx != -1 && messageCount < 9) { + messageTexts[messageCount] = messagesCopy.substring(startIdx, atIdx); + messageCount++; + startIdx = atIdx + 1; + atIdx = messagesCopy.indexOf('@', startIdx); + } + + if (startIdx < messagesCopy.length()) { + messageTexts[messageCount] = messagesCopy.substring(startIdx); + messageCount++; + } + + if (messageCount == 0) { + Serial1.println("❌ No messages"); + return false; + } + + bool allSent = true; + int totalMessagesSent = 0; + + for (int i = 0; i < phoneCount; i++) { + String phone = phoneNumbers[i]; + if (phone.length() < 10) { + Serial1.print("❌ Invalid number: "); + Serial1.println(phone); + allSent = false; + continue; + } + + // انتخاب پیام + String message; + if (messageCount == 1) { + message = messageTexts[0]; + } else if (i < messageCount) { + message = messageTexts[i]; + } else { + message = messageTexts[messageCount - 1]; + } + + if (message.length() == 0) { + Serial1.print("❌ Empty message for: "); + Serial1.println(phone); + allSent = false; + continue; + } + + Serial1.print("To: "); + Serial1.print(phone); + Serial1.print(" | Msg: "); + if (message.length() > 30) { + Serial1.print(message.substring(0, 30)); + Serial1.print("..."); + } else { + Serial1.print(message); + } + Serial1.println(); + + // --- روش ۱: ابتدا با GSM معمولی امتحان کن --- + sendAT("AT+CMGF=1", 2000, false); // Text mode + sendAT("AT+CSCS=\"GSM\"", 2000, false); + sendAT("AT+CSMP=17,167,0,0", 2000, false); + + EC200U.print("AT+CMGS=\""); + EC200U.print(phone); + EC200U.println("\""); + + delay(100); + + String response = ""; + unsigned long start = millis(); + bool gotPrompt = false; + + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (response.indexOf(">") != -1) { + gotPrompt = true; + break; + } + } + if (gotPrompt) break; + } + + if (!gotPrompt) { + Serial1.println("❌ No prompt"); + allSent = false; + continue; + } + + // ارسال پیام + EC200U.print(message); + EC200U.write(26); + + response = ""; + start = millis(); + bool gotResponse = false; + bool hasError = false; + + while (millis() - start < 10000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (response.indexOf("+CMGS:") != -1) { + gotResponse = true; + totalMessagesSent++; + break; + } + if (response.indexOf("ERROR") != -1 || response.indexOf("CMS ERROR") != -1) { + hasError = true; + break; + } + } + if (gotResponse || hasError) break; + } + + if (gotResponse) { + Serial1.println("✅ Sent with GSM encoding"); + continue; // موفق بود، برو به شماره بعدی + } + + if (hasError) { + Serial1.println("⚠️ GSM failed, trying 8-bit encoding..."); + + // --- روش ۲: 8-bit encoding --- + sendAT("AT+CMGF=1", 2000, false); + sendAT("AT+CSMP=17,167,0,8", 2000, false); // 8-bit data coding + + EC200U.print("AT+CMGS=\""); + EC200U.print(phone); + EC200U.println("\""); + + delay(100); + + response = ""; + start = millis(); + gotPrompt = false; + + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (response.indexOf(">") != -1) { + gotPrompt = true; + break; + } + } + if (gotPrompt) break; + } + + if (!gotPrompt) { + Serial1.println("❌ No prompt for 8-bit"); + allSent = false; + continue; + } + + EC200U.print(message); + EC200U.write(26); + + response = ""; + start = millis(); + gotResponse = false; + + while (millis() - start < 10000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (response.indexOf("+CMGS:") != -1) { + gotResponse = true; + totalMessagesSent++; + break; + } + if (response.indexOf("ERROR") != -1) { + break; + } + } + if (gotResponse) break; + } + + if (gotResponse) { + Serial1.println("✅ Sent with 8-bit encoding"); + } else { + Serial1.println("❌ 8-bit also failed"); + allSent = false; + } + } + + // تاخیر بین ارسال‌ها + if (i < phoneCount - 1) { + delay(3000); + } + } + + Serial1.print("📊 Result: "); + Serial1.print(totalMessagesSent); + Serial1.print("/"); + Serial1.print(phoneCount); + Serial1.println(" messages sent"); + + // بازگشت به تنظیمات عادی + sendAT("AT+CSMP=17,167,0,0", 2000, false); + + return (totalMessagesSent > 0); +} + +// -------------------- پردازش SMS -------------------- +inline void extractNumbersFromSMS2(String message, String numbers[], int &count) { + count = 0; + String current = ""; + + for (int i = 0; i < message.length(); i++) { + char c = message.charAt(i); + if (c >= '0' && c <= '9') { + current += c; + } else { + if (current.length() > 0) { + if (count < 10) { + numbers[count] = current; + count++; + } + current = ""; + } + } + } + + if (current.length() > 0 && count < 10) { + numbers[count] = current; + count++; + } +} +inline void updateNetworkStatus() { + if (!config.verified) { + simConnected = false; + networkRegistered = false; + signalStrength = 0; + return; + } + + simConnected = sendAT("AT", 1000, false); + + EC200U.println("AT+CSQ"); + String response = ""; + unsigned long start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1 || response.indexOf("ERROR") != -1) break; + } + + if (response.indexOf("+CSQ:") != -1) { + int idx = response.indexOf("+CSQ:"); + int comma = response.indexOf(',', idx); + if (comma != -1) { + String rssiStr = response.substring(idx + 6, comma); + rssiStr.trim(); + signalStrength = rssiStr.toInt(); + if (signalStrength > 31) signalStrength = 0; + } + } + + EC200U.println("AT+CREG?"); + response = ""; + start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1 || response.indexOf("ERROR") != -1) break; + } + + networkRegistered = (response.indexOf("+CREG:") != -1 && + (response.indexOf(",1,") != -1 || response.indexOf(",5,") != -1)); +} +inline int detectSMSType(String message) { + int count = 0; + String current = ""; + + for (int i = 0; i < message.length(); i++) { + char c = message.charAt(i); + if (c >= '0' && c <= '9') { + current += c; + } else { + if (current.length() > 0) { + count++; + current = ""; + } + } + } + if (current.length() > 0) { + count++; + } + + return (count >= 5) ? 2 : 1; +} +inline void connectToNetwork() { + Serial1.println("\n======== NETWORK CONNECTION ========"); + lastMessage = "Connecting"; + + if (!config.verified) { + Serial1.println("❌ Cannot connect: Device not configured"); + return; + } + + currentAPN = simTypeToAPN(config.simType); + Serial1.print("Setting APN: "); + Serial1.println(currentAPN); + String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + currentAPN + "\""; + sendAT(apnCmd, 2000, false); + + Serial1.println("Waiting for network registration..."); + bool registered = false; + for (int attempt = 1; attempt <= 30; attempt++) { + EC200U.println("AT+CREG?"); + String response = ""; + unsigned long start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1) break; + } + + if (response.indexOf(",1") != -1 || response.indexOf(",5") != -1) { + registered = true; + Serial1.print("✅ Network registered after "); + Serial1.print(attempt); + Serial1.println(" seconds"); + break; + } + + Serial1.print(" Attempt "); + Serial1.print(attempt); + Serial1.println("/30 - Searching..."); + delay(1000); + } + + if (!registered) { + Serial1.println("⚠️ Network registration timeout - continuing anyway"); + } + + Serial1.println("Deactivating old PDP context..."); + sendAT("AT+QIDEACT=1", 5000, false); + delay(1000); + + Serial1.println("Activating PDP context..."); + if (sendAT("AT+QIACT=1", 30000, false)) { + networkConnected = true; + lastMessage = "Online"; + Serial1.println("✅ Internet connection established!"); + } else { + Serial1.println("First attempt failed, retrying..."); + delay(2000); + if (sendAT("AT+QIACT=1", 30000, false)) { + networkConnected = true; + lastMessage = "Online"; + Serial1.println("✅ Internet connection established on retry!"); + } else { + networkConnected = false; + lastMessage = "Offline"; + Serial1.println("❌ Failed to connect to internet"); + } + } + + if (networkConnected) { + Serial1.println("Configuring SSL for HTTPS..."); + sendAT("AT+QSSLCFG=\"sslversion\",1,4", 2000, false); + sendAT("AT+QSSLCFG=\"ciphersuite\",1,0xFFFF", 2000, false); + sendAT("AT+QSSLCFG=\"seclevel\",1,0", 2000, false); + sendAT("AT+QHTTPCFG=\"sslctxid\",1", 2000, false); + Serial1.println("✅ SSL configured for HTTPS"); + } + + updateNetworkStatus(); + + Serial1.println("--- NETWORK STATUS ---"); + Serial1.print(" SIM: "); + Serial1.println(simConnected ? "Connected" : "Not detected"); + Serial1.print(" Network: "); + Serial1.println(networkRegistered ? "Registered" : "Searching"); + Serial1.print(" Signal: "); + Serial1.print(signalStrength); + Serial1.println("/31"); + Serial1.print(" Internet: "); + Serial1.println(networkConnected ? "Online" : "Offline"); + Serial1.println("=================================\n"); +} + +inline void processSMS1(String message) { + Serial1.println("\n======== SMS1 RECEIVED ========"); + Serial1.print("Raw Message: "); + Serial1.println(message); + + String numbers[10]; + int count = 0; + extractNumbersFromSMS2(message, numbers, count); + + + String phone = ""; + String token = ""; + + if (count >= 2) { + phone = numbers[0]; + token = numbers[1]; + } + + Serial1.print("Phone: "); + Serial1.println(phone); + Serial1.print("Token: "); + Serial1.println(token); + + if (phone.length() >= 10 && token.length() == 5) { + tempPhoneNumber = phone; + tempTokenCode = token; + + String verifyCode = generateVerificationCode(token); + String reply = "Code: " + verifyCode; + + Serial1.print("Generated Verification Code: "); + Serial1.println(verifyCode); + Serial1.print("Sending SMS to: "); + Serial1.println(phone); + + if (sendSMS(phone, reply)) { + Serial1.println("✅ SMS1: Verification code sent successfully!"); + awaitingSMS2 = true; + lastMessage = "Wait SMS2"; + } else { + Serial1.println("❌ SMS1: Failed to send verification code"); + lastMessage = "SMS1 Failed"; + } + } else { + Serial1.println("❌ SMS1: Invalid format - phone or token missing"); + Serial1.print(" Phone length: "); + Serial1.print(phone.length()); + Serial1.print(", Token length: "); + Serial1.println(token.length()); + lastMessage = "Invalid SMS1"; + } + Serial1.println("================================\n"); +} + +inline void processSMS2(String message) { + Serial1.println("\n======== SMS2 RECEIVED ========"); + Serial1.print("Raw Message: "); + Serial1.println(message); + + String numbers[10]; + int count = 0; + extractNumbersFromSMS2(message, numbers, count); + + Serial1.print("Numbers found: "); + Serial1.println(count); + for (int i = 0; i < count; i++) { + Serial1.print(" ["); + Serial1.print(i); + Serial1.print("]: "); + Serial1.println(numbers[i]); + } + + if (count >= 5) { + // 1. Device ID + String deviceId = numbers[0]; + Serial1.print("Original Device ID: "); + Serial1.println(deviceId); + if (deviceId.length() > 2) { + deviceId = deviceId.substring(0, deviceId.length() - 2); + } + deviceId.toCharArray(config.deviceId, 16); + Serial1.print("Parsed Device ID: "); + Serial1.println(config.deviceId); + + // 2. Upload Interval + if (numbers[1].length() > 1) { + config.uploadInterval = numbers[1].substring(1).toInt(); + } else { + config.uploadInterval = numbers[1].toInt(); + } + Serial1.print("Upload Interval: "); + Serial1.print(config.uploadInterval); + Serial1.println(" min"); + + // 3. SMS Settings + if (numbers[2].length() >= 2) { + config.smsEnabled = (numbers[2].charAt(0) == '1'); + config.smsInterval = numbers[2].substring(1).toInt(); + } else { + config.smsEnabled = false; + config.smsInterval = 0; + } + Serial1.print("SMS Enabled: "); + Serial1.println(config.smsEnabled ? "Yes" : "No"); + Serial1.print("SMS Interval: "); + Serial1.print(config.smsInterval); + Serial1.println(" min"); + + // 4. SIM Type (index 4 if 6 numbers, else last) + int simIndex = (count >= 6) ? 4 : (count - 1); + String simValue = numbers[simIndex]; + Serial1.print("SIM Value (raw): "); + Serial1.println(simValue); + + if (simValue.length() >= 2) { + String simCode = simValue.substring(simValue.length() - 2); + if (simCode == "21") config.simType = SIM_HAMRAHE_AVAL; + else if (simCode == "22") config.simType = SIM_IRANCELL; + else if (simCode == "23") config.simType = SIM_RIGHTEL; + else if (simCode == "71") config.simType = SIM_HAMRAHE_AVAL; + else config.simType = SIM_UNKNOWN; + } else { + int simInt = simValue.toInt(); + if (simInt == 21 || simInt == 71) config.simType = SIM_HAMRAHE_AVAL; + else if (simInt == 22) config.simType = SIM_IRANCELL; + else if (simInt == 23) config.simType = SIM_RIGHTEL; + else config.simType = SIM_UNKNOWN; + } + Serial1.print("SIM Type: "); + Serial1.println(simTypeToString(config.simType)); + + // 5. Phone number + if (tempPhoneNumber.length() > 0) { + tempPhoneNumber.toCharArray(config.serverPhoneNumber, 16); + } else { + strcpy(config.serverPhoneNumber, ""); + } + Serial1.print("Server Phone: "); + Serial1.println(config.serverPhoneNumber); + + // 6. Save and connect + config.verified = true; + saveConfig(); + + currentAPN = simTypeToAPN(config.simType); +awaitingSMS2 = false; + + Serial1.println("\n✅ SMS2: Configuration Complete!"); + Serial1.println("--- FINAL CONFIG ---"); + Serial1.print(" Device ID: "); + Serial1.println(config.deviceId); + Serial1.print(" SIM Type: "); + Serial1.println(simTypeToString(config.simType)); + Serial1.print(" APN: "); + Serial1.println(currentAPN); + Serial1.print(" Upload Int: "); + Serial1.print(config.uploadInterval); + Serial1.println(" min"); + Serial1.print(" SMS Enabled: "); + Serial1.println(config.smsEnabled ? "Yes" : "No"); + Serial1.print(" Server Phone: "); + Serial1.println(config.serverPhoneNumber); + Serial1.println("--------------------"); + + lastMessage = "Configured"; + Serial1.println("Connecting to network..."); + connectToNetwork(); // به جای initEC200U() از این تابع استفاده شود + } else { + Serial1.println("❌ SMS2: Invalid format - need at least 5 numbers"); + Serial1.print(" Found only: "); + Serial1.println(count); + lastMessage = "Invalid SMS2"; + } + Serial1.println("================================\n"); +} + +inline void checkSMS() { + if (!sendAT("AT", 2000, false)) return; + + sendAT("AT+CMGF=1", 2000, false); + delay(100); + + EC200U.println("AT+CMGL=\"REC UNREAD\""); + + String response = ""; + unsigned long start = millis(); + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1) break; + } + + if (response.indexOf("+CMGL:") != -1) { + int msgIndex = response.indexOf("+CMGL:"); + int comma1 = response.indexOf(',', msgIndex); + int comma2 = response.indexOf(',', comma1 + 1); + int quote1 = response.indexOf('\"', comma2 + 1); + int quote2 = response.indexOf('\"', quote1 + 1); + + if (quote1 != -1 && quote2 != -1) { + int msgStart = response.indexOf('\n', quote2) + 1; + int msgEnd = response.indexOf('\n', msgStart); + if (msgEnd == -1) msgEnd = response.length(); + + String message = response.substring(msgStart, msgEnd); + message.trim(); + + Serial1.println("\n📱 SMS received"); + + int smsType = detectSMSType(message); + + if (config.verified) { + Serial1.println("⚠️ Already configured - try SMS Proccess2"); + lastMessage = "Already Configured - try sms process 2"; + processSMS2(message); + } else if (smsType == 1 && !awaitingSMS2) { + processSMS1(message); + } else if (smsType == 2 && awaitingSMS2) { + processSMS2(message); + } else if (smsType == 2 && !awaitingSMS2 && !config.verified) { + // این حالت برای زمانی است که مستقیم SMS2 دریافت شود + processSMS2(message); + } else { + Serial1.println("❌ Invalid SMS or wrong state"); + lastMessage = "Invalid SMS"; + } + + String indexStr = response.substring(msgIndex + 6, comma1); + indexStr.trim(); + sendAT("AT+CMGD=" + indexStr, 2000, false); + } + } +} + +// -------------------- توابع مدیریت EC200U -------------------- +inline void powerOffEC200U() { + Serial1.println("Powering off EC200U..."); + + sendAT("AT+QPOWD", 3000, true); + delay(3000); + + digitalWrite(PWRKEY_PIN, HIGH); + delay(1500); + digitalWrite(PWRKEY_PIN, LOW); + + Serial1.println("EC200U powered off"); + networkConnected = false; +} + + +inline void initEC200U() { + // حذف شرط config.verified - همیشه اجرا شود + Serial1.println("\n======== MODEM INITIALIZATION ========"); + lastMessage = "Modem Initializing"; + + Serial1.println("Checking if modem is already running..."); + bool modemAlreadyOn = sendAT("AT", 2000, false); + + if (modemAlreadyOn) { + Serial1.println("✅ Modem is already ON"); + lastMessage = "Modem Ready"; + } else { + /*Serial1.println("Modem not responding, powering on..."); + + Serial1.println(" PWRKEY -> HIGH (MOSFET ON, PWRKEY to GND)"); + digitalWrite(PWRKEY_PIN, HIGH); + delay(1500); + Serial1.println(" PWRKEY -> LOW (MOSFET OFF, PWRKEY floating)"); + digitalWrite(PWRKEY_PIN, LOW); + */ + Serial1.println("Waiting for modem to boot (8s)..."); + delay(8000); + + if (!sendAT("AT", 5000, false)) { + Serial1.println("⚠️ First attempt failed, trying again with longer pulse..."); + digitalWrite(PWRKEY_PIN, HIGH); + delay(2000); + digitalWrite(PWRKEY_PIN, LOW); + delay(10000); + + if (!sendAT("AT", 5000, false)) { + Serial1.println("❌ Modem not responding after power on!"); + lastMessage = "Modem Err"; + Serial1.println("=================================\n"); + return; + } + } + Serial1.println("✅ Modem powered on successfully"); + lastMessage = "Modem Ready"; + } + + digitalWrite(PWRKEY_PIN, LOW); + + // تنظیمات اولیه + sendAT("AT+CMGF=1", 2000, false); // تنظیم فرمت SMS + sendAT("AT+CNMI=2,1,0,0,0", 2000, false); // اعلان SMS جدید + + // فقط اگر config.verified بود به شبکه وصل شود + if (config.verified) { + Serial1.println("Device is configured, connecting to network..."); + connectToNetwork(); + } else { + Serial1.println("⏳ Device not configured. Waiting for configuration SMS..."); + Serial1.println("📱 Send SMS1 format: PHONENUMBER TOKENCODE"); + lastMessage = "Wait SMS1"; + } + + updateNetworkStatus(); + Serial1.println("=================================\n"); +} + +// -------------------- توابع SSL و HTTP -------------------- +inline void configureSSL() { + Serial1.println("Configuring SSL for HTTPS..."); + + sendAT("AT+QSSLCFG=\"sslversion\",1,4", 2000, false); + sendAT("AT+QSSLCFG=\"ciphersuite\",1,0xFFFF", 2000, false); + sendAT("AT+QSSLCFG=\"seclevel\",1,0", 2000, false); + sendAT("AT+QHTTPCFG=\"sslctxid\",1", 2000, false); + + Serial1.println("✅ SSL configured"); +} + +inline String readHttpResponse() { + Serial1.println("Reading HTTP response body..."); + + while (EC200U.available()) { + EC200U.read(); + } + + EC200U.println("AT+QHTTPREAD=80"); + + char buffer[600]; + int bufIndex = 0; + unsigned long start = millis(); + bool gotConnect = false; + int bodyStart = -1; + + while (millis() - start < 30000 && bufIndex < 599) { + if (EC200U.available()) { + char c = EC200U.read(); + buffer[bufIndex++] = c; + Serial1.write(c); + + if (!gotConnect && bufIndex >= 7) { + if (strncmp(&buffer[bufIndex-7], "CONNECT", 7) == 0) { + gotConnect = true; + } + } + } + + if (bufIndex >= 12) { + buffer[bufIndex] = '\0'; + if (strstr(buffer, "+QHTTPREAD: 0") != NULL) { + delay(100); + while (EC200U.available() && bufIndex < 599) { + buffer[bufIndex++] = EC200U.read(); + } + break; + } + if (strstr(buffer, "ERROR") != NULL) { + Serial1.println("\n❌ Error reading HTTP response"); + return ""; + } + } + } + + buffer[bufIndex] = '\0'; + + Serial1.println("\n--- Raw buffer ---"); + Serial1.println(buffer); + Serial1.println("--- End raw ---"); + + String body = ""; + + char* connectPtr = strstr(buffer, "CONNECT"); + if (connectPtr != NULL) { + char* bodyPtr = strchr(connectPtr, '\n'); + if (bodyPtr != NULL) { + bodyPtr++; + + char* endPtr = strstr(bodyPtr, "\r\nOK"); + if (endPtr == NULL) endPtr = strstr(bodyPtr, "\nOK"); + if (endPtr == NULL) endPtr = strstr(bodyPtr, "+QHTTPREAD"); + + if (endPtr != NULL) { + int len = endPtr - bodyPtr; + body.reserve(len + 1); + for (int i = 0; i < len; i++) { + if (bodyPtr[i] != '\r' && bodyPtr[i] != '\n') { + body += bodyPtr[i]; + } + } + } else { + body = String(bodyPtr); + } + } + } + + body.trim(); + + Serial1.print("\n--- Extracted Body ("); + Serial1.print(body.length()); + Serial1.print(" chars) ---\n["); + Serial1.print(body); + Serial1.println("]"); + Serial1.println("--- End Body ---"); + + return body; +} + +// -------------------- توابع دانلود و تماس صوتی -------------------- +inline bool downloadAMRFile(String url) { + Serial1.println("\n--- Downloading AMR File ---"); + Serial1.print("URL ("); + Serial1.print(url.length()); + Serial1.print(" chars): ["); + Serial1.print(url); + Serial1.println("]"); + + if (url.length() < 10 || !url.startsWith("http")) { + Serial1.println("❌ Invalid URL!"); + return false; + } + + sendAT("AT+QFDEL=\"UFS:audio.amr\"", 2000, false); + delay(100); + + int urlLen = url.length(); + //Serial1.print("Setting URL with length: "); + //Serial1.println(urlLen); + + String cmd = "AT+QHTTPURL=" + String(urlLen) + ",80"; + //Serial1.print("Command: "); + //Serial1.println(cmd); + 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("❌ QHTTPURL error"); + return false; + } + } + + if (!gotConnect) { + Serial1.println("❌ No CONNECT for URL"); + return false; + } + + 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("❌ URL not accepted"); + return false; + } + + Serial1.println("✅ URL set successfully"); + delay(200); + + //Serial1.println("Step 1: Sending HTTP GET request..."); + EC200U.println("AT+QHTTPGET=60"); + + response = ""; + start = millis(); + bool httpSuccess = false; + + 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(); + int httpCode = afterQhttp.substring(firstComma + 1, secondComma).toInt(); + + Serial1.print("\n Error code: "); + Serial1.print(errorCode); + Serial1.print(", HTTP code: "); + Serial1.println(httpCode); + + if (errorCode == 0 && httpCode >= 200 && httpCode < 300) { + httpSuccess = true; + } + break; + } + } + + if (response.indexOf("ERROR") != -1) { + Serial1.println("\n❌ HTTP GET failed"); + return false; + } + } + + if (!httpSuccess) { + Serial1.println("\n❌ HTTP GET timeout or failed"); + return false; + } + + Serial1.println("✅ HTTP GET successful"); + + delay(500); + while (EC200U.available()) EC200U.read(); + + Serial1.println("Step 2: Saving response to file..."); + EC200U.println("AT+QHTTPREADFILE=\"UFS:audio.amr\",80"); + + response = ""; + start = millis(); + bool success = false; + + while (millis() - start < 60000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + + if (response.indexOf("+QHTTPREADFILE: 0") != -1) { + success = true; + break; + } + if (response.indexOf("ERROR") != -1) { + break; + } + } + + if (success) { + Serial1.println("\n✅ AMR file downloaded to RAM"); + return true; + } else { + Serial1.println("\n❌ Failed to save AMR file"); + return false; + } +} + +inline int getCallState(String clccResponse) { + int clccIdx = clccResponse.indexOf("+CLCC:"); + if (clccIdx == -1) return -1; + + String afterClcc = clccResponse.substring(clccIdx + 7); + int firstComma = afterClcc.indexOf(','); + if (firstComma == -1) return -1; + int secondComma = afterClcc.indexOf(',', firstComma + 1); + if (secondComma == -1) return -1; + int thirdComma = afterClcc.indexOf(',', secondComma + 1); + if (thirdComma == -1) return -1; + + String statStr = afterClcc.substring(secondComma + 1, thirdComma); + statStr.trim(); + return statStr.toInt(); +} +inline bool makeVoiceCallWithAudio(String phoneNumber) { + isInCall = true; + Serial1.println("\n--- Making Voice Call ---"); + Serial1.print("Phone: "); + Serial1.println(phoneNumber); + + delay(1000); + + String dialCmd = "ATD" + phoneNumber + ";"; + EC200U.println(dialCmd); + + String response = ""; + unsigned long start = millis(); + bool callAnswered = false; + + while (millis() - start < 10000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + if (response.indexOf("OK") != -1) break; + if (response.indexOf("ERROR") != -1 || + response.indexOf("NO CARRIER") != -1 || + response.indexOf("BUSY") != -1) { + Serial1.println("❌ Call initiation failed"); + isInCall = false; + return false; + } + } + + Serial1.println("📞 Call initiated, waiting for answer..."); + + start = millis(); + while (millis() - start < 60000) { + while (EC200U.available()) { + char c = EC200U.read(); + Serial1.write(c); + } + + EC200U.println("AT+CLCC"); + delay(500); + + String clccResp = ""; + unsigned long clccStart = millis(); + while (millis() - clccStart < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + clccResp += c; + Serial1.write(c); + } + if (clccResp.indexOf("OK") != -1) break; + } + + int callState = getCallState(clccResp); + Serial1.print(" Call state: "); + Serial1.println(callState); + + if (callState == 0) { + callAnswered = true; + Serial1.println("✅ Call answered!"); + break; + } else if (callState == 2) { + Serial1.println(" ⏳ Dialing..."); + } else if (callState == 3) { + Serial1.println(" 🔔 Ringing..."); + } else if (callState == -1) { + if (clccResp.indexOf("NO CARRIER") != -1 || + clccResp.indexOf("+CLCC:") == -1) { + Serial1.println("❌ Call ended or no answer"); + break; + } + } + + delay(2000); + } + + if (!callAnswered) { + Serial1.println("❌ Call not answered"); + sendAT("ATH", 2000, false); + isInCall = false; + return false; + } + + Serial1.println("✅ Call connected, playing audio..."); + + // بارگذاری فایل صوتی در RAM + sendAT("AT+QPSND=0", 1000, false); // تنظیم حالت پخش + sendAT("AT+QHTTPREADFILE=\"UFS:audio.amr\"", 1000, false); + + // پخش فایل صوتی + sendAT("AT+QPSND=1,\"UFS:audio.amr\"", 1000, false); + + response = ""; + start = millis(); + bool playStarted = false; + + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + if (response.indexOf("OK") != -1) { + playStarted = true; + break; + } + if (response.indexOf("ERROR") != -1) { + Serial1.println("❌ Failed to play audio"); + break; + } + } + + if (playStarted) { + Serial1.println("✅ Audio playing..."); + + bool playEnded = false; + start = millis(); + + while (millis() - start < 120000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + + if (response.indexOf("+QPSND: 0") != -1 || + response.indexOf("QPSND: END") != -1) { + playEnded = true; + break; + } + + if (response.indexOf("NO CARRIER") != -1) { + Serial1.println("Call ended by other party"); + break; + } + + delay(500); + } + + if (playEnded) { + Serial1.println("✅ Audio playback completed"); + } + } + + delay(500); + Serial1.println("Hanging up..."); + sendAT("ATH", 3000, true); + + delay(2000); + + Serial1.println("✅ Call ended"); + isInCall = false; + return true; +} + +inline void deleteAMRFile() { + Serial1.println("Deleting AMR file from RAM..."); + sendAT("AT+QFDEL=\"UFS:audio.amr\"", 2000, false); + Serial1.println("✅ File deleted"); +} + +// -------------------- پردازش پاسخ سرور -------------------- +inline void processServerResponse(String response) { + if (response.length() == 0) { + Serial1.println("Empty response, no action needed"); + return; + } + + Serial1.println("\n======== PROCESSING SERVER RESPONSE ========"); + Serial1.print("Response: "); + Serial1.println(response); + + if (response.startsWith("tt")) { + Serial1.println("Response Type: TT (Send SMS)"); + + String data = response.substring(2); + + int hashIdx = data.indexOf('#'); + if (hashIdx != -1) { + String phoneNumber = data.substring(0, hashIdx); + String message = data.substring(hashIdx + 1); + + phoneNumber.trim(); + message.trim(); + + Serial1.print(" Phone: "); + Serial1.println(phoneNumber); + Serial1.print(" Message: "); + Serial1.println(message); + + if (phoneNumber.length() >= 10 && message.length() > 0) { + if (sendSMS(phoneNumber, message)) { + Serial1.println("✅ SMS sent successfully"); + lastMessage = "TT SMS Sent"; + } else { + Serial1.println("❌ Failed to send SMS"); + lastMessage = "TT SMS Fail"; + } + } else { + Serial1.println("❌ Invalid phone or message"); + lastMessage = "TT Invalid"; + } + } else { + Serial1.println("❌ Invalid TT format (no #)"); + lastMessage = "TT Format Err"; + } + + } + else if (response.startsWith("RST"))//ریست شدن + { + NVIC_SystemReset(); + } + else if (response.startsWith("SHD")) // حالت شبیه سازی خاموش شدن + { + HAL_PWR_EnterSTANDBYMode(); + } + else if (response.startsWith("TY")) { + Serial1.println("Response Type: TY (Voice Call with Audio)"); + + String data = response.substring(2); + + Serial1.print("Data after TY removal ("); + Serial1.print(data.length()); + Serial1.print(" chars): "); + Serial1.println(data); + + int hashIdx = data.indexOf('#'); + Serial1.print("Hash index: "); + Serial1.println(hashIdx); + + if (hashIdx != -1) { + String phoneNumber = data.substring(0, hashIdx); + String audioUrl = data.substring(hashIdx + 1); + + phoneNumber.trim(); + audioUrl.trim(); + + Serial1.print("Phone length: "); + Serial1.println(phoneNumber.length()); + Serial1.print("URL length: "); + Serial1.println(audioUrl.length()); + + Serial1.print(" Phone: "); + Serial1.println(phoneNumber); + Serial1.print(" Audio URL: "); + Serial1.println(audioUrl); + + if (phoneNumber.length() >= 10 && audioUrl.length() > 0) { + if (downloadAMRFile(audioUrl)) { + if (makeVoiceCallWithAudio(phoneNumber)) { + Serial1.println("✅ Voice call completed"); + lastMessage = "TY Call Done"; + } else { + Serial1.println("❌ Voice call failed"); + lastMessage = "TY Call Fail"; + } + + deleteAMRFile(); + } else { + Serial1.println("❌ Failed to download audio"); + lastMessage = "TY DL Fail"; + } + } else { + Serial1.println("❌ Invalid phone or URL"); + lastMessage = "TY Invalid"; + } + } else { + Serial1.println("❌ Invalid TY format (no #)"); + lastMessage = "TY Format Err"; + } + + } else { + Serial1.println("Response Type: Numeric or unknown - No action needed"); + } + + Serial1.println("=============================================\n"); +} + +// -------------------- ارسال داده به سرور -------------------- +inline void uploadData() { + Serial1.println("\n======== DATA UPLOAD ========"); + + if (!config.verified) { + Serial1.println("❌ Upload skipped: Device not verified"); + Serial1.println("==============================\n"); + return; + } + if (!networkConnected && config.verified) { + Serial1.println("⚠️ Not connected, trying to reconnect..."); + connectToNetwork(); + } + if (!networkConnected) { + Serial1.println("⚠️ Not connected, trying to reconnect..."); + initEC200U(); + if (!networkConnected) { + Serial1.println("❌ Upload skipped: No network connection"); + Serial1.println("==============================\n"); + return; + } + } + + float coToSend = currentData.coPPM; + + //String data = "deviceId=" + String(7) + + String data = "deviceId=" + String(config.deviceId) + + "&temperatureC=" + String(currentData.temperature, 1) + + "&humidityPercent=" + String(currentData.humidity, 1) + + "&gasPPM=" + String(coToSend, 0) + + "&lux=" + String(currentData.lightLux, 1) + + "&voltage=" + String(currentData.volage, 1) + + "&power=" + String(currentData.power) + + "&oldPower=" + String(currentData.oldPower) + + "&batteryPercent=" + String(currentData.batteryPercent, 1) + + "&batteryVoltage=" + String(currentData.batteryVoltage, 1); + + + data.replace(" ", ""); + String url = String(config.serverUrl) + "/api/Telemetry/AddData?" + data; + //url = 'https://farsnews.ir/4512.2232f38.js'; + //for test call + //url="http://ghback.nabaksoft.ir/My_StaticFiles/calltest.html"; + Serial1.println("--- REQUEST INFO ---"); + Serial1.print(" Server: "); + Serial1.println(config.serverUrl); + Serial1.print(" Device ID: "); + Serial1.println(config.deviceId); + Serial1.print(" Temp: "); + Serial1.print(currentData.temperature, 1); + Serial1.println(" C"); + Serial1.print(" Hum: "); + Serial1.print(currentData.humidity, 1); + Serial1.println(" %"); + Serial1.print(" CO (local): "); + Serial1.print(currentData.coPPM, 0); + Serial1.println(" ppm"); + Serial1.print(" CO (to server): "); + Serial1.print(coToSend, 0); + Serial1.print(" ppm"); + if (!calibrationComplete) { + Serial1.print(" [CALIBRATING]"); + } + // Serial1.println(); + // Serial1.print(" Light: "); + // Serial1.print(currentData.lightLux, 1); + // Serial1.println(" lux"); + Serial1.println("--- FULL URL ---"); + Serial1.println(url); + Serial1.print(" URL Length: "); + Serial1.println(url.length()); + //Serial1.println("--------------------"); + + configureSSL(); + + //Serial1.println("Step 1: Setting URL..."); + String cmd = "AT+QHTTPURL=" + String(url.length()) + ",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; + Serial1.write(c); + } + if (response.indexOf("CONNECT") != -1) { + gotConnect = true; + break; + } + if (response.indexOf("ERROR") != -1) { + Serial1.println("\n❌ Step 1 failed: QHTTPURL error"); + lastMessage = "URL Err"; + Serial1.println("==============================\n"); + return; + } + } + + if (!gotConnect) { + Serial1.println("❌ Step 1 failed: No CONNECT response"); + lastMessage = "URL Err"; + Serial1.println("==============================\n"); + return; + } + + Serial1.println("\n✅ Step 1: Got CONNECT, sending URL..."); + + EC200U.print(url); + delay(1000); + + response = ""; + start = millis(); + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + if (response.indexOf("OK") != -1) break; + } + + if (response.indexOf("OK") == -1) { + Serial1.println("\n❌ Step 1 failed: URL not accepted"); + lastMessage = "URL Err"; + Serial1.println("==============================\n"); + return; + } + + Serial1.println("\n✅ Step 1: URL set successfully"); + delay(500); + + Serial1.println("Step 2: Sending HTTP GET (waiting up to 60 sec)..."); + EC200U.println("AT+QHTTPGET=60"); + + response = ""; + start = millis(); + bool success = false; + bool gotResponse = false; + + while (millis() - start < 65000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + + if (response.indexOf("+QHTTPGET:") != -1) { + delay(1000); + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + gotResponse = true; + + int idx = response.indexOf("+QHTTPGET:"); + String httpResult = response.substring(idx); + Serial1.print("\nHTTP Result: "); + Serial1.println(httpResult); + + if (httpResult.indexOf(" 0,200") != -1 || httpResult.indexOf(" 0,201") != -1 || + httpResult.indexOf(":0,200") != -1 || httpResult.indexOf(":0,201") != -1) { + success = true; + } else if (httpResult.indexOf(",200,") != -1 || httpResult.indexOf(",201,") != -1) { + success = true; + } + break; + } + + if (response.indexOf("ERROR") != -1) { + Serial1.println("\n❌ HTTP GET command error"); + break; + } + } + + if (success) { + lastUpload = millis(); + lastMessage = "Uploaded"; + Serial1.println("\n✅ Step 2: Data uploaded successfully!"); + Serial1.println("--- UPLOAD SUCCESS ---"); + Serial1.print(" Time: "); + Serial1.print(millis() / 1000); + Serial1.println(" sec since boot"); + Serial1.println("----------------------"); + + delay(500); + String serverResponse = readHttpResponse(); + if (serverResponse.length() > 0) { + processServerResponse(serverResponse); + } + } else { + lastMessage = "Upload Err"; + if (gotResponse) { + Serial1.println("\n❌ Step 2: Server returned error"); + } else { + Serial1.println("\n❌ Step 2: Timeout waiting for response"); + } + + Serial1.println("--- FULL RESPONSE ---"); + Serial1.println(response); + Serial1.println("---------------------"); + + Serial1.println("Will retry on next interval..."); + networkConnected = false; + } + + Serial1.println("==============================\n"); +} + + +#endif // EC200U_H + diff --git a/14041130/TestLCD/EC200U.h b/14041130/TestLCD/EC200U.h new file mode 100644 index 0000000..d3adccf --- /dev/null +++ b/14041130/TestLCD/EC200U.h @@ -0,0 +1,1509 @@ +#ifndef EC200U_H +#define EC200U_H + +#include +#include "Config.h" +#include "Memory.h" + +// -------------------- متغیرهای خارجی -------------------- +extern HardwareSerial EC200U; +extern DeviceConfig config; +extern SensorData currentData; +extern bool networkConnected; +extern bool simConnected; +extern bool networkRegistered; +extern int signalStrength; +extern String lastMessage; +extern String currentAPN; +extern String tempPhoneNumber; +extern String tempTokenCode; +extern bool awaitingSMS2; +extern bool isInCall; +//extern bool calibrationComplete; +extern unsigned long lastUpload; + +// Forward declarations +void initEC200U(); +void updateNetworkStatus(); + +// -------------------- توابع کمکی جدید -------------------- +bool checkAudioPlaybackStatus() { + Serial1.println("Checking audio playback status..."); + EC200U.println("AT+QVTSTAT?"); + + String response = ""; + unsigned long start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + } + + if (response.indexOf("+QVTSTAT: 0,0") != -1) { + return true; // پخش تمام شده + } else if (response.indexOf("+QVTSTAT: 0,1") != -1) { + Serial1.println("Audio is playing"); + return false; // در حال پخش است + } else { + Serial1.println("Unknown playback status"); + return false; + } +} +inline bool sendAT(String cmd, uint16_t wait = 2000, bool showResponse = false) { + if (showResponse) { + 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; + if (showResponse) Serial1.write(c); + } + if (response.indexOf("OK") != -1) return true; + if (response.indexOf("ERROR") != -1) return false; + } + return false; +} +inline String stringSendAT(String cmd, uint16_t wait = 2000, bool showResponse = false) { + if (showResponse) { + 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; + if (showResponse) Serial1.write(c); + } + + // اگر پاسخ کامل دریافت شده (دارای OK یا ERROR) خاتمه بده + if (response.indexOf("OK") != -1 || + response.indexOf("ERROR") != -1 || + response.indexOf("NO CARRIER") != -1 || + response.indexOf("CONNECT") != -1) { + break; + } + } + return response; +} +// void initAudioSettings() { +// Serial1.println("Initializing audio settings..."); + +// // ریست تنظیمات صدا +// sendAT("AT+CRESET", 3000, true); +// delay(2000); + +// // تنظیمات اولیه +// sendAT("AT+CLVL=5", 2000, true); // سطح صدا متوسط +// sendAT("AT+CMUT=0", 2000, true); // غیرفعال کردن mute +// sendAT("AT+QDAI=5", 2000, true); // تنظیم رابط صوتی +// sendAT("AT+VTD=1", 2000, true); // تنظیم تون DTMF + +// // تنظیمات کدک صوتی +// sendAT("AT+QMBNCFG=\"select\",0", 2000, true); +// sendAT("AT+QCODECCFG=0,2,1", 2000, true); // AMR-NB, 12.2kbps + +// Serial1.println("✅ Audio settings initialized"); +// } + + +bool sendSMS(String numbers, String messages) { + Serial1.println("\n📱 SENDING SMS - FINAL OPTIMIZED VERSION"); + + // تقسیم شماره‌ها + int phoneCount = 0; + String phoneNumbers[10]; + String numbersCopy = numbers; + + numbersCopy.replace(" ", ""); + + int startIdx = 0; + int commaIdx = numbersCopy.indexOf(','); + + while (commaIdx != -1 && phoneCount < 9) { + phoneNumbers[phoneCount] = numbersCopy.substring(startIdx, commaIdx); + phoneCount++; + startIdx = commaIdx + 1; + commaIdx = numbersCopy.indexOf(',', startIdx); + } + + if (startIdx < numbersCopy.length()) { + phoneNumbers[phoneCount] = numbersCopy.substring(startIdx); + phoneCount++; + } + + if (phoneCount == 0) { + Serial1.println("❌ No phone numbers"); + return false; + } + + // تقسیم پیام‌ها + int messageCount = 0; + String messageTexts[10]; + String messagesCopy = messages; + + startIdx = 0; + int atIdx = messagesCopy.indexOf('@'); + + while (atIdx != -1 && messageCount < 9) { + messageTexts[messageCount] = messagesCopy.substring(startIdx, atIdx); + messageCount++; + startIdx = atIdx + 1; + atIdx = messagesCopy.indexOf('@', startIdx); + } + + if (startIdx < messagesCopy.length()) { + messageTexts[messageCount] = messagesCopy.substring(startIdx); + messageCount++; + } + + if (messageCount == 0) { + Serial1.println("❌ No messages"); + return false; + } + + bool allSent = true; + int totalMessagesSent = 0; + + // تنظیمات اولیه ماژول (یک بار انجام می‌شود) + Serial1.println("📋 Initializing module..."); + 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(); // پاکسازی مجدد + + // ارسال به همه شماره‌ها + for (int i = 0; i < phoneCount; i++) { + String phone = phoneNumbers[i]; + if (phone.length() < 10) { + Serial1.print("❌ Invalid number: "); + Serial1.println(phone); + allSent = false; + continue; + } + + // انتخاب پیام + String message; + if (messageCount == 1) { + message = messageTexts[0]; + } else if (i < messageCount) { + message = messageTexts[i]; + } else { + message = messageTexts[messageCount - 1]; + } + + if (message.length() == 0) { + Serial1.print("❌ Empty message for: "); + Serial1.println(phone); + allSent = false; + continue; + } + + Serial1.print("📤 To: "); + Serial1.print(phone); + Serial1.print(" | Msg: "); + if (message.length() > 30) { + Serial1.print(message.substring(0, 30)); + Serial1.print("..."); + } else { + Serial1.print(message); + } + Serial1.println(); + + // پاکسازی بافر قبل از ارسال + while (EC200U.available()) EC200U.read(); + + // ارسال دستور CMGS + EC200U.print("AT+CMGS=\""); + EC200U.print(phone); + EC200U.println("\""); + + // منتظر prompt > + int timeout = 0; + bool gotPrompt = false; + while (timeout < 100) { + if (EC200U.available() && EC200U.read() == '>') { + gotPrompt = true; + break; + } + delay(50); + timeout++; + } + + if (!gotPrompt) { + Serial1.println("❌ No prompt received"); + allSent = false; + continue; + } + + // ارسال متن (بدون خط جدید) + for (int j = 0; j < message.length(); j++) { + EC200U.write(message.charAt(j)); + } + + // ارسال Ctrl+Z + EC200U.write(26); + + // بررسی نتیجه + timeout = 0; + bool sent = false; + String response = ""; + while (timeout < 80) { // 8 ثانیه (80 * 100ms) + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (response.indexOf("+CMGS:") != -1 || response.indexOf("OK") != -1) { + sent = true; + break; + } + if (response.indexOf("ERROR") != -1 || response.indexOf("CMS ERROR") != -1) { + sent = false; + break; + } + } + if (sent || response.indexOf("ERROR") != -1) break; + delay(100); + timeout++; + } + + if (sent || response.indexOf("ERROR") == -1) { // اگر خطایی نیامد، موفق فرض کن + totalMessagesSent++; + Serial1.println("✅ Sent successfully"); + } else { + Serial1.println("❌ Failed"); + allSent = false; + } + + if (sent) { + Serial1.println("✅ Sent successfully"); + } else { + Serial1.println("❌ Failed"); + allSent = false; + } + + // تاخیر بین ارسال‌ها + if (i < phoneCount - 1) { + Serial1.println("⏳ Waiting 2 seconds..."); + delay(2000); + } + } + + Serial1.print("\n📊 Result: "); + Serial1.print(totalMessagesSent); + Serial1.print("/"); + Serial1.print(phoneCount); + Serial1.println(" messages sent"); + + return (totalMessagesSent > 0); +} +// { +// 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; +// } +// } + +// -------------------- پردازش SMS -------------------- +inline void extractNumbersFromSMS2(String message, String numbers[], int &count) { + count = 0; + String current = ""; + + for (int i = 0; i < message.length(); i++) { + char c = message.charAt(i); + if (c >= '0' && c <= '9') { + current += c; + } else { + if (current.length() > 0) { + if (count < 10) { + numbers[count] = current; + count++; + } + current = ""; + } + } + } + + if (current.length() > 0 && count < 10) { + numbers[count] = current; + count++; + } +} + +inline void updateNetworkStatus() { + if (!config.verified) { + simConnected = false; + networkRegistered = false; + signalStrength = 0; + return; + } + + simConnected = sendAT("AT", 1000, false); + + EC200U.println("AT+CSQ"); + String response = ""; + unsigned long start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1 || response.indexOf("ERROR") != -1) break; + } + + if (response.indexOf("+CSQ:") != -1) { + int idx = response.indexOf("+CSQ:"); + int comma = response.indexOf(',', idx); + if (comma != -1) { + String rssiStr = response.substring(idx + 6, comma); + rssiStr.trim(); + signalStrength = rssiStr.toInt(); + if (signalStrength > 31) signalStrength = 0; + } + } + + EC200U.println("AT+CREG?"); + response = ""; + start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1 || response.indexOf("ERROR") != -1) break; + } + + networkRegistered = (response.indexOf("+CREG:") != -1 && + (response.indexOf(",1,") != -1 || response.indexOf(",5,") != -1)); +} + +inline int detectSMSType(String message) { + int count = 0; + String current = ""; + + for (int i = 0; i < message.length(); i++) { + char c = message.charAt(i); + if (c >= '0' && c <= '9') { + current += c; + } else { + if (current.length() > 0) { + count++; + current = ""; + } + } + } + if (current.length() > 0) { + count++; + } + + return (count >= 5) ? 2 : 1; +} + +inline void connectToNetwork() { + Serial1.println("\n======== NETWORK CONNECTION ========"); + lastMessage = "Connecting"; + + if (!config.verified) { + Serial1.println("❌ Cannot connect: Device not configured"); + return; + } + + currentAPN = simTypeToAPN(config.simType); + Serial1.print("Setting APN: "); + Serial1.println(currentAPN); + String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + currentAPN + "\""; + sendAT(apnCmd, 2000, false); + + Serial1.println("Waiting for network registration..."); + bool registered = false; + for (int attempt = 1; attempt <= 30; attempt++) { + EC200U.println("AT+CREG?"); + String response = ""; + unsigned long start = millis(); + while (millis() - start < 2000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1) break; + } + + if (response.indexOf(",1") != -1 || response.indexOf(",5") != -1) { + registered = true; + Serial1.print("✅ Network registered after "); + Serial1.print(attempt); + Serial1.println(" seconds"); + break; + } + + Serial1.print(" Attempt "); + Serial1.print(attempt); + Serial1.println("/30 - Searching..."); + delay(1000); + } + + if (!registered) { + Serial1.println("⚠️ Network registration timeout - continuing anyway"); + } + + Serial1.println("Deactivating old PDP context..."); + sendAT("AT+QIDEACT=1", 5000, false); + delay(1000); + + Serial1.println("Activating PDP context..."); + if (sendAT("AT+QIACT=1", 30000, false)) { + networkConnected = true; + lastMessage = "Online"; + Serial1.println("✅ Internet connection established!"); + } else { + Serial1.println("First attempt failed, retrying..."); + delay(2000); + if (sendAT("AT+QIACT=1", 30000, false)) { + networkConnected = true; + lastMessage = "Online"; + Serial1.println("✅ Internet connection established on retry!"); + } else { + networkConnected = false; + lastMessage = "Offline"; + Serial1.println("❌ Failed to connect to internet"); + } + } + + if (networkConnected) { + Serial1.println("Configuring SSL for HTTPS..."); + sendAT("AT+QSSLCFG=\"sslversion\",1,4", 2000, false); + sendAT("AT+QSSLCFG=\"ciphersuite\",1,0xFFFF", 2000, false); + sendAT("AT+QSSLCFG=\"seclevel\",1,0", 2000, false); + sendAT("AT+QHTTPCFG=\"sslctxid\",1", 2000, false); + Serial1.println("✅ SSL configured for HTTPS"); + } + + updateNetworkStatus(); + + Serial1.println("--- NETWORK STATUS ---"); + Serial1.print(" SIM: "); + Serial1.println(simConnected ? "Connected" : "Not detected"); + Serial1.print(" Network: "); + Serial1.println(networkRegistered ? "Registered" : "Searching"); + Serial1.print(" Signal: "); + Serial1.print(signalStrength); + Serial1.println("/31"); + Serial1.print(" Internet: "); + Serial1.println(networkConnected ? "Online" : "Offline"); + Serial1.println("=================================\n"); +} + +inline void processSMS1(String message) { + Serial1.println("\n======== SMS1 RECEIVED ========"); + Serial1.print("Raw Message: "); + Serial1.println(message); + + String numbers[10]; + int count = 0; + extractNumbersFromSMS2(message, numbers, count); + + String phone = ""; + String token = ""; + + if (count >= 2) { + phone = numbers[0]; + token = numbers[1]; + } + + Serial1.print("Phone: "); + Serial1.println(phone); + Serial1.print("Token: "); + Serial1.println(token); + + if (phone.length() >= 10 && token.length() == 5) { + tempPhoneNumber = phone; + tempTokenCode = token; + + String verifyCode = generateVerificationCode(token); + String reply = "Code: " + verifyCode; + + Serial1.print("Generated Verification Code: "); + Serial1.println(verifyCode); + Serial1.print("Sending SMS to: "); + Serial1.println(phone); + + if (sendSMS(phone, reply)) { + Serial1.println("✅ SMS1: Verification code sent successfully!"); + awaitingSMS2 = true; + lastMessage = "Wait SMS2"; + } else { + Serial1.println("❌ SMS1: Failed to send verification code"); + lastMessage = "SMS1 Failed"; + } + } else { + Serial1.println("❌ SMS1: Invalid format - phone or token missing"); + Serial1.print(" Phone length: "); + Serial1.print(phone.length()); + Serial1.print(", Token length: "); + Serial1.println(token.length()); + lastMessage = "Invalid SMS1"; + } + Serial1.println("================================\n"); +} + +inline void processSMS2(String message) { + Serial1.println("\n======== SMS2 RECEIVED ========"); + Serial1.print("Raw Message: "); + Serial1.println(message); + + String numbers[10]; + int count = 0; + extractNumbersFromSMS2(message, numbers, count); + + Serial1.print("Numbers found: "); + Serial1.println(count); + for (int i = 0; i < count; i++) { + Serial1.print(" ["); + Serial1.print(i); + Serial1.print("]: "); + Serial1.println(numbers[i]); + } + + if (count >= 5) { + // 1. Device ID + String deviceId = numbers[0]; + Serial1.print("Original Device ID: "); + Serial1.println(deviceId); + if (deviceId.length() > 2) { + deviceId = deviceId.substring(0, deviceId.length() - 2); + } + deviceId.toCharArray(config.deviceId, 16); + Serial1.print("Parsed Device ID: "); + Serial1.println(config.deviceId); + + // 2. Upload Interval + if (numbers[1].length() > 1) { + config.uploadInterval = numbers[1].substring(1).toInt(); + } else { + config.uploadInterval = numbers[1].toInt(); + } + Serial1.print("Upload Interval: "); + Serial1.print(config.uploadInterval); + Serial1.println(" min"); + + // 3. SMS Settings + if (numbers[2].length() >= 2) { + config.smsEnabled = (numbers[2].charAt(0) == '1'); + config.smsInterval = numbers[2].substring(1).toInt(); + } else { + config.smsEnabled = false; + config.smsInterval = 0; + } + Serial1.print("SMS Enabled: "); + Serial1.println(config.smsEnabled ? "Yes" : "No"); + Serial1.print("SMS Interval: "); + Serial1.print(config.smsInterval); + Serial1.println(" min"); + + // 4. SIM Type (index 4 if 6 numbers, else last) + int simIndex = (count >= 6) ? 4 : (count - 1); + String simValue = numbers[simIndex]; + Serial1.print("SIM Value (raw): "); + Serial1.println(simValue); + + if (simValue.length() >= 2) { + String simCode = simValue.substring(simValue.length() - 2); + if (simCode == "21") config.simType = SIM_HAMRAHE_AVAL; + else if (simCode == "22") config.simType = SIM_IRANCELL; + else if (simCode == "23") config.simType = SIM_RIGHTEL; + else if (simCode == "71") config.simType = SIM_HAMRAHE_AVAL; + else config.simType = SIM_UNKNOWN; + } else { + int simInt = simValue.toInt(); + if (simInt == 21 || simInt == 71) config.simType = SIM_HAMRAHE_AVAL; + else if (simInt == 22) config.simType = SIM_IRANCELL; + else if (simInt == 23) config.simType = SIM_RIGHTEL; + else config.simType = SIM_UNKNOWN; + } + Serial1.print("SIM Type: "); + Serial1.println(simTypeToString(config.simType)); + + // 5. Phone number + if (tempPhoneNumber.length() > 0) { + tempPhoneNumber.toCharArray(config.serverPhoneNumber, 16); + } else { + strcpy(config.serverPhoneNumber, ""); + } + Serial1.print("Server Phone: "); + Serial1.println(config.serverPhoneNumber); + + // 6. Save and connect + config.verified = true; + saveConfig(); + + currentAPN = simTypeToAPN(config.simType); + awaitingSMS2 = false; + + Serial1.println("\n✅ SMS2: Configuration Complete!"); + Serial1.println("--- FINAL CONFIG ---"); + Serial1.print(" Device ID: "); + Serial1.println(config.deviceId); + Serial1.print(" SIM Type: "); + Serial1.println(simTypeToString(config.simType)); + Serial1.print(" APN: "); + Serial1.println(currentAPN); + Serial1.print(" Upload Int: "); + Serial1.print(config.uploadInterval); + Serial1.println(" min"); + Serial1.print(" SMS Enabled: "); + Serial1.println(config.smsEnabled ? "Yes" : "No"); + Serial1.print(" Server Phone: "); + Serial1.println(config.serverPhoneNumber); + Serial1.println("--------------------"); + + lastMessage = "Configured"; + Serial1.println("Connecting to network..."); + connectToNetwork(); + } else { + Serial1.println("❌ SMS2: Invalid format - need at least 5 numbers"); + Serial1.print(" Found only: "); + Serial1.println(count); + lastMessage = "Invalid SMS2"; + } + Serial1.println("================================\n"); +} + +inline void checkSMS() { + if (!sendAT("AT", 2000, false)) return; + + sendAT("AT+CMGF=1", 2000, false); + delay(100); + + EC200U.println("AT+CMGL=\"REC UNREAD\""); + + String response = ""; + unsigned long start = millis(); + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + } + if (response.indexOf("OK") != -1) break; + } + + if (response.indexOf("+CMGL:") != -1) { + int msgIndex = response.indexOf("+CMGL:"); + int comma1 = response.indexOf(',', msgIndex); + int comma2 = response.indexOf(',', comma1 + 1); + int quote1 = response.indexOf('\"', comma2 + 1); + int quote2 = response.indexOf('\"', quote1 + 1); + + if (quote1 != -1 && quote2 != -1) { + int msgStart = response.indexOf('\n', quote2) + 1; + int msgEnd = response.indexOf('\n', msgStart); + if (msgEnd == -1) msgEnd = response.length(); + + String message = response.substring(msgStart, msgEnd); + message.trim(); + + Serial1.println("\n📱 SMS received"); + + int smsType = detectSMSType(message); + + if (config.verified) { + Serial1.println("⚠️ Already configured - try SMS Proccess2"); + lastMessage = "Already Configured - try sms process 2"; + processSMS2(message); + } else if (smsType == 1 && !awaitingSMS2) { + processSMS1(message); + } else if (smsType == 2 && awaitingSMS2) { + processSMS2(message); + } else if (smsType == 2 && !awaitingSMS2 && !config.verified) { + // این حالت برای زمانی است که مستقیم SMS2 دریافت شود + processSMS2(message); + } else { + Serial1.println("❌ Invalid SMS or wrong state"); + lastMessage = "Invalid SMS"; + } + + String indexStr = response.substring(msgIndex + 6, comma1); + indexStr.trim(); + sendAT("AT+CMGD=" + indexStr, 2000, false); + } + } +} + +// -------------------- توابع مدیریت EC200U -------------------- +inline void powerOffEC200U() { + Serial1.println("Powering off EC200U..."); + + sendAT("AT+QPOWD", 3000, true); + delay(3000); + + digitalWrite(PWRKEY_PIN, HIGH); + delay(1500); + digitalWrite(PWRKEY_PIN, LOW); + + Serial1.println("EC200U powered off"); + networkConnected = false; +} + +inline void initEC200U() { + Serial1.println("\n======== MODEM INITIALIZATION ========"); + lastMessage = "Modem Initializing"; + + Serial1.println("Checking if modem is already running..."); + bool modemAlreadyOn = sendAT("AT", 2000, false); + + if (modemAlreadyOn) { + Serial1.println("✅ Modem is already ON"); + lastMessage = "Modem Ready"; + } else { + Serial1.println("Waiting for modem to boot (8s)..."); + delay(8000); + + if (!sendAT("AT", 5000, false)) { + Serial1.println("❌ Modem not responding!"); + lastMessage = "Modem Err"; + Serial1.println("=================================\n"); + return; + } + Serial1.println("✅ Modem powered on successfully"); + lastMessage = "Modem Ready"; + } + + digitalWrite(PWRKEY_PIN, LOW); + + // تنظیمات اولیه + sendAT("AT+CMGF=1", 2000, false); // تنظیم فرمت SMS + sendAT("AT+CNMI=2,1,0,0,0", 2000, false); // اعلان SMS جدید + + // تنظیمات صوتی + //initAudioSettings(); + + // فقط اگر config.verified بود به شبکه وصل شود + if (config.verified) { + Serial1.println("Device is configured, connecting to network..."); + connectToNetwork(); + } else { + Serial1.println("⏳ Device not configured. Waiting for configuration SMS..."); + Serial1.println("📱 Send SMS1 format: PHONENUMBER TOKENCODE"); + lastMessage = "Wait SMS1"; + } + + updateNetworkStatus(); + Serial1.println("=================================\n"); +} + +// -------------------- توابع SSL و HTTP -------------------- +inline void configureSSL() { + Serial1.println("Configuring SSL for HTTPS..."); + + sendAT("AT+QSSLCFG=\"sslversion\",1,4", 2000, false); + sendAT("AT+QSSLCFG=\"ciphersuite\",1,0xFFFF", 2000, false); + sendAT("AT+QSSLCFG=\"seclevel\",1,0", 2000, false); + sendAT("AT+QHTTPCFG=\"sslctxid\",1", 2000, false); + + Serial1.println("✅ SSL configured"); +} + +inline String readHttpResponse() { + Serial1.println("Reading HTTP response body..."); + + while (EC200U.available()) { + EC200U.read(); + } + + EC200U.println("AT+QHTTPREAD=80"); + + char buffer[600]; + int bufIndex = 0; + unsigned long start = millis(); + bool gotConnect = false; + int bodyStart = -1; + + while (millis() - start < 30000 && bufIndex < 599) { + if (EC200U.available()) { + char c = EC200U.read(); + buffer[bufIndex++] = c; + Serial1.write(c); + + if (!gotConnect && bufIndex >= 7) { + if (strncmp(&buffer[bufIndex-7], "CONNECT", 7) == 0) { + gotConnect = true; + } + } + } + + if (bufIndex >= 12) { + buffer[bufIndex] = '\0'; + if (strstr(buffer, "+QHTTPREAD: 0") != NULL) { + delay(100); + while (EC200U.available() && bufIndex < 599) { + buffer[bufIndex++] = EC200U.read(); + } + break; + } + if (strstr(buffer, "ERROR") != NULL) { + Serial1.println("\n❌ Error reading HTTP response"); + return ""; + } + } + } + + buffer[bufIndex] = '\0'; + + Serial1.println("\n--- Raw buffer ---"); + Serial1.println(buffer); + Serial1.println("--- End raw ---"); + + String body = ""; + + char* connectPtr = strstr(buffer, "CONNECT"); + if (connectPtr != NULL) { + char* bodyPtr = strchr(connectPtr, '\n'); + if (bodyPtr != NULL) { + bodyPtr++; + + char* endPtr = strstr(bodyPtr, "\r\nOK"); + if (endPtr == NULL) endPtr = strstr(bodyPtr, "\nOK"); + if (endPtr == NULL) endPtr = strstr(bodyPtr, "+QHTTPREAD"); + + if (endPtr != NULL) { + int len = endPtr - bodyPtr; + body.reserve(len + 1); + for (int i = 0; i < len; i++) { + if (bodyPtr[i] != '\r' && bodyPtr[i] != '\n') { + body += bodyPtr[i]; + } + } + } else { + body = String(bodyPtr); + } + } + } + + body.trim(); + + Serial1.print("\n--- Extracted Body ("); + Serial1.print(body.length()); + Serial1.print(" chars) ---\n["); + Serial1.print(body); + Serial1.println("]"); + Serial1.println("--- End Body ---"); + + return body; +} + + +String extractNumberFromResponse(String response) { + String result = ""; + for (int i = 0; i < response.length(); i++) { + char c = response.charAt(i); + if (c >= '0' && c <= '9') { + result += c; + } + } + return result; +} + + +inline bool downloadAMRFile(String url) { + Serial1.println("\n--- Downloading AMR File ---"); + Serial1.print("URL: "); + Serial1.println(url); + + // 1. بررسی اتصال + if (!sendAT("AT", 2000, true)) { + Serial1.println("❌ Modem not responding"); + return false; + } + + // 2. پاکسازی بافر + while (EC200U.available()) EC200U.read(); + + // 3. تنظیم URL (روش مستقیم) + Serial1.println("Setting URL..."); + EC200U.println("AT+QHTTPURL"); + + String response = ""; + unsigned long start = millis(); + bool gotConnect = false; + + // منتظر CONNECT + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + + if (response.indexOf("CONNECT") != -1) { + gotConnect = true; + break; + } + } + if (gotConnect) break; + } + + if (!gotConnect) { + Serial1.println("❌ No CONNECT, trying alternative..."); + + // روش جایگزین: استفاده از طول URL + String cmd = "AT+QHTTPURL=" + String(url.length()) + ",30"; + EC200U.println(cmd); + + response = ""; + start = millis(); + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (c == '\n') { + if (response.indexOf("CONNECT") != -1) { + gotConnect = true; + break; + } + } + } + if (gotConnect) break; + } + } + + if (!gotConnect) { + Serial1.println("❌ Could not get CONNECT prompt"); + return false; + } + + // 4. ارسال URL + Serial1.println("Sending URL..."); + EC200U.println(url); + + // 5. منتظر OK + response = ""; + start = millis(); + bool urlAccepted = false; + + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + + if (response.indexOf("OK") != -1) { + urlAccepted = true; + break; + } + if (response.indexOf("ERROR") != -1) { + Serial1.println("❌ URL rejected"); + return false; + } + } + if (urlAccepted) break; + } + + if (!urlAccepted) { + Serial1.println("❌ URL not accepted (timeout)"); + return false; + } + + Serial1.println("✅ URL set successfully"); + delay(1000); + + // 6. ارسال GET request + Serial1.println("Sending GET request..."); + EC200U.println("AT+QHTTPGET"); + + response = ""; + start = millis(); + bool gotResponse = false; + + while (millis() - start < 30000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + + if (response.indexOf("+QHTTPGET:") != -1) { + gotResponse = true; + + // بررسی کد وضعیت + int idx = response.indexOf("+QHTTPGET:"); + String httpResponse = response.substring(idx); + Serial1.print("HTTP Response: "); + Serial1.println(httpResponse); + + // استخراج کد HTTP + int comma1 = httpResponse.indexOf(','); + int comma2 = httpResponse.indexOf(',', comma1 + 1); + + if (comma1 != -1 && comma2 != -1) { + int httpCode = httpResponse.substring(comma1 + 1, comma2).toInt(); + Serial1.print("HTTP Code: "); + Serial1.println(httpCode); + + if (httpCode != 200) { + Serial1.println("❌ HTTP error"); + return false; + } + } + break; + } + } + if (gotResponse) break; + } + + if (!gotResponse) { + Serial1.println("❌ GET request timeout"); + return false; + } + + Serial1.println("✅ GET successful"); + delay(2000); + + // 7. ذخیره فایل + Serial1.println("Saving to file..."); + EC200U.println("AT+QHTTPREADFILE=\"UFS:output.amr\""); + + response = ""; + start = millis(); + bool fileSaved = false; + + while (millis() - start < 60000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + + // نمایش پیشرفت + static int dots = 0; + if (c == '\n') { + Serial1.print("."); + dots++; + if (dots % 50 == 0) Serial1.println(); + } + + if (response.indexOf("+QHTTPREADFILE: 0") != -1) { + fileSaved = true; + break; + } + } + if (fileSaved) break; + } + + if (fileSaved) { + Serial1.println("\n✅ File saved successfully"); + + // بررسی وجود فایل + Serial1.println("Verifying file..."); + EC200U.println("AT+QFLST=\"UFS:output.amr\""); + + response = ""; + start = millis(); + while (millis() - start < 3000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + if (response.indexOf("output.amr") != -1) break; + } + + if (response.indexOf("output.amr") != -1) { + Serial1.println("✅ File verified"); + return true; + } else { + Serial1.println("⚠️ File not found in listing"); + return true; // باز هم true برگردان چون ذخیره شد + } + } + + Serial1.println("\n❌ Failed to save file"); + return false; +} + + +// ==================== تابع نهایی: تماس با DTMF Tones ==================== +// // تابع اصلی که واقعاً باید کار کند +// inline bool makeVoiceCallWithAudio(String phoneNumber) { +// return false; +// } + +// ==================== تابع اصلاح شده processServerResponse ==================== +inline void processServerResponse(String response) { + if (response.length() == 0) { + Serial1.println("Empty response, no action needed"); + return; + } + + Serial1.println("\n======== PROCESSING SERVER RESPONSE ========"); + Serial1.print("Response: "); + Serial1.println(response); + + if (response.startsWith("tt")) { + Serial1.println("Response Type: TT (Send SMS)"); + + String data = response.substring(2); + + int hashIdx = data.indexOf('#'); + if (hashIdx != -1) { + String phoneNumber = data.substring(0, hashIdx); + String message = data.substring(hashIdx + 1); + + phoneNumber.trim(); + message.trim(); + + Serial1.print(" Phone: "); + Serial1.println(phoneNumber); + Serial1.print(" Message: "); + Serial1.println(message); + + if (phoneNumber.length() >= 10 && message.length() > 0) { + if (sendSMS(phoneNumber, message)) { + Serial1.println("✅ SMS sent successfully"); + lastMessage = "TT SMS Sent"; + } else { + Serial1.println("❌ Failed to send SMS"); + lastMessage = "TT SMS Fail"; + } + } else { + Serial1.println("❌ Invalid phone or message"); + lastMessage = "TT Invalid"; + } + } else { + Serial1.println("❌ Invalid TT format (no #)"); + lastMessage = "TT Format Err"; + } + } + else if (response.startsWith("RST")) { + NVIC_SystemReset(); + } + else if (response.startsWith("SHD")) { + HAL_PWR_EnterSTANDBYMode(); + } + // else if (response.startsWith("TY")) { + // Serial1.println("Response Type: TY (Voice Call with Audio/DMTF)"); + + // String data = response.substring(2); + + // int hashIdx = data.indexOf('#'); + // if (hashIdx != -1) { + // String phoneNumber = data.substring(0, hashIdx); + // String audioUrl = data.substring(hashIdx + 1); + + // phoneNumber.trim(); + // audioUrl.trim(); + + // Serial1.print(" Phone: "); + // Serial1.println(phoneNumber); + // Serial1.print(" Audio URL: "); + // Serial1.println(audioUrl); + + // if (phoneNumber.length() >= 10) { + // if (makeVoiceCallWithAudio(phoneNumber)) { + // Serial1.println("✅ Voice call with DTMF completed"); + // lastMessage = "TY Call Done (DTMF)"; + // } else { + // Serial1.println("❌ Voice call failed"); + // lastMessage = "TY Call Fail"; + // } + // } else { + // Serial1.println("❌ Invalid phone number"); + // lastMessage = "TY Invalid"; + // } + // } else { + // Serial1.println("❌ Invalid TY format (no #)"); + // lastMessage = "TY Format Err"; + // } + // } + else { + Serial1.println("Response Type: Numeric or unknown - No action needed"); + } + + Serial1.println("=============================================\n"); +} + +// -------------------- ارسال داده به سرور -------------------- +inline void uploadData() { + Serial1.println("\n======== DATA UPLOAD ========"); + + if (!config.verified) { + Serial1.println("❌ Upload skipped: Device not verified"); + Serial1.println("==============================\n"); + return; + } + if (!networkConnected && config.verified) { + Serial1.println("⚠️ Not connected, trying to reconnect..."); + connectToNetwork(); + } + if (!networkConnected) { + Serial1.println("⚠️ Not connected, trying to reconnect..."); + initEC200U(); + if (!networkConnected) { + Serial1.println("❌ Upload skipped: No network connection"); + Serial1.println("==============================\n"); + return; + } + } + + float coToSend = currentData.coPPM; + + String data = "deviceId=" + String(config.deviceId) + + "&temperatureC=" + String(currentData.temperature, 1) + + "&humidityPercent=" + String(currentData.humidity, 1) + + "&gasPPM=" + String(coToSend, 0) + + "&lux=" + String(currentData.lightLux, 1) + + "&voltage=" + String(currentData.volage, 1) + + "&power=" + String(currentData.power) + + "&oldPower=" + String(currentData.oldPower) + + "&batteryPercent=" + String(currentData.batteryPercent, 1) + + "&batteryVoltage=" + String(currentData.batteryVoltage, 1); + + data.replace(" ", ""); + String url = String(config.serverUrl) + "/api/Telemetry/AddData?" + data; + //url = "https://ghback.nabaksoft.ir/My_StaticFiles/calltest.html"; + Serial1.println("--- REQUEST INFO ---"); + Serial1.print(" Server: "); + Serial1.println(config.serverUrl); + Serial1.print(" Device ID: "); + Serial1.println(config.deviceId); + Serial1.print(" Temp: "); + Serial1.print(currentData.temperature, 1); + Serial1.println(" C"); + Serial1.print(" Hum: "); + Serial1.print(currentData.humidity, 1); + Serial1.println(" %"); + Serial1.print(" CO (local): "); + Serial1.print(currentData.coPPM, 0); + Serial1.println(" ppm"); + Serial1.print(" CO (to server): "); + Serial1.print(coToSend, 0); + Serial1.print(" ppm"); + if (!MQ7sensorPreheated) { + Serial1.print(" [CALIBRATING]"); + } + Serial1.println("--- FULL URL ---"); + Serial1.println(url); + Serial1.print(" URL Length: "); + Serial1.println(url.length()); + + configureSSL(); + + String cmd = "AT+QHTTPURL=" + String(url.length()) + ",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; + Serial1.write(c); + } + if (response.indexOf("CONNECT") != -1) { + gotConnect = true; + break; + } + if (response.indexOf("ERROR") != -1) { + Serial1.println("\n❌ Step 1 failed: QHTTPURL error"); + lastMessage = "URL Err"; + Serial1.println("==============================\n"); + return; + } + } + + if (!gotConnect) { + Serial1.println("❌ Step 1 failed: No CONNECT response"); + lastMessage = "URL Err"; + Serial1.println("==============================\n"); + return; + } + + Serial1.println("\n✅ Step 1: Got CONNECT, sending URL..."); + + EC200U.print(url); + delay(1000); + + response = ""; + start = millis(); + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + if (response.indexOf("OK") != -1) break; + } + + if (response.indexOf("OK") == -1) { + Serial1.println("\n❌ Step 1 failed: URL not accepted"); + lastMessage = "URL Err"; + Serial1.println("==============================\n"); + return; + } + + Serial1.println("\n✅ Step 1: URL set successfully"); + delay(500); + + Serial1.println("Step 2: Sending HTTP GET (waiting up to 60 sec)..."); + EC200U.println("AT+QHTTPGET=60"); + + response = ""; + start = millis(); + bool success = false; + bool gotResponse = false; + + while (millis() - start < 65000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + + if (response.indexOf("+QHTTPGET:") != -1) { + delay(1000); + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + gotResponse = true; + + int idx = response.indexOf("+QHTTPGET:"); + String httpResult = response.substring(idx); + Serial1.print("\nHTTP Result: "); + Serial1.println(httpResult); + + if (httpResult.indexOf(" 0,200") != -1 || httpResult.indexOf(" 0,201") != -1 || + httpResult.indexOf(":0,200") != -1 || httpResult.indexOf(":0,201") != -1) { + success = true; + } else if (httpResult.indexOf(",200,") != -1 || httpResult.indexOf(",201,") != -1) { + success = true; + } + break; + } + + if (response.indexOf("ERROR") != -1) { + Serial1.println("\n❌ HTTP GET command error"); + break; + } + } + + if (success) { + lastUpload = millis(); + lastMessage = "Uploaded"; + Serial1.println("\n✅ Step 2: Data uploaded successfully!"); + Serial1.println("--- UPLOAD SUCCESS ---"); + Serial1.print(" Time: "); + Serial1.print(millis() / 1000); + Serial1.println(" sec since boot"); + Serial1.println("----------------------"); + + delay(500); + String serverResponse = readHttpResponse(); + if (serverResponse.length() > 0) { + processServerResponse(serverResponse); + } + } else { + lastMessage = "Upload Err"; + if (gotResponse) { + Serial1.println("\n❌ Step 2: Server returned error"); + } else { + Serial1.println("\n❌ Step 2: Timeout waiting for response"); + } + + Serial1.println("--- FULL RESPONSE ---"); + Serial1.println(response); + Serial1.println("---------------------"); + + Serial1.println("Will retry on next interval..."); + networkConnected = false; + } + + Serial1.println("==============================\n"); +} + +#endif // EC200U_H \ No newline at end of file diff --git a/14041130/TestLCD/Icons.h b/14041130/TestLCD/Icons.h new file mode 100644 index 0000000..e43bb13 --- /dev/null +++ b/14041130/TestLCD/Icons.h @@ -0,0 +1,214 @@ +#ifndef ICONS_H +#define ICONS_H + +#include +#include + +// ==================== کدهای آیکون‌های 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 \ No newline at end of file diff --git a/14041130/TestLCD/Memory.h b/14041130/TestLCD/Memory.h new file mode 100644 index 0000000..9e6447f --- /dev/null +++ b/14041130/TestLCD/Memory.h @@ -0,0 +1,433 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include +#include +#include "Config.h" + +// -------------------- کتابخانه EEPROM داخلی برای STM32 -------------------- +#if defined(STM32F1xx) || defined(STM32F3xx) || defined(STM32F4xx) + #include +#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 \ No newline at end of file diff --git a/14041130/TestLCD/STM32_InternalFlash.h.h b/14041130/TestLCD/STM32_InternalFlash.h.h new file mode 100644 index 0000000..256f418 --- /dev/null +++ b/14041130/TestLCD/STM32_InternalFlash.h.h @@ -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 \ No newline at end of file diff --git a/14041130/TestLCD/Sensors - Copy.hold b/14041130/TestLCD/Sensors - Copy.hold new file mode 100644 index 0000000..e0707aa --- /dev/null +++ b/14041130/TestLCD/Sensors - Copy.hold @@ -0,0 +1,472 @@ +#ifndef SENSORS_H +#define SENSORS_H + +#include +#include +#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 \ No newline at end of file diff --git a/14041130/TestLCD/Sensors.h b/14041130/TestLCD/Sensors.h new file mode 100644 index 0000000..ceadbcb --- /dev/null +++ b/14041130/TestLCD/Sensors.h @@ -0,0 +1,924 @@ +#ifndef SENSORS_H +#define SENSORS_H + +#include +#include +#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 \ No newline at end of file diff --git a/14041130/TestLCD/SoftI2C.cppppp b/14041130/TestLCD/SoftI2C.cppppp new file mode 100644 index 0000000..e0b59ae --- /dev/null +++ b/14041130/TestLCD/SoftI2C.cppppp @@ -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; +} \ No newline at end of file diff --git a/14041130/TestLCD/SoftI2C.hhhh b/14041130/TestLCD/SoftI2C.hhhh new file mode 100644 index 0000000..d2a8b48 --- /dev/null +++ b/14041130/TestLCD/SoftI2C.hhhh @@ -0,0 +1,41 @@ +#ifndef SOFTI2C_H +#define SOFTI2C_H + +#include + +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 \ No newline at end of file diff --git a/14041130/TestLCD/TestLCD.ino b/14041130/TestLCD/TestLCD.ino new file mode 100644 index 0000000..eb0fcb0 --- /dev/null +++ b/14041130/TestLCD/TestLCD.ino @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include + +// -------------------- فایل‌های پروژه -------------------- +#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); +} diff --git a/14041130/TestLCD/Voltage_Reader.h b/14041130/TestLCD/Voltage_Reader.h new file mode 100644 index 0000000..30b2e63 --- /dev/null +++ b/14041130/TestLCD/Voltage_Reader.h @@ -0,0 +1,142 @@ +#ifndef VOLTAGE_READER_H +#define VOLTAGE_READER_H + +#include +#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 \ No newline at end of file diff --git a/14041130/TestLCD/fonts/FreeSansBold12pt7b.h b/14041130/TestLCD/fonts/FreeSansBold12pt7b.h new file mode 100644 index 0000000..c1d6ef4 --- /dev/null +++ b/14041130/TestLCD/fonts/FreeSansBold12pt7b.h @@ -0,0 +1,290 @@ +#pragma once +#include + +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 diff --git a/14041130/TestLCD/fonts/FreeSansBold9pt7b.h b/14041130/TestLCD/fonts/FreeSansBold9pt7b.h new file mode 100644 index 0000000..cffb178 --- /dev/null +++ b/14041130/TestLCD/fonts/FreeSansBold9pt7b.h @@ -0,0 +1,210 @@ +#pragma once +#include + +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 diff --git a/14041130/TestProgram/TestEcu/EC200U-Call.h b/14041130/TestProgram/TestEcu/EC200U-Call.h new file mode 100644 index 0000000..49a3b60 --- /dev/null +++ b/14041130/TestProgram/TestEcu/EC200U-Call.h @@ -0,0 +1,475 @@ +#ifndef EC200U_AUDIOCALL_H +#define EC200U_AUDIOCALL_H + +#include + +// -------------------- تعریف 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 \ No newline at end of file diff --git a/14041130/TestProgram/TestEcu/EC200U-sms.h b/14041130/TestProgram/TestEcu/EC200U-sms.h new file mode 100644 index 0000000..132ed42 --- /dev/null +++ b/14041130/TestProgram/TestEcu/EC200U-sms.h @@ -0,0 +1,434 @@ +#ifndef EC200U_FINAL_H +#define EC200U_FINAL_H + +#include + +// -------------------- تعریف 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 + +// // -------------------- تعریف 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 \ No newline at end of file diff --git a/14041130/TestProgram/TestEcu/TestEcu.ino b/14041130/TestProgram/TestEcu/TestEcu.ino new file mode 100644 index 0000000..1868367 --- /dev/null +++ b/14041130/TestProgram/TestEcu/TestEcu.ino @@ -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); +// } \ No newline at end of file diff --git a/14041130/mq/calibr/Voltage_Reader.h b/14041130/mq/calibr/Voltage_Reader.h new file mode 100644 index 0000000..22d0797 --- /dev/null +++ b/14041130/mq/calibr/Voltage_Reader.h @@ -0,0 +1,148 @@ +#ifndef VOLTAGE_READER_H +#define VOLTAGE_READER_H + +#include + +#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 \ No newline at end of file diff --git a/14041130/mq/calibr/calibr.ino b/14041130/mq/calibr/calibr.ino new file mode 100644 index 0000000..18401cb --- /dev/null +++ b/14041130/mq/calibr/calibr.ino @@ -0,0 +1,127 @@ +#include +#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 + +// // ========== پارامترهای مدار (بر اساس آخرین اندازه‌گیری) ========== +// #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); +// } \ No newline at end of file diff --git a/14041130/mq/ppm/ppm.ino b/14041130/mq/ppm/ppm.ino new file mode 100644 index 0000000..2c1f060 --- /dev/null +++ b/14041130/mq/ppm/ppm.ino @@ -0,0 +1,57 @@ +#include + +// ========== پارامترهای مدار ========== +#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); +} \ No newline at end of file