☰
المستخدم: مدير النظام
قاعدة بيانات الوحدات
عدد الوحدات: 1
وحدة جديدة
RealModule1
ID: 1770759351
2026/02/10 · 01:08
اسم الوحدة
الوصف
الإصدار
حذف
حفظ
الأيقونة
اللون
// ========================================= // نظام مراقبة بيئي ذكي باستخدام ESP32 // المتطلبات: WiFi + DS18B20 (عدد 2) + DHT11 + TEMT6000 + MQ-135 + LCD I2C + SPIFFS + HTTP POST + ويب سيرفر محلي // المكتبات المستخدمة (المتاحة): // Arduino.h, Wire.h, LiquidCrystal_I2C.h, OneWire.h, DallasTemperature.h, WiFi.h, HTTPClient.h, SPIFFS.h, DHT.h // ملاحظات: // - استخدام HTTPS يتطلب عميل آمن؛ نستخدم WiFiClientSecure (ضمن نواة ESP32) بوضع غير صارم setInsecure لأغراض الاختبار. // - الكود منظم ومقسّم لأجزاء مستقلة مع تعليقات عربية. // ========================================= #include <Arduino.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <OneWire.h> #include <DallasTemperature.h> #include <WiFi.h> #include <HTTPClient.h> #include <SPIFFS.h> #include <DHT.h> #include <WiFiClientSecure.h> // ========================================= // 1) إعدادات عامة // ========================================= // بيانات الشبكة (عدّلها قبل الرفع) const char* WIFI_SSID = "apally-4"; const char* WIFI_PASS = "Apally@703388@2024"; // رابط الـ API const char* API_URL = "https://sv2-12026.ai-dawajen.tech/api.php"; // ملف التخزين المحلي const char* DATA_FILE = "/data.txt"; // فواصل زمنية const unsigned long SEND_INTERVAL_MS = 5000; // إرسال كل 5 ثواني const unsigned long WIFI_RETRY_MS = 5000; // إعادة المحاولة للواي فاي const unsigned long DS_DELAY_MS = 750; // تأخير مناسب لـ DS18B20 const unsigned long DHT_INTERVAL_MS = 2000; // قراءة DHT كل 2 ثانية const unsigned long ADC_INTERVAL_MS = 1000; // قراءات الـ ADC كل 1 ثانية const unsigned long MQ_CALIB_TIME_MS = 3000; // زمن معايرة أولية لـ MQ-135 // ========================================= // 2) تعريف الأرجل (GPIO) والأجهزة // ========================================= #define DS18B20_PIN_1 4 #define DS18B20_PIN_2 5 #define DHT_PIN 27 #define DHT_TYPE DHT11 #define TEMT_PIN 34 #define MQ135_PIN 35 // LCD I2C على العنوان 0x27, SDA=21, SCL=22 LiquidCrystal_I2C lcd(0x27, 16, 2); // OneWire + DallasTemperature لحساسي DS18B20 OneWire oneWire1(DS18B20_PIN_1); DallasTemperature ds1(&oneWire1); OneWire oneWire2(DS18B20_PIN_2); DallasTemperature ds2(&oneWire2); // حساس DHT11 DHT dht(DHT_PIN, DHT_TYPE); // ويب سيرفر محلي مبسط (باستخدام WiFiServer مباشرة لتجنب مكتبات إضافية) WiFiServer webServer(80); // ========================================= // 3) متغيرات الحالة // ========================================= // حالة الشبكة bool wifiConnected = false; bool wifiWasConnected = false; unsigned long lastWiFiRetry = 0; // إدارة شاشة LCD لتقليل الوميض (التحديث فقط عند تغير النص) String lastLCDLine1 = ""; String lastLCDLine2 = ""; // جدولة الإرسال unsigned long lastSendMillis = 0; // جدولة قراءات المستشعرات unsigned long dsRequestMillis = 0; bool dsRequested = false; unsigned long lastDhtMillis = 0; unsigned long lastAdcMillis = 0; // قياسات حديثة + آخر قياسات سليمة float lastTempValid = NAN; float lastHumidityValid = NAN; float lastLuxValid = NAN; float lastNH3Valid = NAN; // معايرة MQ-135 (تقريبية) const float VCC = 3.3f; const float RL_MQ135 = 10000.0f; // المقاومة الحمل (تقريبية 10 كيلو) float RO_MQ135 = 10000.0f; // قيمة RO (تُحسب بالمعايرة) bool mqCalibrated = false; unsigned long mqCalibStart = 0; float mqCalibAccumRS = 0.0f; int mqCalibSamples = 0; const float CLEAN_AIR_FACTOR = 3.6f; // عامل الهواء النقي: RS/RO تقريبياً // معامل تحويل NH3 من منحنى تقريبي: RS/Ro = a * (ppm)^b => ppm = pow((RS/Ro)/a, 1/b) // هذه معاملات تقريبية ويمكن تعديلها ميدانياً حسب المعايرة في المكان. const float MQ135_A_NH3 = 4.5f; // معامل a (تقريبي) const float MQ135_B_NH3 = -0.6f; // معامل b (تقريبي، سالب) // بنية قياس موحدة struct Measurement { float temperature; // متوسط DS18B20 float humidity; // DHT11 مع تعويض +10% والحد 100% float lux; // من TEMT6000 (تحويل جهد -> لوكس، حد أقصى 1000) float ammonia; // NH3 من MQ-135 بشكل تقريبي unsigned long ts; // توقيت القراءة }; Measurement currentMeas = {NAN, NAN, NAN, NAN, 0}; // ========================================= // 4) دوال مساعدة (بدون إيقاف loop) // ========================================= float clampFloat(float v, float lo, float hi) { if (isnan(v)) return v; if (v < lo) return lo; if (v > hi) return hi; return v; } bool isTempValid(float t) { if (isnan(t)) return false; if (t == DEVICE_DISCONNECTED_C) return false; // تجاهل القيم غير المنطقية return (t > -10.0f && t < 85.0f); } // تحديث شاشة LCD بدون وميض void lcdPrintStable(const String& line1, const String& line2) { if (line1 != lastLCDLine1) { lcd.setCursor(0, 0); lcd.print(" "); lcd.setCursor(0, 0); lcd.print(line1); lastLCDLine1 = line1; } if (line2 != lastLCDLine2) { lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 1); lcd.print(line2); lastLCDLine2 = line2; } } // حالة الشبكة لعرض LCD فقط void updateLCDWifiState(bool connected, bool sending) { // الحالة المطلوبة: "WiFi ON SEND" / "WiFi OFF SAVE" String s1 = connected ? "WiFi ON SEND" : "WiFi OFF SAVE"; // السطر الثاني يُترك فارغاً لتجنّب ظهور كلمات إضافية lcdPrintStable(s1, " "); } // محاولة اتصال WiFi بدون إيقاف اللووب void tryConnectWiFi() { if (WiFi.status() == WL_CONNECTED) { wifiConnected = true; return; } wifiConnected = false; unsigned long now = millis(); if (now - lastWiFiRetry >= WIFI_RETRY_MS) { WiFi.disconnect(true); WiFi.begin(WIFI_SSID, WIFI_PASS); lastWiFiRetry = now; } } // تحويل قراءة TEMT6000 إلى لوكس (تقريبية) float readLuxApprox() { int raw = analogRead(TEMT_PIN); // 12-bit: 0..4095 float voltage = (float)raw * (VCC / 4095.0f); float lux = (voltage / VCC) * 1000.0f; // تحويل خطي بسيط (تقريبي) lux = clampFloat(lux, 0.0f, 1000.0f); return lux; } // حساب RS من فولتية الخرج bool calcRSFromVoltage(float vo, float& rs) { if (vo <= 0.01f) return false; // تجنب القسمة على صفر float ratio = (VCC / vo) - 1.0f; if (ratio <= 0.0f) return false; rs = RL_MQ135 * ratio; return true; } // معايرة أولية لـ MQ-135 في الهواء النقي (غير مانعة للّووب) void handleMQCalibration() { if (mqCalibrated) return; unsigned long now = millis(); if (mqCalibStart == 0) mqCalibStart = now; // جمع عينات خلال نافذة المعايرة if (now - mqCalibStart <= MQ_CALIB_TIME_MS) { int raw = analogRead(MQ135_PIN); float vo = (float)raw * (VCC / 4095.0f); float rs; if (calcRSFromVoltage(vo, rs)) { mqCalibAccumRS += rs; mqCalibSamples++; } } else { // حساب RO على أساس عامل الهواء النقي if (mqCalibSamples > 0) { float avgRS = mqCalibAccumRS / (float)mqCalibSamples; RO_MQ135 = avgRS / CLEAN_AIR_FACTOR; } mqCalibrated = true; } } // تقدير NH3 ppm من MQ-135 (تقريبي جداً) float estimateNH3ppm() { int raw = analogRead(MQ135_PIN); float vo = (float)raw * (VCC / 4095.0f); float rs; if (!calcRSFromVoltage(vo, rs)) return NAN; if (RO_MQ135 <= 1.0f) return NAN; // تجنب القيم غير المنطقية float rs_ro = rs / RO_MQ135; // نموذج: RS/Ro = a * ppm^b => ppm = pow((RS/Ro)/a, 1/b) float ppm = pow((rs_ro / MQ135_A_NH3), (1.0f / MQ135_B_NH3)); if (!isnan(ppm)) { // ضبط نطاق معقول للمزارع (اختياري) ppm = clampFloat(ppm, 0.0f, 100.0f); } return ppm; } // طلب تحويل درجات حرارة DS (غير مانع) void requestDSIfNeeded() { if (!dsRequested) { ds1.requestTemperatures(); ds2.requestTemperatures(); dsRequested = true; dsRequestMillis = millis(); } } // قراءة درجات حرارة DS بعد تأخير مناسب bool readDSTemperatures(float& avg) { if (!dsRequested) return false; if (millis() - dsRequestMillis < DS_DELAY_MS) return false; // الحصول على القراءات float t1 = ds1.getTempCByIndex(0); float t2 = ds2.getTempCByIndex(0); float sum = 0.0f; int cnt = 0; if (isTempValid(t1)) { sum += t1; cnt++; } if (isTempValid(t2)) { sum += t2; cnt++; } if (cnt > 0) { avg = sum / (float)cnt; lastTempValid = avg; } else { // لا قراءات منطقية: استخدم آخر قيمة سليمة إن وجدت if (!isnan(lastTempValid)) avg = lastTempValid; else avg = NAN; } dsRequested = false; // جاهزون للطلب التالي return true; } // قراءة DHT11 (رطوبة مع تعويض +10% وحد أقصى 100%) void readDHTIfDue() { unsigned long now = millis(); if (now - lastDhtMillis >= DHT_INTERVAL_MS) { float h = dht.readHumidity(); if (!isnan(h)) { h = h + 10.0f; // تعويض +10% h = clampFloat(h, 0.0f, 100.0f); lastHumidityValid = h; } lastDhtMillis = now; } } // قراءة ADC (TEMT6000 + MQ135) void readADCIfDue() { unsigned long now = millis(); if (now - lastAdcMillis >= ADC_INTERVAL_MS) { float lux = readLuxApprox(); if (!isnan(lux)) lastLuxValid = lux; float nh3 = estimateNH3ppm(); if (!isnan(nh3)) lastNH3Valid = nh3; lastAdcMillis = now; } } // بناء حمولة JSON للإرسال String buildJSON(float temp, float hum, float lux, float nh3) { String payload = "{"; payload += "\"temperature\": " + String(temp, 2) + ", "; payload += "\"humidity\": " + String(hum, 2) + ", "; payload += "\"lux\": " + String(lux, 0) + ", "; payload += "\"ammonia\": " + String(nh3, 2); payload += "}"; return payload; } // إضافة سطر إلى ملف التخزين المحلي void appendOffline(const String& line) { File f = SPIFFS.open(DATA_FILE, FILE_APPEND); if (!f) return; f.println(line); f.close(); } // إرسال حمولة JSON عبر HTTP POST (HTTPS غير صارم) bool postJSON(const String& payload) { WiFiClientSecure client; client.setInsecure(); // لأغراض الاختبار بدون شهادة HTTPClient http; if (!http.begin(client, API_URL)) { return false; } http.addHeader("Content-Type", "application/json"); http.setTimeout(6000); // مهلة مناسبة int code = http.POST(payload); bool ok = (code >= 200 && code < 300); http.end(); return ok; } // تفريغ البيانات المخزنة أولاً عند توفر الإنترنت bool flushOfflineBuffer() { if (!SPIFFS.exists(DATA_FILE)) return true; File f = SPIFFS.open(DATA_FILE, "r"); if (!f) return false; bool allSent = true; while (f.available()) { String line = f.readStringUntil('\n'); line.trim(); if (line.length() == 0) continue; if (!postJSON(line)) { allSent = false; break; } // بدون delay للحفاظ على سلاسة loop() updateLCDWifiState(true, true); } f.close(); if (allSent) { SPIFFS.remove(DATA_FILE); // حذف الملف بعد نجاح الإرسال } return allSent; } // خدمة ويب محلية مبسطة تعرض القراءات (HTML/JSON) void handleWebServer() { WiFiClient client = webServer.available(); if (!client) return; client.setTimeout(200); String reqLine = client.readStringUntil('\n'); String path = "/"; if (reqLine.startsWith("GET ")) { int start = 4; int end = reqLine.indexOf(' ', start); if (end > start) path = reqLine.substring(start, end); } // تجاهل بقية الرؤوس while (client.available()) client.read(); if (path == "/json") { String payload = buildJSON(currentMeas.temperature, currentMeas.humidity, currentMeas.lux, currentMeas.ammonia); client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print(payload); } else { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html; charset=utf-8"); client.println("Connection: close"); client.println(); client.print("<!DOCTYPE html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>"); client.print("<meta http-equiv='refresh' content='5'>"); client.print("<style>body{font-family:Arial;margin:16px} .box{border:1px solid #ccc;padding:12px;border-radius:8px} .v{font-weight:bold}</style>"); client.print("</head><body>"); client.print("<h2>نظام مراقبة بيئي محلي (ESP32)</h2>"); client.print("<div class='box'>"); client.print("<p>حالة الشبكة: <span class='v'>"); client.print(wifiConnected ? "متصل" : "غير متصل"); client.print("</span></p>"); client.print("<p>درجة الحرارة (°C): <span class='v'>"); client.print(isnan(currentMeas.temperature) ? String("N/A") : String(currentMeas.temperature, 2)); client.print("</span></p>"); client.print("<p>الرطوبة (%): <span class='v'>"); client.print(isnan(currentMeas.humidity) ? String("N/A") : String(currentMeas.humidity, 2)); client.print("</span></p>"); client.print("<p>الإضاءة (Lux): <span class='v'>"); client.print(isnan(currentMeas.lux) ? String("N/A") : String(currentMeas.lux, 0)); client.print("</span></p>"); client.print("<p>الأمونيا NH3 (ppm): <span class='v'>"); client.print(isnan(currentMeas.ammonia) ? String("N/A") : String(currentMeas.ammonia, 2)); client.print("</span></p>"); client.print("<p>آخر تحديث: <span class='v'>"); client.print(String((millis() - currentMeas.ts) / 1000)); client.print(" ثانية مضت</span></p>"); client.print("<p>رابط JSON: <a href='/json'>/json</a></p>"); client.print("</div></body></html>"); } client.stop(); } // ========================================= // 5) الإعداد (setup) // ========================================= void setup() { // تسريع سيريال للتشخيص (اختياري) Serial.begin(115200); Serial.println(); Serial.println("ESP32 EnvMonitor booting..."); // إعداد الأرجل لحساسات DS بدون مقاومة خارجية باستخدام INPUT_PULLUP pinMode(DS18B20_PIN_1, INPUT_PULLUP); pinMode(DS18B20_PIN_2, INPUT_PULLUP); // بدء I2C على SDA=21, SCL=22 Wire.begin(21, 22); // تهيئة LCD lcd.init(); lcd.backlight(); updateLCDWifiState(false, false); // تهيئة DHT dht.begin(); // تهيئة DallasTemperature ds1.begin(); ds2.begin(); // عدم الانتظار للتحويل؛ سنؤقّت 750ms يدوياً ds1.setWaitForConversion(false); ds2.setWaitForConversion(false); ds1.setResolution(12); ds2.setResolution(12); // تهيئة ADC للأرجل التناظرية analogReadResolution(12); analogSetPinAttenuation(TEMT_PIN, ADC_11db); analogSetPinAttenuation(MQ135_PIN, ADC_11db); // تهيئة SPIFFS SPIFFS.begin(true); // بدء WiFi (STA) WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); Serial.print("Connecting to WiFi SSID: "); Serial.println(WIFI_SSID); lastWiFiRetry = millis(); // بدء ويب سيرفر webServer.begin(); Serial.println("Local web server started on port 80"); Serial.println("Will print IP when connected..."); // طلب أول تحويل لدرجات الحرارة requestDSIfNeeded(); // بدء معايرة MQ-135 mqCalibStart = millis(); } // ========================================= // 6) الحلقة الرئيسية (loop) – غير مانعة // ========================================= void loop() { // الحفاظ على سلاسة النظام 24/7 بدون توقف tryConnectWiFi(); // إدارة شاشة الحالة updateLCDWifiState(wifiConnected, false); // معالجة الويب سيرفر المحلي handleWebServer(); // معايرة MQ-135 إن لزم handleMQCalibration(); // إدارة طلب/قراءة DS requestDSIfNeeded(); float tempAvg; if (readDSTemperatures(tempAvg)) { currentMeas.temperature = tempAvg; currentMeas.ts = millis(); } // قراءة DHT (رطوبة) readDHTIfDue(); if (!isnan(lastHumidityValid)) { currentMeas.humidity = lastHumidityValid; currentMeas.ts = millis(); } // قراءة ADC (Lux + NH3) readADCIfDue(); if (!isnan(lastLuxValid)) { currentMeas.lux = lastLuxValid; currentMeas.ts = millis(); } if (!isnan(lastNH3Valid)) { currentMeas.ammonia = lastNH3Valid; currentMeas.ts = millis(); } // منطق الإرسال / التخزين كل 5 ثواني + إرسال فوري عند عودة الإنترنت unsigned long now = millis(); bool sendDue = (now - lastSendMillis >= SEND_INTERVAL_MS); // اكتشاف عودة الشبكة لإرسال فوري + تفريغ المخزن if (!wifiWasConnected && wifiConnected) { Serial.print("WiFi connected. IP: "); Serial.println(WiFi.localIP()); Serial.print("Open http://"); Serial.print(WiFi.localIP()); Serial.println("/ and /json"); // تفريغ المخزن أولاً flushOfflineBuffer(); // إرسال فوري sendDue = true; } wifiWasConnected = wifiConnected; if (sendDue) { // بناء الحمولة من أحدث قياسات سليمة float t = isnan(currentMeas.temperature) ? (isnan(lastTempValid) ? 0.0f : lastTempValid) : currentMeas.temperature; float h = isnan(currentMeas.humidity) ? (isnan(lastHumidityValid) ? 0.0f : lastHumidityValid) : currentMeas.humidity; float l = isnan(currentMeas.lux) ? (isnan(lastLuxValid) ? 0.0f : lastLuxValid) : currentMeas.lux; float a = isnan(currentMeas.ammonia) ? (isnan(lastNH3Valid) ? 0.0f : lastNH3Valid) : currentMeas.ammonia; String payload = buildJSON(t, h, l, a); Serial.print("Measurements -> T: "); Serial.print(t, 2); Serial.print(" C, H: "); Serial.print(h, 2); Serial.print(" %, L: "); Serial.print(l, 0); Serial.print(" lux, NH3: "); Serial.print(a, 2); Serial.println(" ppm"); if (wifiConnected) { updateLCDWifiState(true, true); // إرسال المخزن أولاً (إن وُجد) flushOfflineBuffer(); // ثم إرسال البيانات الحالية bool ok = postJSON(payload); if (!ok) { // فشل الإرسال: خزّن محلياً appendOffline(payload); updateLCDWifiState(false, false); Serial.println("POST failed -> saved offline"); } else { Serial.println("POST OK"); } } else { // غير متصل: خزّن محلياً appendOffline(payload); updateLCDWifiState(false, false); Serial.println("Offline -> saved to SPIFFS"); } lastSendMillis = now; } }
إنشاء وحدة جديدة
✕
اسم الوحدة
الإصدار
الوصف
الأيقونة
اللون
الكود
يمكن إضافة الكود لاحقاً من محرر الوحدة
إلغاء
إنشاء