#include #include #include #include #include #include #include // -------------------- پیکربندی حافظه SPI Flash -------------------- #define FLASH_CS PA4 #define FLASH_MOSI PA7 #define FLASH_MISO PA6 #define FLASH_SCK PA5 // کتابخانه جایگزین برای حافظه SPI #include // -------------------- پیکربندی OLED -------------------- #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // -------------------- پیکربندی سنسورها -------------------- #define SOIL_PIN PA1 #define MQ7_PIN PA2 #define SDA_PIN PB9 #define SCL_PIN PB8 BH1750 lightMeter; Adafruit_SHT31 sht31 = Adafruit_SHT31(); // -------------------- پیکربندی EC200U -------------------- #define PWRKEY_PIN PB5 HardwareSerial EC200U(USART3); // -------------------- پارامترهای قابل تنظیم -------------------- #define SENSOR_READ_INTERVAL 60000 // خواندن سنسورها هر 1 دقیقه // -------------------- ساختار تنظیمات -------------------- struct DeviceConfig { char signature[4]; char apn[32]; char serverUrl[64]; char deviceName[16]; char smsNumber[16]; unsigned long webInterval; // دقیقه unsigned long smsInterval; // دقیقه unsigned long saveInterval; // ثانیه - فاصله ذخیره در قطعی bool smsEnabled; bool valid; }; // -------------------- ساختار داده سنسورها -------------------- struct SensorData { float temperature; float humidity; int soilMoisture; float coPPM; float lightLux; unsigned long timestamp; }; // -------------------- متغیرهای سراسری -------------------- DeviceConfig config; SensorData currentData; SensorData storedData; unsigned long lastSensorRead = 0; unsigned long lastUpload = 0; unsigned long lastSMS = 0; unsigned long lastSave = 0; bool networkConnected = false; bool dataStored = false; int displayMode = 0; unsigned long lastDisplayChange = 0; // -------------------- توابع حافظه SPI Flash -------------------- void initFlash() { SPI.setMOSI(FLASH_MOSI); SPI.setMISO(FLASH_MISO); SPI.setSCLK(FLASH_SCK); SPI.begin(); pinMode(FLASH_CS, OUTPUT); digitalWrite(FLASH_CS, HIGH); delay(100); // مقداردهی اولیه SerialFlash if (!SerialFlash.begin(FLASH_CS)) { Serial1.println("❌ Flash init failed!"); return; } Serial1.println("✅ Flash OK"); // نمایش اطلاعات حافظه SerialFlash::ID id; SerialFlash.readID(&id); Serial1.print("Flash ID: "); Serial1.print(id.id, HEX); Serial1.print(" Size: "); Serial1.print(SerialFlash.capacity(id) / 1024); Serial1.println("KB"); } void readConfig() { // ابتدا از EEPROM داخلی بخوان EEPROM.get(0, config); // اگر تنظیمات معتبر نیست، از حافظه SPI بخوان if (strcmp(config.signature, "CFG") != 0) { if (SerialFlash.exists("config.bin")) { SerialFlashFile file = SerialFlash.open("config.bin"); if (file) { file.read(&config, sizeof(DeviceConfig)); file.close(); Serial1.println("✅ Config read from SPI Flash"); } } } // اگر هنوز تنظیمات معتبر نیست، مقادیر پیش‌فرض if (strcmp(config.signature, "CFG") != 0) { strcpy(config.signature, "CFG"); strcpy(config.apn, "mcinet"); strcpy(config.serverUrl, "https://ghback.nabaksoft.ir/api/Telemetry/AddData"); strcpy(config.smsNumber, "+989120000000"); strcpy(config.deviceName, "ST-"); config.webInterval = 1; config.smsInterval = 5; config.saveInterval = 60; // پیش‌فرض 60 ثانیه config.smsEnabled = false; config.valid = false; } } void saveConfig() { config.valid = true; // در EEPROM داخلی ذخیره کن EEPROM.put(0, config); EEPROM.commit(); // در حافظه SPI هم ذخیره کن if (SerialFlash.exists("config.bin")) { SerialFlash.remove("config.bin"); } SerialFlashFile file = SerialFlash.create("config.bin", sizeof(DeviceConfig)); if (file) { file.write((byte*)&config, sizeof(DeviceConfig)); file.close(); Serial1.println("✅ Config saved to SPI Flash"); } } void saveDataToFlash() { if (!dataStored) { if (SerialFlash.exists("data.bin")) { SerialFlash.remove("data.bin"); } dataStored = true; } currentData.timestamp = millis(); SerialFlashFile file = SerialFlash.open("data.bin"); if (!file) { file = SerialFlash.create("data.bin", sizeof(SensorData)); } if (file) { file.seek(0); file.write((byte*)¤tData, sizeof(SensorData)); file.close(); Serial1.println("💾 Data saved to flash"); } } void readDataFromFlash() { if (SerialFlash.exists("data.bin")) { SerialFlashFile file = SerialFlash.open("data.bin"); if (file) { file.read(&storedData, sizeof(SensorData)); file.close(); // بررسی اعتبار داده if (storedData.timestamp > 0) { Serial1.println("📖 Stored data found"); } } } } void clearStoredData() { if (SerialFlash.exists("data.bin")) { SerialFlash.remove("data.bin"); storedData.timestamp = 0; dataStored = false; } } // -------------------- توابع EC200U -------------------- bool sendAT(String cmd, uint16_t wait = 2000) { Serial1.print(">> "); Serial1.println(cmd); EC200U.println(cmd); String response = ""; unsigned long start = millis(); while (millis() - start < wait) { while (EC200U.available()) { char c = EC200U.read(); response += c; Serial1.write(c); } if (response.indexOf("OK") != -1) return true; if (response.indexOf("ERROR") != -1) return false; } return false; } bool waitForResponse(String expected, unsigned long timeout = 10000) { unsigned long startTime = millis(); String response = ""; while (millis() - startTime < timeout) { while (EC200U.available()) { char c = EC200U.read(); response += c; Serial1.write(c); if (response.indexOf(expected) != -1) return true; if (response.indexOf("ERROR") != -1) return false; } } return false; } bool sendSMS(String number, String message) { if (!sendAT("AT+CMGF=1", 2000)) return false; String cmd = "AT+CMGS=\"" + number + "\""; if (!sendAT(cmd, 2000)) return false; delay(100); EC200U.print(message); EC200U.write(26); return waitForResponse("+CMGS:", 10000); } void initEC200U() { Serial1.println("🚀 Starting EC200U..."); digitalWrite(PWRKEY_PIN, HIGH); delay(2000); digitalWrite(PWRKEY_PIN, LOW); delay(5000); if (!sendAT("AT", 3000)) { Serial1.println("❌ EC200U not responding!"); return; } String apnCmd = "AT+CGDCONT=1,\"IP\",\"" + String(config.apn) + "\""; sendAT(apnCmd, 2000); if (sendAT("AT+QIACT=1", 15000)) { networkConnected = true; Serial1.println("✅ Network connected"); // اگر داده ذخیره شده وجود دارد، ارسال کن if (storedData.timestamp > 0) { Serial1.println("📤 Sending stored data..."); sendStoredData(); } } else { networkConnected = false; Serial1.println("❌ Network failed"); } } // -------------------- توابع سنسورها -------------------- void readSensors() { currentData.temperature = sht31.readTemperature(); currentData.humidity = sht31.readHumidity(); currentData.soilMoisture = readSoil(); currentData.coPPM = MQ7_ReadPPM(); currentData.lightLux = lightMeter.readLightLevel(); if (isnan(currentData.temperature)) currentData.temperature = -999; if (isnan(currentData.humidity)) currentData.humidity = -999; if (isnan(currentData.lightLux)) currentData.lightLux = 0; lastSensorRead = millis(); } int readSoil() { int adcValue = analogRead(SOIL_PIN); const int adcDry = 4095; const int adcWet = 1200; float soilPercent = (float)(adcDry - adcValue) / (adcDry - adcWet) * 100.0; if (soilPercent > 100.0) soilPercent = 100.0; if (soilPercent < 0.0) soilPercent = 0.0; return (int)soilPercent; } float MQ7_ReadPPM() { long sum = 0; for (int i = 0; i < 10; i++) { sum += analogRead(MQ7_PIN); delay(5); } float adcValue = sum / 10.0; float voltage = (adcValue / 4095.0) * 3.3; float ppm; if (voltage < 0.1) ppm = 0; else if (voltage < 0.2) ppm = 10 * (voltage / 0.2); else if (voltage < 0.5) ppm = 50 + (voltage - 0.2) * 300; else if (voltage < 1.0) ppm = 140 + (voltage - 0.5) * 800; else if (voltage < 2.0) ppm = 500 + (voltage - 1.0) * 1500; else ppm = 2000 + (voltage - 2.0) * 2000; if (ppm < 0) ppm = 0; if (ppm > 10000) ppm = 10000; return ppm; } // -------------------- توابع ارسال داده -------------------- bool sendData(SensorData data) { if (data.temperature == -999 || data.humidity == -999) return false; String dataStr = "temperatureC=" + String(data.temperature, 1) + "&humidityPercent=" + String(data.humidity, 1) + "&soilPercent=" + String(data.soilMoisture) + "&gasPPM=" + String(data.coPPM, 0) + "&lux=" + String(data.lightLux, 1); String url = String(config.serverUrl) + "?deviceName=" + String(config.deviceName) + "&" + dataStr; String httpCmd = "AT+QHTTPURL=" + String(url.length()) + ",80"; if (!sendAT(httpCmd, 5000)) return false; delay(100); EC200U.print(url); if (!waitForResponse("OK", 5000)) return false; return sendAT("AT+QHTTPGET=60", 15000); } void sendStoredData() { if (sendData(storedData)) { Serial1.println("✅ Stored data sent successfully"); clearStoredData(); } } // -------------------- توابع نمایش OLED -------------------- void updateDisplay() { if (millis() - lastDisplayChange > 5000) { displayMode = (displayMode + 1) % 3; lastDisplayChange = millis(); } display.clearDisplay(); display.setTextSize(1); switch(displayMode) { case 0: display.setTextSize(2); display.setCursor(20, 10); display.println("DEVICE"); display.setCursor(30, 35); display.println(config.deviceName); break; case 1: display.setCursor(0, 0); display.print("T:"); display.print(currentData.temperature, 1); display.print("C "); display.print("H:"); display.print(currentData.humidity, 1); display.println("%"); display.setCursor(0, 16); display.print("S:"); display.print(currentData.soilMoisture); display.print("% "); display.print("C:"); display.print(currentData.coPPM, 0); display.println("P"); display.setCursor(0, 32); display.print("L:"); display.print(currentData.lightLux, 0); display.println("Lx"); display.setCursor(0, 48); display.print(networkConnected ? "ONLINE" : "OFFLINE"); if (dataStored) display.print(" *"); break; case 2: display.setTextSize(3); display.setCursor(10, 25); display.print(currentData.temperature, 1); display.setTextSize(2); display.setCursor(100, 30); display.print("C"); break; } display.display(); } // -------------------- setup -------------------- void setup() { Serial1.begin(115200); delay(1000); Serial1.println("\n\n🚀 System Starting..."); pinMode(PWRKEY_PIN, OUTPUT); pinMode(SOIL_PIN, INPUT); pinMode(MQ7_PIN, INPUT); digitalWrite(PWRKEY_PIN, LOW); Wire.setSDA(SDA_PIN); Wire.setSCL(SCL_PIN); Wire.begin(); if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial1.println("❌ OLED failed!"); } sht31.begin(0x44); lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE); // مقداردهی حافظه initFlash(); readConfig(); readDataFromFlash(); // اگر نام دستگاه تنظیم نشده، از UID استفاده کن if (strlen(config.deviceName) < 3) { uint32_t uid0 = *(uint32_t*)0x1FFFF7E8; uint32_t uid1 = *(uint32_t*)0x1FFFF7EC; uint32_t uid2 = *(uint32_t*)0x1FFFF7F0; sprintf(config.deviceName, "ST-%08X", (unsigned int)(uid0 ^ uid1 ^ uid2)); saveConfig(); } EC200U.begin(115200); if (config.valid) { initEC200U(); } Serial1.println("✅ System Ready"); } // -------------------- loop -------------------- void loop() { // خواندن سنسورها در بازه زمانی مشخص if (millis() - lastSensorRead > SENSOR_READ_INTERVAL) { readSensors(); Serial1.println("📊 Sensors read"); // اگر شبکه وصل است، داده را ارسال کن if (networkConnected) { if (millis() - lastUpload > config.webInterval * 60000) { if (sendData(currentData)) { Serial1.println("✅ Web data sent"); lastUpload = millis(); } } } // اگر شبکه قطع است، داده را ذخیره کن else { if (config.saveInterval > 0) { if (millis() - lastSave > config.saveInterval * 1000) { saveDataToFlash(); lastSave = millis(); } } // ارسال SMS در صورت فعال بودن if (config.smsEnabled && millis() - lastSMS > config.smsInterval * 60000) { String smsMsg = String(config.deviceName) + " T:" + String(currentData.temperature, 1) + " H:" + String(currentData.humidity, 1) + " S:" + String(currentData.soilMoisture) + "%"; if (sendSMS(config.smsNumber, smsMsg)) { Serial1.println("✅ SMS sent"); lastSMS = millis(); } } } } updateDisplay(); // تلاش برای اتصال مجدد شبکه static unsigned long lastReconnect = 0; if (!networkConnected && millis() - lastReconnect > 60000) { initEC200U(); lastReconnect = millis(); } delay(1000); }