From 3ca7b7df9afb6c2e8543967736990a984a6f42ac Mon Sep 17 00:00:00 2001 From: rahimi rahimi Date: Mon, 29 Dec 2025 00:00:20 +0330 Subject: [PATCH] add test page --- src/GreenHome.Api/GreenHome.Api.csproj | 2 +- .../My_StaticFiles/calltest.html | 1 + src/GreenHome.Api/My_StaticFiles/smsTest.html | 1 + src/GreenHome.Api/Program.cs | 13 +- .../DeviceTokenService.cs | 44 +- src/Test/Untitled-1.cpp | 1977 +++++++++++++++++ 6 files changed, 2017 insertions(+), 21 deletions(-) create mode 100644 src/GreenHome.Api/My_StaticFiles/calltest.html create mode 100644 src/GreenHome.Api/My_StaticFiles/smsTest.html create mode 100644 src/Test/Untitled-1.cpp diff --git a/src/GreenHome.Api/GreenHome.Api.csproj b/src/GreenHome.Api/GreenHome.Api.csproj index 14c7f53..4e5ee0d 100644 --- a/src/GreenHome.Api/GreenHome.Api.csproj +++ b/src/GreenHome.Api/GreenHome.Api.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/src/GreenHome.Api/My_StaticFiles/calltest.html b/src/GreenHome.Api/My_StaticFiles/calltest.html new file mode 100644 index 0000000..93bb0bd --- /dev/null +++ b/src/GreenHome.Api/My_StaticFiles/calltest.html @@ -0,0 +1 @@ +TY09192530212#ghback.nabaksoft.ir/calltest.amr \ No newline at end of file diff --git a/src/GreenHome.Api/My_StaticFiles/smsTest.html b/src/GreenHome.Api/My_StaticFiles/smsTest.html new file mode 100644 index 0000000..ba01f53 --- /dev/null +++ b/src/GreenHome.Api/My_StaticFiles/smsTest.html @@ -0,0 +1 @@ +TT09192530212#سلام خوبی \ No newline at end of file diff --git a/src/GreenHome.Api/Program.cs b/src/GreenHome.Api/Program.cs index 54f7862..7f00eec 100644 --- a/src/GreenHome.Api/Program.cs +++ b/src/GreenHome.Api/Program.cs @@ -5,6 +5,7 @@ using GreenHome.Infrastructure; using GreenHome.Sms.Ippanel; using GreenHome.VoiceCall.Avanak; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.FileProviders; using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); @@ -98,17 +99,21 @@ using (var scope = app.Services.CreateScope()) // Configure the HTTP request pipeline. app.MapOpenApi(); app.MapScalarApiReference(); - -// HTTPS Redirection فقط در Production if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } app.UseCors(CorsPolicy); - app.UseAuthorization(); +app.UseStaticFiles(new StaticFileOptions +{ + FileProvider = new PhysicalFileProvider( + Path.Combine(builder.Environment.ContentRootPath, "My_StaticFiles")), + RequestPath = "/My_StaticFiles" +}); + app.MapControllers(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/src/GreenHome.Infrastructure/DeviceTokenService.cs b/src/GreenHome.Infrastructure/DeviceTokenService.cs index 61b79c8..c5dadd2 100644 --- a/src/GreenHome.Infrastructure/DeviceTokenService.cs +++ b/src/GreenHome.Infrastructure/DeviceTokenService.cs @@ -112,13 +112,18 @@ public sealed class DeviceTokenService : IDeviceTokenService await dbContext.SaveChangesAsync(cancellationToken); - // ارسال کد توکن از طریق پیامک + // ارسال کد توکن از طریق پیامک الگویی try { - await smsService.SendWebserviceSmsAsync(new WebserviceSmsRequest + await smsService.SendPatternSmsAsync(new PatternSmsRequest { - Recipient = request.DevicePhoneNumber, - Message = $"0911925302120#{tokenCode}" + Recipients = new List { request.DevicePhoneNumber }, + PatternCode = "gfukab9r0nca0pt", // TODO: کد الگوی پیامک را اینجا قرار دهید + Variables = new Dictionary + { + { "tel", "09192530212" }, + { "verifyCode", verificationCode } + } }, cancellationToken); } catch (Exception smsEx) @@ -194,21 +199,28 @@ public sealed class DeviceTokenService : IDeviceTokenService }; } Random rnd = new Random(); - // رمزگذاری ساده تنظیمات: DeviceName|UploadIntervalMin به Base64 - var deviceId = settings.Device.Id; - var uploadInterval = settings.UploadIntervalMin; - string smsConfig = (settings.MinimumSmsIntervalMinutes != 0 ? "1" : rnd.Next(2, 9).ToString()) - + settings.MinimumSmsIntervalMinutes; - var plainText = $"{deviceId}01|{rnd.Next(1,9)}{uploadInterval}|{smsConfig}|{rnd.Next(139,97654)}|{rnd.Next(1,9)}{(int)settings.SimCardType}"; - var encodedSettings = plainText; //Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(plainText)); + // آماده‌سازی پارامترها برای ارسال پیامک الگویی + var deviceId = settings.Device.Id + "01"; + var uploadInterval = rnd.Next(1, 9) + "" + settings.UploadIntervalMin; + var smsInterval = settings.MinimumSmsIntervalMinutes > 0 ? "1" + settings.MinimumSmsIntervalMinutes : "0" + rnd.Next(1, 9); + var sysNumber = rnd.Next(139, 97654); // عدد رندوم SysNumber + var simType = rnd.Next(1, 9).ToString() + ((int?)settings.SimCardType)?.ToString() ?? "1"; // مقدار عددی enum - // ارسال تنظیمات کدشده از طریق پیامک + // ارسال تنظیمات از طریق پیامک الگویی try { - await smsService.SendWebserviceSmsAsync(new WebserviceSmsRequest + await smsService.SendPatternSmsAsync(new PatternSmsRequest { - Recipient = request.DevicePhoneNumber, - Message = $"{encodedSettings}" + Recipients = new List { request.DevicePhoneNumber }, + PatternCode = "kx3kfqri7g09r02", // TODO: کد الگوی پیامک را اینجا قرار دهید + Variables = new Dictionary + { + { "deviceId", deviceId }, + { "uploadInterval", uploadInterval }, + { "smsInterval", smsInterval.ToString() }, + { "SysNumber", sysNumber.ToString() }, + { "SimType", simType } + } }, cancellationToken); } catch (Exception smsEx) @@ -234,7 +246,7 @@ public sealed class DeviceTokenService : IDeviceTokenService { Success = true, Message = "تنظیمات با موفقیت ارسال شد", - EncodedSettings = encodedSettings + EncodedSettings = null }; } catch (Exception ex) diff --git a/src/Test/Untitled-1.cpp b/src/Test/Untitled-1.cpp new file mode 100644 index 0000000..a4c419b --- /dev/null +++ b/src/Test/Untitled-1.cpp @@ -0,0 +1,1977 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// -------------------- پیکربندی -------------------- +#define FLASH_CS PA4 +#define FLASH_MOSI PA7 +#define FLASH_MISO PA6 +#define FLASH_SCK PA5 +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define OLED_RESET -1 +#define MQ7_PIN PA2 +#define SDA_PIN PB9 +#define SCL_PIN PB8 +#define PWRKEY_PIN PB5 +#define SENSOR_READ_INTERVAL 60000 + +// -------------------- آدرس‌های حافظه -------------------- +#define CONFIG_ADDRESS 0x000000 +#define DATA_ADDRESS 0x010000 + +// -------------------- انوم‌ها -------------------- +enum SIMType { SIM_UNKNOWN = 0, SIM_HAMRAHE_AVAL = 1, SIM_IRANCELL = 2, SIM_RIGHTEL = 3 }; + +// -------------------- ساختارها -------------------- +struct DeviceConfig { + char signature[4]; + char deviceId[16]; + char serverPhoneNumber[16]; + char serverUrl[64]; + unsigned long uploadInterval; + unsigned long smsInterval; + unsigned long saveInterval; + SIMType simType; + bool smsEnabled; + bool verified; + bool valid; +}; + +struct SensorData { + float temperature; + float humidity; + float coPPM; + float lightLux; + unsigned long timestamp; + uint8_t sent; +}; + +// -------------------- متغیرهای سراسری -------------------- +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +BH1750 lightMeter; +Adafruit_SHT31 sht31; +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; + +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; // 0-31 برای AT+CSQ + +// --- متغیرهای جدید برای CO کالیبراسیون --- +unsigned long systemStartTime = 0; // زمان شروع سیستم +const long calibrationPeriod = 600000; // 10 دقیقه = 600000 میلی‌ثانیه +bool calibrationComplete = false; // پایان دوره کالیبراسیون +float MQ7_R0 = 10000.0; // مقاومت سنسور در هوای پاک + +// -------------------- توابع حافظه Flash -------------------- +void flashInit() { + pinMode(FLASH_CS, OUTPUT); + digitalWrite(FLASH_CS, HIGH); + flashSPI.begin(); + flashSPI.setClockDivider(SPI_CLOCK_DIV4); + delay(100); +} + +bool flashIsBusy() { + digitalWrite(FLASH_CS, LOW); + flashSPI.transfer(0x05); + uint8_t status = flashSPI.transfer(0); + digitalWrite(FLASH_CS, HIGH); + return (status & 0x01); +} + +void flashWaitForReady() { + while (flashIsBusy()) delay(1); +} + +void flashRead(uint32_t addr, uint8_t *data, uint32_t len) { + flashWaitForReady(); + digitalWrite(FLASH_CS, LOW); + flashSPI.transfer(0x03); + 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); +} + +void flashWrite(uint32_t addr, uint8_t *data, uint32_t len) { + uint32_t offset = 0; + while (offset < len) { + // Write Enable + digitalWrite(FLASH_CS, LOW); + flashSPI.transfer(0x06); + digitalWrite(FLASH_CS, HIGH); + + uint32_t pageOffset = addr % 256; + uint32_t remaining = 256 - pageOffset; + uint32_t writeSize = min(remaining, len - offset); + + 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 < writeSize; i++) { + flashSPI.transfer(data[offset + i]); + } + + digitalWrite(FLASH_CS, HIGH); + flashWaitForReady(); + + addr += writeSize; + offset += writeSize; + } +} + +void flashSectorErase(uint32_t addr) { + digitalWrite(FLASH_CS, LOW); + flashSPI.transfer(0x06); + digitalWrite(FLASH_CS, HIGH); + + 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(); +} + +// -------------------- توابع مدیریت داده -------------------- +void readConfig() { + uint8_t buffer[sizeof(DeviceConfig)]; + flashRead(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig)); + memcpy(&config, buffer, sizeof(DeviceConfig)); + + if (strcmp(config.signature, "CFG") != 0) { + 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; + + saveConfig(); + } +} + +void saveConfig() { + config.valid = true; + flashSectorErase(CONFIG_ADDRESS); + uint8_t buffer[sizeof(DeviceConfig)]; + memcpy(buffer, &config, sizeof(DeviceConfig)); + flashWrite(CONFIG_ADDRESS, buffer, sizeof(DeviceConfig)); + Serial1.println("✅ Config saved"); +} + +// -------------------- توابع کمکی -------------------- +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"; + } +} + +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"; + } +} + +String generateVerificationCode(String tokenCode) { + long token = tokenCode.toInt(); + long verification = (token * 7 + 12345) % 100000; + char buffer[6]; + sprintf(buffer, "%05ld", verification); + return String(buffer); +} + +// -------------------- توابع CO بهبود یافته -------------------- +void calibrateMQ7() { + Serial1.println("Calibrating MQ7..."); + + float sum = 0; + for(int i = 0; i < 100; i++) { + int adc = analogRead(MQ7_PIN); + float voltage = adc * (3.3 / 4095.0); + float RS = (3.3 - voltage) / voltage * 10000.0; // مقاومت بار 10K + sum += RS; + delay(50); + } + + MQ7_R0 = sum / 100.0; + Serial1.print("MQ7 R0 calibrated: "); + Serial1.println(MQ7_R0); +} + +float readCOImproved() { + // خواندن چند نمونه برای میانگین‌گیری + float sumVoltage = 0; + for (int i = 0; i < 20; i++) { + int adc = analogRead(MQ7_PIN); + float voltage = adc * (3.3 / 4095.0); + sumVoltage += voltage; + delay(2); + } + + float voltage = sumVoltage / 20.0; + + // محافظت از تقسیم بر صفر + if (voltage < 0.01) voltage = 0.01; + if (voltage > 3.29) voltage = 3.29; + + // محاسبه مقاومت سنسور (برای مدار سنسور بالا) + float RS = (3.3 - voltage) / voltage * 10000.0; + + // نسبت Rs/R0 + float ratio = RS / MQ7_R0; + + float ppm = 0; + + if (ratio > 0) { + // فرمول اصلاح‌شده بر اساس دیتاشیت MQ7 + // مقادیر استخراج‌شده از منحنی حساسیت: + // نقطه ۱: 200ppm → Rs/R0 = 0.4 + // نقطه ۲: 2000ppm → Rs/R0 = 0.1 + float m = -0.60; // شیب (اصلاح‌شده) + float b = 0.95; // عرض از مبدأ (اصلاح‌شده) + + ppm = pow(10, (log10(ratio) - b) / m); + + // محدود کردن به بازه معتبر MQ7: 20-2000 ppm + if (ppm < 0) ppm = 0; + if (ppm > 2000) ppm = 2000; + } + + // در 10 دقیقه اول، مقدار منفی بده + if (!calibrationComplete) { + ppm = -ppm; + } + + return ppm; +} + +// -------------------- پردازش SMS -------------------- +// استخراج همه اعداد از متن پیامک (برای SMS1 و SMS2) +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++; + } +} + +// تشخیص نوع پیامک (SMS1 یا SMS2) بر اساس تعداد اعداد +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++; + } + + // اگر حداقل ۵ تا عدد داشته باشیم، SMS2 فرض می‌کنیم + return (count >= 5) ? 2 : 1; +} + +// -------------------- توابع EC200U -------------------- +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; +} + +bool sendSMS(String number, String message) { + if (!sendAT("AT+CMGF=1", 3000, false)) { + return false; + } + + delay(200); + + EC200U.print("AT+CMGS=\""); + EC200U.print(number); + EC200U.println("\""); + + String response = ""; + unsigned long start = millis(); + bool gotPrompt = false; + + while (millis() - start < 5000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + if (response.indexOf(">") != -1) { + gotPrompt = true; + break; + } + } + if (gotPrompt) break; + } + + if (!gotPrompt) { + return false; + } + + EC200U.print(message); + EC200U.write(26); + + response = ""; + start = millis(); + bool gotResponse = false; + + while (millis() - start < 15000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + if (response.indexOf("+CMGS:") != -1) { + gotResponse = true; + break; + } + if (response.indexOf("ERROR") != -1) return false; + } + if (gotResponse) break; + } + + if (!gotResponse) return false; + + delay(1000); + sendAT("AT+CMGD=1,4", 3000, false); + return true; +} + +// -------------------- پردازش پیامک‌ها -------------------- +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); + + 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]); + } + + 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"); +} + +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..."); + 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"); +} + +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"); + lastMessage = "Already Configured"; + } else if (smsType == 1 && !awaitingSMS2) { + processSMS1(message); + } else if (smsType == 2 && awaitingSMS2) { + processSMS2(message); + } else if (smsType == 2 && !awaitingSMS2 && !config.verified) { + processSMS2(message); + } else { + Serial1.println("❌ Invalid SMS"); + lastMessage = "Invalid SMS"; + } + + String indexStr = response.substring(msgIndex + 6, comma1); + indexStr.trim(); + sendAT("AT+CMGD=" + indexStr, 2000, false); + } + } +} + +// -------------------- اتصال شبکه -------------------- +void initEC200U() { + if (!config.verified) return; + + Serial1.println("\n======== NETWORK CONNECTION ========"); + lastMessage = "Connecting"; + + // اول چک کن آیا مودم از قبل روشن است + Serial1.println("Checking if modem is already running..."); + bool modemAlreadyOn = sendAT("AT", 2000, false); + + if (modemAlreadyOn) { + Serial1.println("✅ Modem is already ON - skipping power cycle"); + } else { + // فقط اگر مودم خاموش است، روشنش کن + Serial1.println("Modem not responding, powering on..."); + digitalWrite(PWRKEY_PIN, HIGH); + Serial1.println(" PWRKEY -> HIGH"); + delay(1500); + digitalWrite(PWRKEY_PIN, LOW); + Serial1.println(" PWRKEY -> LOW"); + Serial1.println("Waiting for modem to boot (5s)..."); + delay(5000); + + if (!sendAT("AT", 3000, false)) { + Serial1.println("❌ Modem not responding after power on!"); + lastMessage = "Modem Err"; + Serial1.println("=================================\n"); + return; + } + Serial1.println("✅ Modem powered on successfully"); + } + + // تنظیم APN + 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++) { // حداکثر 30 ثانیه صبر + 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; + } + + // بررسی وضعیت ثبت‌نام: ,1 = ثبت‌شده در شبکه خانگی، ,5 = ثبت‌شده در رومینگ + 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"); + } + + // غیرفعال کردن PDP context قبلی (اگر وجود داشته باشد) + Serial1.println("Deactivating old PDP context..."); + sendAT("AT+QIDEACT=1", 5000, false); + delay(1000); + + // فعال کردن PDP context جدید + Serial1.println("Activating PDP context..."); + if (sendAT("AT+QIACT=1", 30000, false)) { // افزایش timeout به 30 ثانیه + 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"); + } + } + + // تنظیم SSL برای HTTPS (اگر اتصال موفق بود) + if (networkConnected) { + Serial1.println("Configuring SSL for HTTPS..."); + sendAT("AT+QSSLCFG=\"sslversion\",1,4", 2000, false); // TLS 1.2 + sendAT("AT+QSSLCFG=\"ciphersuite\",1,0xFFFF", 2000, false); // All ciphers + sendAT("AT+QSSLCFG=\"seclevel\",1,0", 2000, false); // No certificate verification + 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"); +} + +// -------------------- بررسی وضعیت سنسورها -------------------- +void checkSensorStatus() { + // بررسی SHT31 (دما و رطوبت) + float temp = sht31.readTemperature(); + float hum = sht31.readHumidity(); + sht31Connected = !isnan(temp) && !isnan(hum) && temp > -40 && temp < 125 && hum >= 0 && hum <= 100; + + // بررسی نور + float lux = lightMeter.readLightLevel(); + lightSensorConnected = !isnan(lux) && lux >= 0; + + // بررسی CO + int adc = analogRead(MQ7_PIN); + coSensorConnected = (adc > 0 && adc < 4095); +} + +// -------------------- دریافت اطلاعات شبکه -------------------- +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)); +} + +// -------------------- لاگ کامل وضعیت سیستم -------------------- +void printFullStatus() { + Serial1.println("\n========== SYSTEM STATUS =========="); + + // وضعیت سنسورها + Serial1.println("--- SENSORS ---"); + Serial1.print(" Temperature: "); + Serial1.print(currentData.temperature, 1); + Serial1.print(" C ["); + Serial1.print(sht31Connected ? "OK" : "ERR"); + Serial1.println("]"); + + Serial1.print(" Humidity: "); + Serial1.print(currentData.humidity, 1); + Serial1.print(" % ["); + Serial1.print(sht31Connected ? "OK" : "ERR"); + Serial1.println("]"); + + Serial1.print(" Light: "); + Serial1.print(currentData.lightLux, 0); + Serial1.print(" lux ["); + Serial1.print(lightSensorConnected ? "OK" : "ERR"); + Serial1.println("]"); + + Serial1.print(" CO: "); + Serial1.print(currentData.coPPM, 1); + Serial1.print(" ppm ["); + Serial1.print(coSensorConnected ? "OK" : "ERR"); + Serial1.print("] Cal:"); + Serial1.println(calibrationComplete ? "Complete" : "In Progress"); + + // وضعیت شبکه + Serial1.println("--- NETWORK ---"); + Serial1.print(" SIM Connected: "); + Serial1.println(simConnected ? "Yes" : "No"); + Serial1.print(" Network Registered:"); + Serial1.println(networkRegistered ? "Yes" : "No"); + Serial1.print(" Signal Strength: "); + Serial1.print(signalStrength); + Serial1.println("/31"); + Serial1.print(" Internet: "); + Serial1.println(networkConnected ? "Online" : "Offline"); + + // وضعیت پیکربندی + Serial1.println("--- CONFIG ---"); + Serial1.print(" Verified: "); + Serial1.println(config.verified ? "Yes" : "No"); + if (config.verified) { + Serial1.print(" Device ID: "); + Serial1.println(config.deviceId); + Serial1.print(" SIM Type: "); + Serial1.println(simTypeToString(config.simType)); + Serial1.print(" Upload Int: "); + Serial1.print(config.uploadInterval); + Serial1.println(" min"); + } + + // وضعیت SMS + Serial1.println("--- SMS STATUS ---"); + Serial1.print(" Awaiting SMS2: "); + Serial1.println(awaitingSMS2 ? "Yes" : "No"); + Serial1.print(" Last Message: "); + Serial1.println(lastMessage); + + Serial1.println("====================================\n"); +} + +// -------------------- خواندن سنسورها با فرمول بهبود یافته -------------------- +void readSensors() { + // خواندن دما و رطوبت + currentData.temperature = sht31.readTemperature(); + currentData.humidity = sht31.readHumidity(); + + // بررسی پایان دوره کالیبراسیون (10 دقیقه) + if (!calibrationComplete && (millis() - systemStartTime >= calibrationPeriod)) { + calibrationComplete = true; + Serial1.println("✅ Calibration period completed!"); + } + + // CO با فرمول جدید و کالیبراسیون + currentData.coPPM = readCOImproved(); + + // نور + currentData.lightLux = lightMeter.readLightLevel(); + if (isnan(currentData.lightLux)) currentData.lightLux = 0; + + // بررسی وضعیت سنسورها + checkSensorStatus(); + + // چاپ وضعیت کامل + printFullStatus(); + + lastSensorRead = millis(); +} + +// -------------------- نمایشگر بهبود یافته -------------------- +void displayAllParameters() { + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + + // عنوان با فونت زیبا + display.setFont(&FreeSans9pt7b); + display.setCursor(8, 12); + display.print("All Parameters"); + + // خط جداکننده + display.drawLine(0, 16, 128, 16, SSD1306_WHITE); + + // دما + display.setFont(&FreeSans9pt7b); + display.setCursor(2, 32); + display.print("T:"); + display.setFont(&FreeSansBold12pt7b); + display.setCursor(22, 33); + display.print(currentData.temperature, 1); + display.setFont(&FreeSans9pt7b); + display.setCursor(85, 32); + display.print("C"); + + // رطوبت + display.setCursor(2, 50); + display.print("H:"); + display.setFont(&FreeSansBold12pt7b); + display.setCursor(22, 51); + display.print(currentData.humidity, 0); + display.setFont(&FreeSans9pt7b); + display.setCursor(60, 50); + display.print("%"); + + // نور + display.setCursor(75, 50); + display.print("L:"); + display.setFont(&FreeSansBold12pt7b); + display.setCursor(95, 51); + display.print((int)currentData.lightLux); + + // برگشت به فونت پیش‌فرض + display.setFont(); + display.display(); +} + +void displayTemperature() { + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + + // عنوان + 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(100, 45); + display.print("C"); + + // علامت درجه + display.drawCircle(95, 30, 3, SSD1306_WHITE); + + // وضعیت سنسور + display.setFont(&FreeSans9pt7b); + display.setCursor(50, 63); + if (sht31Connected) { + display.print("OK"); + } else { + display.print("ERR"); + } + + display.setFont(); + display.display(); +} + +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(85, 48); + display.print("%"); + + // وضعیت سنسور + display.setFont(&FreeSans9pt7b); + display.setCursor(50, 63); + if (sht31Connected) { + display.print("OK"); + } else { + display.print("ERR"); + } + + display.setFont(); + display.display(); +} + +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(90, 48); + display.print("ppm"); + + // وضعیت سنسور و کالیبراسیون + display.setCursor(5, 63); + if (!calibrationComplete) { + display.print("Calibrating..."); + } else if (coSensorConnected) { + display.print("Sensor OK"); + } else { + display.print("Sensor ERR"); + } + + display.setFont(); + display.display(); +} + +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(95, 48); + display.print("lux"); + + // وضعیت سنسور + display.setCursor(50, 63); + if (lightSensorConnected) { + display.print("OK"); + } else { + display.print("ERR"); + } + + display.setFont(); + display.display(); +} + +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(); +} + +void updateDisplay() { + // تغییر صفحه هر 3 ثانیه + if (millis() - lastDisplayChange > 3000) { + displayMode = (displayMode + 1) % 6; // 6 صفحه داریم + lastDisplayChange = millis(); + } + + // به‌روزرسانی وضعیت شبکه هر 30 ثانیه + if (millis() - lastNetworkStatusUpdate > 30000) { + updateNetworkStatus(); + lastNetworkStatusUpdate = millis(); + } + + switch(displayMode) { + case 0: + displayAllParameters(); + break; + case 1: + displayTemperature(); + break; + case 2: + displayHumidity(); + break; + case 3: + displayCO(); + break; + case 4: + displayLight(); + break; + case 5: + displaySIMStatus(); + break; + } +} + +// -------------------- Setup & Loop -------------------- +void setup() { + Serial1.begin(115200); + pinMode(PWRKEY_PIN, OUTPUT); + delay(1000); + + Serial1.println("\n\n🚀 IoT Device Starting..."); + + // ثبت زمان شروع سیستم برای کالیبراسیون CO + systemStartTime = millis(); + + pinMode(MQ7_PIN, INPUT); + // برای MOSFET IRLML2502TRPBF: LOW = خاموش (حالت اولیه) + digitalWrite(PWRKEY_PIN, LOW); + + Wire.setSDA(SDA_PIN); + Wire.setSCL(SCL_PIN); + Wire.begin(); + + if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { + Serial1.println("❌ OLED failed!"); + } + + sht31.begin(0x44); + lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); + + flashInit(); + readConfig(); + + // کالیبراسیون سنسور MQ7 + calibrateMQ7(); + + // همیشه در شروع awaitingSMS2 = false + awaitingSMS2 = false; + tempPhoneNumber = ""; + tempTokenCode = ""; + + EC200U.begin(115200); + + if (config.verified) { + Serial1.println("Device ID: " + String(config.deviceId)); + currentAPN = simTypeToAPN(config.simType); + initEC200U(); + } else { + Serial1.println("⚠️ Not configured - Waiting for SMS"); + } + + // بررسی اولیه وضعیت سنسورها + readSensors(); + + // بررسی اولیه وضعیت شبکه + updateNetworkStatus(); + lastNetworkStatusUpdate = millis(); + + Serial1.println("✅ Ready - CO calibration: " + String(calibrationComplete ? "Complete" : "In progress")); +} + +// -------------------- تنظیم SSL برای HTTPS -------------------- +void configureSSL() { + Serial1.println("Configuring SSL for HTTPS..."); + + // تنظیم SSL context + sendAT("AT+QSSLCFG=\"sslversion\",1,4", 2000, true); // TLS 1.2 + sendAT("AT+QSSLCFG=\"ciphersuite\",1,0xFFFF", 2000, true); // All ciphers + sendAT("AT+QSSLCFG=\"seclevel\",1,0", 2000, true); // No certificate verification + + // تنظیم HTTP برای استفاده از SSL + sendAT("AT+QHTTPCFG=\"sslctxid\",1", 2000, true); + + Serial1.println("✅ SSL configured"); +} + +// -------------------- خواندن پاسخ HTTP -------------------- +String readHttpResponse() { + Serial1.println("Reading HTTP response body..."); + + // درخواست خواندن پاسخ + EC200U.println("AT+QHTTPREAD=80"); + + String response = ""; + String body = ""; + unsigned long start = millis(); + bool inBody = false; + bool gotConnect = false; + + while (millis() - start < 30000) { + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + + // بعد از CONNECT، محتوای بدنه شروع می‌شود + if (!gotConnect && response.indexOf("CONNECT") != -1) { + gotConnect = true; + inBody = true; + body = ""; + // صبر کنید تا CONNECT کامل شود + delay(100); + continue; + } + + if (inBody) { + body += c; + } + } + + if (response.indexOf("+QHTTPREAD: 0") != -1 || response.indexOf("OK") != -1) { + break; + } + if (response.indexOf("ERROR") != -1) { + Serial1.println("❌ Error reading HTTP response"); + return ""; + } + } + + // پاکسازی body از کاراکترهای اضافی + body.trim(); + + // حذف OK و +QHTTPREAD از انتها + int okIdx = body.indexOf("OK"); + if (okIdx != -1) { + body = body.substring(0, okIdx); + } + int qhttpIdx = body.indexOf("+QHTTPREAD"); + if (qhttpIdx != -1) { + body = body.substring(0, qhttpIdx); + } + body.trim(); + + Serial1.print("HTTP Response Body: "); + Serial1.println(body); + + return body; +} + +// -------------------- دانلود فایل AMR -------------------- +bool downloadAMRFile(String url) { + Serial1.println("\n--- Downloading AMR File ---"); + Serial1.print("URL: "); + Serial1.println(url); + + // حذف فایل قبلی اگر وجود دارد + sendAT("AT+QFDEL=\"RAM:audio.amr\"", 2000, false); + delay(100); + + // تنظیم 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; + } + 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; + } + + // ارسال 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("❌ URL not accepted"); + return false; + } + + // دانلود فایل مستقیم به RAM + Serial1.println("Downloading file to RAM..."); + EC200U.println("AT+QHTTPREADFILE=\"RAM: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 download AMR file"); + return false; + } +} + +// -------------------- برقراری تماس صوتی و پخش فایل -------------------- +bool makeVoiceCallWithAudio(String phoneNumber) { + Serial1.println("\n--- Making Voice Call ---"); + Serial1.print("Phone: "); + Serial1.println(phoneNumber); + + // برقراری تماس + String dialCmd = "ATD" + phoneNumber + ";"; + EC200U.println(dialCmd); + + String response = ""; + unsigned long start = millis(); + bool callConnected = false; + + // منتظر وصل شدن تماس + while (millis() - start < 60000) { // 60 ثانیه برای جواب دادن + while (EC200U.available()) { + char c = EC200U.read(); + response += c; + Serial1.write(c); + } + + // بررسی وضعیت تماس + if (response.indexOf("OK") != -1) { + // تماس شروع شد، صبر برای وصل شدن + delay(2000); + + // بررسی وضعیت تماس + EC200U.println("AT+CLCC"); + delay(1000); + + String clccResp = ""; + unsigned long clccStart = millis(); + while (millis() - clccStart < 30000) { + while (EC200U.available()) { + char c = EC200U.read(); + clccResp += c; + Serial1.write(c); + } + + // وضعیت 0 = فعال (تماس برقرار شده) + if (clccResp.indexOf(",0,") != -1) { + callConnected = true; + break; + } + // بررسی اگر تماس قطع شده + if (clccResp.indexOf("NO CARRIER") != -1 || + clccResp.indexOf("BUSY") != -1 || + clccResp.indexOf("NO ANSWER") != -1) { + break; + } + + delay(1000); + EC200U.println("AT+CLCC"); + } + break; + } + + if (response.indexOf("NO CARRIER") != -1 || + response.indexOf("BUSY") != -1 || + response.indexOf("NO ANSWER") != -1 || + response.indexOf("ERROR") != -1) { + Serial1.println("❌ Call failed"); + return false; + } + } + + if (!callConnected) { + Serial1.println("❌ Call not connected"); + sendAT("ATH", 2000, false); // قطع تماس + return false; + } + + Serial1.println("✅ Call connected, playing audio..."); + + // پخش فایل صوتی + // تنظیم خروجی صدا به مسیر تماس + sendAT("AT+QAUDMOD=0", 1000, false); // Audio path to voice call + + // پخش فایل AMR + EC200U.println("AT+QPSND=1,\"RAM:audio.amr\""); + + 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) { // حداکثر 2 دقیقه + 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); + + Serial1.println("✅ Call ended"); + return true; +} + +// -------------------- حذف فایل از RAM -------------------- +void deleteAMRFile() { + Serial1.println("Deleting AMR file from RAM..."); + sendAT("AT+QFDEL=\"RAM:audio.amr\"", 2000, false); + Serial1.println("✅ File deleted"); +} + +// -------------------- پردازش پاسخ سرور -------------------- +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")) { + // نوع TT: ارسال پیامک + Serial1.println("Response Type: TT (Send SMS)"); + + // حذف TT از ابتدا + 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("TY")) { + // نوع TY: تماس صوتی با پخش فایل + Serial1.println("Response Type: TY (Voice Call with Audio)"); + + // حذف TY از ابتدا + 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 && audioUrl.length() > 0) { + // مرحله 1: دانلود فایل AMR + if (downloadAMRFile(audioUrl)) { + // مرحله 2: برقراری تماس و پخش صدا + if (makeVoiceCallWithAudio(phoneNumber)) { + Serial1.println("✅ Voice call completed"); + lastMessage = "TY Call Done"; + } else { + Serial1.println("❌ Voice call failed"); + lastMessage = "TY Call Fail"; + } + + // مرحله 3: حذف فایل + 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"); +} + +// -------------------- ارسال داده به سرور -------------------- +void uploadData() { + Serial1.println("\n======== DATA UPLOAD ========"); + + // بررسی شرایط ارسال + if (!config.verified) { + Serial1.println("❌ Upload skipped: Device not verified"); + Serial1.println("==============================\n"); + return; + } + + if (!networkConnected) { + Serial1.println("⚠️ Not connected, trying to reconnect..."); + initEC200U(); + if (!networkConnected) { + Serial1.println("❌ Upload skipped: No network connection"); + Serial1.println("==============================\n"); + return; + } + } + + // ساخت داده برای ارسال + // برای CO: اگر در حال کالیبراسیون است، قدر مطلق بفرست ولی با پرچم کالیبراسیون + float coToSend = currentData.coPPM; + /*if (coToSend < 0) { + coToSend = -coToSend; // قدر مطلق برای سرور + }*/ + + String data = "deviceName=" + String(config.deviceId) + + "&temperatureC=" + String(currentData.temperature, 1) + + "&humidityPercent=" + String(currentData.humidity, 1) + + "&gasPPM=" + String(coToSend, 0) + + "&lux=" + String(currentData.lightLux, 1); + + String url = String(config.serverUrl) + "/api/Telemetry/AddData?" + data; + + 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("--------------------"); + + // تنظیم SSL برای HTTPS + configureSSL(); + + // مرحله 1: تنظیم URL + 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..."); + + // ارسال 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); + + // مرحله 2: ارسال درخواست GET + 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) { // 65 ثانیه timeout + 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; + + // بررسی کد پاسخ + // فرمت: +QHTTPGET: ,, + int idx = response.indexOf("+QHTTPGET:"); + String httpResult = response.substring(idx); + Serial1.print("\nHTTP Result: "); + Serial1.println(httpResult); + + // بررسی موفقیت: err=0 و httpcode=200 یا 201 + 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"); +} + +void loop() { + checkSMS(); + + // بررسی پایان دوره کالیبراسیون + if (!calibrationComplete && (millis() - systemStartTime >= calibrationPeriod)) { + calibrationComplete = true; + Serial1.println("✅ CO calibration period completed!"); + } + + if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) { + readSensors(); + + // بررسی زمان ارسال + unsigned long uploadIntervalMs = config.uploadInterval * 60000UL; + if (millis() - lastUpload > uploadIntervalMs) { + Serial1.print("Upload interval reached ("); + Serial1.print(config.uploadInterval); + Serial1.println(" min), starting upload..."); + uploadData(); + } + } + + updateDisplay(); + delay(100); +} \ No newline at end of file