مشروع محطة قياس الحرارة والرطوبة باستخدام ESP32 وDHT22 مع شاشة OLED وWi-Fi
في الحلقة الثانية عشرة من سلسلة ESP32 من الصفر سنصنع محطة صغيرة لقياس درجة الحرارة والرطوبة باستخدام حساس DHT22 ولوحة ESP32. سنعرض القياسات على شاشة OLED SSD1306، ثم ننشئ صفحة Web عبر Wi-Fi لمتابعتها من الهاتف مع تحديث تلقائي للبيانات.
فيديو المشروع
شاهدوا الفيديو رقم 12 من سلسلة ESP32 من الصفر على قناة نادي تك – نادي الابتكار للتعرف على خطوات التركيب والبرمجة والتجربة العملية.
فكرة المشروع
يقيس حساس DHT22 درجة حرارة الهواء والرطوبة النسبية في مكان وجوده. تقرأ ESP32 القياسات وتعرضها محليًا على شاشة OLED عبر I2C، وفي الوقت نفسه تنشئ خادم Web يتيح مشاهدتها على هاتف متصل بالشبكة المحلية.
توضيح: هذه محطة قياس للظروف المحيطة بالحساس وليست خدمة لجلب توقعات الطقس من الإنترنت.
التعرف على حساس DHT22
حساس DHT22 رقمي ويقيس درجة الحرارة والرطوبة النسبية. يبلغ مجال قياس الحرارة تقريبًا من -40 إلى 80 درجة مئوية، والرطوبة من 0 إلى 100%، وفق مواصفات الطراز. وهو عمومًا أوسع مجالًا وأفضل دقة من DHT11.
المكونات المطلوبة
- لوحة ESP32.
- حساس DHT22.
- مقاومة 10kΩ لخط البيانات.
- شاشة OLED SSD1306 بدقة 128×64 مع I2C.
- لوحة تجارب Breadboard وأسلاك توصيل.
- كابل USB وحاسوب مثبت عليه Arduino IDE.
- شبكة Wi-Fi وهاتف لاختبار صفحة Web.
توصيل DHT22 مع ESP32
عند النظر إلى الحساس العاري من الأمام والأرجل نحو الأسفل، يكون ترتيب الأرجل من اليسار إلى اليمين كما يلي:

| رجل DHT22 | ESP32 | الوظيفة |
|---|---|---|
| 1 – VCC | 3.3V | التغذية |
| 2 – DATA | GPIO4 | البيانات |
| 3 – NC | غير موصل | غير مستخدم |
| 4 – GND | GND | الأرضي |
| مقاومة 10kΩ | بين VCC وDATA | Pull-up |
تساعد مقاومة Pull-up على إبقاء خط البيانات في المستوى المنطقي المرتفع عندما لا يقوم الحساس بإرسال البيانات.
ملاحظة: بعض وحدات DHT22 الجاهزة ذات الثلاثة أطراف تتضمن مقاومة Pull-up مسبقًا، وقد يختلف ترتيب أرجلها. راجع الرموز الموجودة على وحدتك.
توصيل شاشة OLED SSD1306
| OLED | ESP32 | الوظيفة |
|---|---|---|
| VCC | 3.3V | التغذية |
| GND | GND | الأرضي |
| SDA | GPIO21 | بيانات I2C |
| SCL | GPIO22 | ساعة I2C |
نستخدم نفس توصيلات شاشة OLED في الحلقة السابقة، دون تغيير التوصيلات عند الانتقال بين التجارب الثلاث. تحقق من توافق وحدة OLED مع جهد 3.3V ومستويات إشارات I2C.
تثبيت المكتبات في Arduino IDE
- DHT sensor library من Adafruit.
- Adafruit Unified Sensor إذا طلبها مدير المكتبات.
- Adafruit SSD1306.
- Adafruit GFX Library.
تتوفر WiFi.h وWebServer.h عادة ضمن حزمة ESP32 من Espressif المثبتة في Arduino IDE.
التجربة الأولى: قراءة DHT22 في Serial Monitor
نستدعي DHT.h، ونحدد GPIO4 ونوع الحساس DHT22، ثم ننشئ كائن DHT. داخل setup() نبدأ الاتصال التسلسلي بسرعة 115200 ونهيئ الحساس باستخدام dht.begin().
داخل loop() نستخدم readTemperature() وreadHumidity() لقراءة الحرارة والرطوبة، ونتحقق من نجاح القراءة بواسطة isnan(). تتكرر القراءة كل ثانيتين تقريبًا باستخدام delay(2000).
كود التجربة الأولى
#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
if (isnan(temperature) || isnan(humidity)) {
Serial.println("DHT22 reading failed!");
} else {
Serial.print("Temperature: ");
Serial.print(temperature);
Serial.println(" C");
Serial.print("Humidity: ");
Serial.print(humidity);
Serial.println(" %");
}
delay(2000);
}
التجربة الثانية: عرض القياسات على OLED
نضيف Wire.h وAdafruit_GFX.h وAdafruit_SSD1306.h، ونحدد أبعاد الشاشة 128×64. داخل setup() نبدأ I2C باستخدام Wire.begin(21, 22) ونهيئ الشاشة بعنوان I2C الشائع 0x3C.
داخل loop() نقرأ القياسات، ونمسح الشاشة بواسطة clearDisplay()، ونكتب عنوان المحطة ونرسم خطًا فاصلًا بواسطة drawLine()، ثم نعرض الحرارة بجانب T والرطوبة بجانب H. وأخيرًا نستدعي display.display() لتحديث الشاشة كل ثانيتين تقريبًا.
كود التجربة الثانية
#include <Wire.h>
#include <DHT.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define DHTPIN 4
#define DHTTYPE DHT22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
DHT dht(DHTPIN, DHTTYPE);
Adafruit_SSD1306 display(
SCREEN_WIDTH,
SCREEN_HEIGHT,
&Wire,
OLED_RESET
);
void setup() {
Serial.begin(115200);
dht.begin();
Wire.begin(21, 22);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED not found!");
while (true) {
delay(1000);
}
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.display();
}
void loop() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println("NAADI TECH WEATHER");
display.drawLine(0, 12, 127, 12, SSD1306_WHITE);
display.setTextSize(2);
if (isnan(temperature) || isnan(humidity)) {
display.setCursor(0, 25);
display.println("Sensor");
display.setCursor(0, 45);
display.println("Error!");
} else {
display.setCursor(0, 20);
display.print("T:");
display.print(temperature, 1);
display.println(" C");
display.setCursor(0, 43);
display.print("H:");
display.print(humidity, 1);
display.println(" %");
}
display.display();
delay(2000);
}
التجربة الثالثة: محطة متصلة عبر Wi-Fi
نحتفظ بقراءة DHT22 وعرض OLED، ونضيف WiFi.h وWebServer.h. نحدد اسم الشبكة وكلمة المرور، وننشئ خادم HTTP على المنفذ 80. تعرض الصفحة بطاقتين للحرارة والرطوبة بتنسيق HTML وCSS، بينما تطلب JavaScript أحدث البيانات تلقائيًا كل ثانيتين.
أمان الشبكة: لا تنشر كلمة مرور Wi-Fi الحقيقية داخل الكود المنشور. هذا الخادم مخصص للشبكة المحلية؛ لا تعرضه مباشرة على الإنترنت دون حماية مناسبة.
تبادل البيانات بتنسيق JSON
تنشئ دالة handleData() استجابة JSON تحتوي على القياسات. مثال توضيحي للبيانات:
{"temperature":24.5,"humidity":53.2}
هذه قيم توضيحية وليست قراءات حقيقية من الفيديو.
أهم الدوال في البرنامج
| الدالة أو التعليمة | الدور |
|---|---|
| readSensor() | قراءة DHT22 والتحقق من النتائج |
| updateOLED() | تحديث شاشة OLED |
| handleData() | إرسال JSON إلى المتصفح |
| server.on(“/”) | عرض صفحة المحطة |
| server.on(“/data”) | توفير القياسات للمتصفح |
| server.begin() | تشغيل الخادم |
| server.handleClient() | معالجة طلبات HTTP |
| millis() | جدولة القياسات دون تعطيل الخادم |
نستخدم millis() بدل delay(2000) في النسخة النهائية حتى يستطيع ESP32 الاستجابة لطلبات المتصفح أثناء انتظار موعد القراءة التالية.
كود المشروع النهائي
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <DHT.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define DHTPIN 4
#define DHTTYPE DHT22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
DHT dht(DHTPIN, DHTTYPE);
WebServer server(80);
Adafruit_SSD1306 display(
SCREEN_WIDTH,
SCREEN_HEIGHT,
&Wire,
OLED_RESET
);
float temperature = NAN;
float humidity = NAN;
unsigned long lastRead = 0;
const unsigned long readInterval = 2000;
// HTML page
const char webpage[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<title>Naadi Tech Weather Station</title>
<style>
body {
font-family: Arial, sans-serif;
background: #06142b;
color: white;
text-align: center;
margin: 0;
padding: 30px 15px;
}
h1 { color: #00d9f5; }
.card {
background: #102744;
border: 1px solid #00d9f5;
border-radius: 15px;
padding: 22px;
margin: 18px auto;
max-width: 350px;
}
.value {
color: #ffcf25;
font-size: 38px;
font-weight: bold;
}
.status { color: #ff922b; }
</style>
</head>
<body>
<h1>Naadi Tech</h1>
<h2>Weather Station</h2>
<div class="card">
<h3>Temperature</h3>
<div class="value" id="temp">--</div>
</div>
<div class="card">
<h3>Humidity</h3>
<div class="value" id="hum">--</div>
</div>
<p class="status" id="status">Connecting...</p>
<script>
async function updateData() {
try {
const response = await fetch('/data');
if (!response.ok) throw new Error('HTTP error');
const data = await response.json();
document.getElementById('temp').textContent =
data.temperature === null
? '--'
: data.temperature.toFixed(1) + ' °C';
document.getElementById('hum').textContent =
data.humidity === null
? '--'
: data.humidity.toFixed(1) + ' %';
document.getElementById('status').textContent =
data.temperature === null
? 'Sensor reading unavailable'
: 'Updated';
} catch (error) {
document.getElementById('status').textContent =
'Connection error';
}
}
updateData();
setInterval(updateData, 2000);
</script>
</body>
</html>
)rawliteral";
// Display measurements on OLED
void updateOLED() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("NAADI TECH WEATHER");
display.drawLine(0, 12, 127, 12, SSD1306_WHITE);
display.setTextSize(2);
if (isnan(temperature) || isnan(humidity)) {
display.setCursor(0, 25);
display.println("Sensor Error");
} else {
display.setCursor(0, 20);
display.print("T:");
display.print(temperature, 1);
display.println(" C");
display.setCursor(0, 43);
display.print("H:");
display.print(humidity, 1);
display.println(" %");
}
display.display();
}
// Read DHT22
void readSensor() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
temperature = t;
humidity = h;
} else {
temperature = NAN;
humidity = NAN;
Serial.println("DHT22 reading failed");
}
updateOLED();
}
// Return data as JSON
void handleData() {
String json = "{";
json += "\"temperature\":";
json += isnan(temperature)
? "null"
: String(temperature, 1);
json += ",\"humidity\":";
json += isnan(humidity)
? "null"
: String(humidity, 1);
json += "}";
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
dht.begin();
Wire.begin(21, 22);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED not found");
while (true) {
delay(1000);
}
}
display.clearDisplay();
display.display();
// Initial sensor reading
readSensor();
lastRead = millis();
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Open: http://");
Serial.println(WiFi.localIP());
// Web routes
server.on("/", []() {
server.send_P(200, "text/html", webpage);
});
server.on("/data", handleData);
server.begin();
Serial.println("Web server started");
}
void loop() {
server.handleClient();
if (millis() - lastRead >= readInterval) {
lastRead = millis();
readSensor();
}
}
فتح صفحة القياسات على الهاتف
- ارفع البرنامج النهائي إلى ESP32.
- افتح Serial Monitor بسرعة 115200.
- انتظر ظهور عنوان IP المحلي بعد الاتصال بشبكة Wi-Fi.
- تأكد أن الهاتف متصل بالشبكة نفسها وأن الاتصال بين الأجهزة مسموح.
- اكتب عنوان IP في متصفح الهاتف.
- شاهد القياسات تتحدث تلقائيًا كل ثانيتين تقريبًا مع استمرار العرض على OLED.
مشكلات شائعة وحلولها
| المشكلة | الحل |
|---|---|
| قراءة NaN أو فشل الحساس | راجع GPIO4 والتغذية ومقاومة Pull-up والفاصل بين القراءات. |
| شاشة OLED فارغة | راجع SDA وSCL وعنوان I2C 0x3C. |
| فشل اتصال Wi-Fi | تحقق من اسم الشبكة وكلمة المرور ودعم شبكة 2.4GHz. |
| صفحة الهاتف لا تفتح | تحقق من عنوان IP واتصال الهاتف بالشبكة المحلية وعدم عزل الأجهزة. |
| الصفحة لا تحدث الأرقام | راجع مسار /data واستجابة JSON وJavaScript. |
| الخادم بطيء | تأكد من استدعاء server.handleClient() وتجنب delay الطويل. |
ماذا تعلمنا؟

- قراءة درجة الحرارة والرطوبة باستخدام DHT22.
- استخدام مقاومة Pull-up بقيمة 10kΩ.
- عرض القياسات على Serial Monitor وشاشة OLED.
- إنشاء Web Server باستخدام ESP32 وWi-Fi.
- بناء صفحة HTML وCSS وتحديثها بواسطة JavaScript.
- تبادل القياسات بتنسيق JSON.
- استخدام millis() لتجنب تعطيل خادم الويب.
أفكار لتطوير المشروع
يمكن تطوير المحطة لتسجيل القياسات مع مرور الوقت، وإضافة رسوم بيانية، وإرسال البيانات إلى خدمة سحابية، أو إصدار تنبيهات عند تجاوز حدود معينة للحرارة والرطوبة.
الخلاصة
جمعنا في هذا المشروع بين DHT22 وOLED وWi-Fi في لوحة ESP32. بدأنا بقراءة الحساس، ثم عرضنا النتائج على الشاشة، وأخيرًا أنشأنا صفحة Web تعرض نفس المعلومات من الهاتف مع تحديث تلقائي. تابعوا باقي حلقات ESP32 من الصفر على قناة نادي تك – نادي الابتكار.
