#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 SOIL_PIN PA1 #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 #define MAX_STORED_DATA 100 // -------------------- انوم‌ها -------------------- 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; int soilMoisture; 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; SensorData storedData[MAX_STORED_DATA]; // متغیرهای موقت برای پردازش پیامک‌ها String tempPhoneNumber = ""; String tempTokenCode = ""; bool awaitingSMS2 = false; unsigned long lastSensorRead = 0; unsigned long lastUpload = 0; unsigned long lastSMS = 0; unsigned long lastSave = 0; unsigned long lastDisplayChange = 0; unsigned long lastReconnectAttempt = 0; bool networkConnected = false; int displayMode = 0; int connectionAttempts = 0; String lastError = ""; String currentAPN = ""; String deviceUID = ""; String lastMessage = ""; int storedDataCount = 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 to Flash"); } // -------------------- توابع کمکی -------------------- String simTypeToString(SIMType type) { switch(type) { case SIM_HAMRAHE_AVAL: return "HA"; case SIM_IRANCELL: return "IR"; case SIM_RIGHTEL: return "RT"; default: return "UK"; } } 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); Serial1.println("=== Code Calculation ==="); Serial1.println("Token: " + tokenCode); Serial1.println("Result: " + String(verification)); Serial1.println("Formatted: " + String(buffer)); Serial1.println("========================"); return String(buffer); } // -------------------- پردازش 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 = true) { 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, true)) { Serial1.println("❌ Failed to set SMS mode"); 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) { Serial1.println("❌ No prompt received"); 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; Serial1.write(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, true); return true; } // -------------------- پردازش پیامک‌ها -------------------- void processSMS1(String message) { Serial1.println("🔐 PROCESSING SMS1 (Verification)"); // فقط اعداد را به‌ترتیب از متن استخراج می‌کنیم String numbers[10]; int count = 0; extractNumbersFromSMS2(message, numbers, count); String phone = ""; String token = ""; if (count >= 2) { phone = numbers[0]; // شماره تلفن (عدد اول) token = numbers[1]; // کد ورود (عدد دوم) } Serial1.println("Phone: " + phone); Serial1.println("Token: " + token); if (phone.length() >= 10 && token.length() == 5) { tempPhoneNumber = phone; tempTokenCode = token; String verifyCode = generateVerificationCode(token); String reply = "Code: " + verifyCode; if (sendSMS(phone, reply)) { Serial1.println("✅ Verification code sent"); awaitingSMS2 = true; lastMessage = "Code Sent"; Serial1.println("⏳ Now awaiting SMS2..."); Serial1.println("tempPhone: " + tempPhoneNumber); Serial1.println("tempToken: " + tempTokenCode); } } } void processSMS2(String message) { Serial1.println("⚙️ PROCESSING SMS2 (Configuration)"); String numbers[10]; int count = 0; extractNumbersFromSMS2(message, numbers, count); Serial1.print("Numbers: "); for (int i = 0; i < count; i++) { Serial1.print(numbers[i] + " "); } Serial1.println("(Count: " + String(count) + ")"); // پیامک دوم معمولاً ۵ یا ۶ عدد دارد // ترتیب: [0]=نابک, [1]=اینتروال, [2]=پیامک, [3]=سیستم(بی‌استفاده), [4]=سیم, [5]=لغو(بی‌استفاده) if (count >= 5) { // 1. Device ID (نابک) - عدد اول String deviceId = numbers[0]; if (deviceId.length() > 2) { deviceId = deviceId.substring(0, deviceId.length() - 2); } deviceId.toCharArray(config.deviceId, 16); Serial1.println("✓ Device ID: " + String(config.deviceId)); // 2. Upload Interval (اینتروال) - عدد دوم if (numbers[1].length() > 1) { config.uploadInterval = numbers[1].substring(1).toInt(); } else { config.uploadInterval = numbers[1].toInt(); } Serial1.println("✓ Upload Interval: " + String(config.uploadInterval)); // 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.println("✓ SMS: " + String(config.smsEnabled ? "ON" : "OFF") + ", Interval: " + String(config.smsInterval)); // 4. عدد چهارم (سیستم) - بی‌استفاده است، رد می‌شود Serial1.println("✓ System ID (ignored): " + numbers[3]); // 5. SIM Type (سیم) - عدد پنجم (یا اگر ۶ تا بود، پنجم) int simIndex = (count >= 6) ? 4 : (count - 1); // اگر ۶ تا بود index=4، وگرنه آخری String simValue = numbers[simIndex]; // دو رقم آخر را بگیر if (simValue.length() >= 2) { String simCode = simValue.substring(simValue.length() - 2); Serial1.println("✓ SIM Code: " + simCode); 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; } // 6. شماره تلفن (از SMS1 یا اگر نبود، خالی می‌ماند) if (tempPhoneNumber.length() > 0) { tempPhoneNumber.toCharArray(config.serverPhoneNumber, 16); Serial1.println("✓ Server Phone: " + tempPhoneNumber); } else { strcpy(config.serverPhoneNumber, ""); Serial1.println("⚠️ No phone number (SMS1 was skipped)"); } // 7. تایید نهایی config.verified = true; saveConfig(); currentAPN = simTypeToAPN(config.simType); awaitingSMS2 = false; Serial1.println("\n✅✅✅ DEVICE FULLY CONFIGURED! ✅✅✅"); Serial1.println("Device ID: " + String(config.deviceId)); Serial1.println("Phone: " + String(config.serverPhoneNumber)); Serial1.println("Upload Interval: " + String(config.uploadInterval) + " minutes"); Serial1.println("SMS Enabled: " + String(config.smsEnabled ? "YES" : "NO")); Serial1.println("SMS Interval: " + String(config.smsInterval) + " minutes"); Serial1.println("SIM Type: " + simTypeToString(config.simType)); Serial1.println("APN: " + currentAPN); Serial1.println("✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅\n"); lastMessage = "Configured"; // اتصال به شبکه initEC200U(); } else { Serial1.println("❌ Not enough numbers in SMS2 (need at least 5)"); } } void checkSMS() { if (!sendAT("AT", 2000, false)) return; sendAT("AT+CMGF=1", 2000, true); 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; Serial1.write(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📱 NEW SMS: " + message); Serial1.println("Verified: " + String(config.verified)); Serial1.println("Awaiting SMS2: " + String(awaitingSMS2)); // استخراج اعداد برای دیباگ String debugNumbers[10]; int debugCount = 0; extractNumbersFromSMS2(message, debugNumbers, debugCount); Serial1.print("🔢 Numbers found: "); for (int i = 0; i < debugCount; i++) { Serial1.print(debugNumbers[i]); if (i < debugCount - 1) Serial1.print(", "); } Serial1.println(" (Total: " + String(debugCount) + ")"); int smsType = detectSMSType(message); Serial1.println("📋 SMS Type detected: " + String(smsType)); if (config.verified) { Serial1.println("⚠️ Already verified, ignoring"); } else if (smsType == 1 && !awaitingSMS2) { processSMS1(message); } else if (smsType == 2 && awaitingSMS2) { // حالت استاندارد: پیامک اول آمده و الان پیامک دوم processSMS2(message); } else if (smsType == 2 && !awaitingSMS2 && !config.verified) { // حالت مستقیم: پیامک دوم بدون پیامک اول (برای تست یا تنظیم سریع) Serial1.println("⚡ Direct SMS2 (without SMS1) - Processing anyway"); processSMS2(message); } else { Serial1.println("❌ Wrong SMS type or state"); Serial1.println(" Expected: SMS type=" + String(awaitingSMS2 ? "2" : "1") + ", Got: " + String(smsType)); } String indexStr = response.substring(msgIndex + 6, comma1); indexStr.trim(); sendAT("AT+CMGD=" + indexStr, 2000, true); } } } // -------------------- اتصال شبکه -------------------- void initEC200U() { if (!config.verified) return; Serial1.println("📡 Connecting to network..."); lastMessage = "Connecting"; digitalWrite(PWRKEY_PIN, HIGH); delay(2000); digitalWrite(PWRKEY_PIN, LOW); delay(5000); if (!sendAT("AT", 3000)) { Serial1.println("❌ Modem error"); return; } currentAPN = simTypeToAPN(config.simType); String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + currentAPN + "\""; sendAT(apnCmd, 2000); if (sendAT("AT+QIACT=1", 15000)) { networkConnected = true; lastMessage = "Connected"; Serial1.println("✅ Network connected"); } else { networkConnected = false; Serial1.println("❌ Network failed"); } } // -------------------- سنسورها -------------------- void readSensors() { currentData.temperature = sht31.readTemperature(); currentData.humidity = sht31.readHumidity(); // خاک int adc = analogRead(SOIL_PIN); float soil = (4095 - adc) / (4095.0 - 1200.0) * 100.0; if (soil > 100) soil = 100; if (soil < 0) soil = 0; currentData.soilMoisture = (int)soil; // CO long sum = 0; for (int i = 0; i < 10; i++) { sum += analogRead(MQ7_PIN); delay(5); } float voltage = (sum / 10.0 / 4095.0) * 3.3; float ppm; if (voltage < 0.1) ppm = 0; else if (voltage < 0.2) ppm = 10 * (voltage / 0.2); else if (voltage < 0.5) ppm = 50 + (voltage - 0.2) * 300; else if (voltage < 1.0) ppm = 140 + (voltage - 0.5) * 800; else if (voltage < 2.0) ppm = 500 + (voltage - 1.0) * 1500; else ppm = 2000 + (voltage - 2.0) * 2000; if (ppm > 10000) ppm = 10000; currentData.coPPM = ppm; // نور currentData.lightLux = lightMeter.readLightLevel(); if (isnan(currentData.lightLux)) currentData.lightLux = 0; lastSensorRead = millis(); } // -------------------- نمایشگر -------------------- void updateDisplay() { if (millis() - lastDisplayChange > 5000) { displayMode = (displayMode + 1) % 3; lastDisplayChange = millis(); } display.clearDisplay(); display.setTextSize(1); switch(displayMode) { case 0: display.setCursor(0, 0); if (config.verified) { display.println("ID: " + String(config.deviceId)); display.println("SIM: " + simTypeToString(config.simType)); display.println("NET: " + String(networkConnected ? "OK" : "NO")); } else if (awaitingSMS2) { display.println("AWAITING SMS2"); display.println("Phone: " + tempPhoneNumber); } else { display.println("WAIT SMS1"); display.println("Send: NUM#CODE"); } break; case 1: display.setCursor(0, 0); display.print("T:"); display.print(currentData.temperature, 1); display.print("C H:"); display.print(currentData.humidity, 1); display.println("%"); display.print("S:"); display.print(currentData.soilMoisture); display.print("% C:"); display.print(currentData.coPPM, 0); display.println("PPM"); display.print("L:"); display.print(currentData.lightLux, 0); display.println("Lx"); break; case 2: display.setTextSize(2); display.setCursor(0, 20); display.print(currentData.temperature, 1); display.println("C"); break; } display.display(); } // -------------------- Setup & Loop -------------------- void setup() { Serial1.begin(115200); delay(1000); Serial1.println("\n\n🚀 IoT Device Starting..."); pinMode(PWRKEY_PIN, OUTPUT); pinMode(SOIL_PIN, INPUT); pinMode(MQ7_PIN, INPUT); digitalWrite(PWRKEY_PIN, LOW); Wire.setSDA(SDA_PIN); Wire.setSCL(SCL_PIN); Wire.begin(); if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial1.println("❌ OLED failed!"); } sht31.begin(0x44); lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); flashInit(); readConfig(); // همیشه در شروع awaitingSMS2 = false awaitingSMS2 = false; tempPhoneNumber = ""; tempTokenCode = ""; EC200U.begin(115200); Serial1.println("\n=== INITIAL STATUS ==="); Serial1.println("Verified: " + String(config.verified)); Serial1.println("Awaiting SMS2: " + String(awaitingSMS2)); Serial1.println("====================="); if (config.verified) { currentAPN = simTypeToAPN(config.simType); initEC200U(); } Serial1.println("✅ Ready"); } void loop() { checkSMS(); if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) { readSensors(); if (config.verified && networkConnected) { if (millis() - lastUpload > config.uploadInterval * 60000) { // ارسال داده به API String data = "deviceId=" + String(config.deviceId) + "&temp=" + String(currentData.temperature, 1) + "&hum=" + String(currentData.humidity, 1) + "&soil=" + String(currentData.soilMoisture) + "&co=" + String(currentData.coPPM, 0) + "&lux=" + String(currentData.lightLux, 1); String url = String(config.serverUrl) + "/api/Telemetry/AddData?" + data; String cmd = "AT+QHTTPURL=" + String(url.length()) + ",80"; if (sendAT(cmd, 5000)) { delay(100); EC200U.print(url); if (sendAT("", 5000)) { if (sendAT("AT+QHTTPGET=60", 10000)) { lastUpload = millis(); lastMessage = "Data Sent"; } } } } } } updateDisplay(); delay(1000); }