بروتوكولات نقل البيانات: MQTT و HTTP APIs
Data Transport Protocols (MQTT & HTTP APIs)
نموذج Publish/Subscribe، الـ Broker، تصميم شجرة المواضيع (Topics)، مستويات QoS، Retained و Last Will — ومقارنته بـ REST/HTTP. نثبت Mosquitto محلياً بمستخدمين وصلاحيات، ونرسل قراءات المزرعة إليه من ESP32.
نظرة عامة على السيشن
بنهاية السيشن ستكون قادراً على
- تشرح نموذج Pub/Sub ودور الـ Broker ولماذا يناسب إنترنت الأشياء.
- تصمم شجرة مواضيع قابلة للتوسع وتستخدم + و # بشكل صحيح.
- تختار QoS 0/1/2 وتفهم Retained و Last Will (LWT).
- تقارن MQTT بـ REST/HTTP وتعرف متى تستخدم كلاً منهما.
- تثبت Mosquitto بأمان (مستخدمين + ACL) وتنشر وتشترك من ESP32 و Python و سطر الأوامر.
التطبيق في مزرعة الكلية
تشغيل Broker محلي (Mosquitto) على لابتوب المزرعة، وإرسال قراءات كل العقد إلى المواضيع kfs/farm/<node>/telemetry ومراقبتها حية بـ MQTT Explorer، مع رسالة Last Will تعلن سقوط أي عقدة.
المُخرج (Deliverable)
تدفق بيانات موثوق من الحقل إلى الـ Broker: شجرة مواضيع موثقة، مستخدمين وصلاحيات ACL، عقدة ESP32 تنشر JSON كل دقيقة وتستقبل أوامر، ولقطة MQTT Explorer للبيانات الحية.
خطة السيشن (180 دقيقة)
- 0:00 مراجعة مراجعة: نتائج نشر الشبكة و PDR 10 د
- 0:10 شرح Pub/Sub، Broker، Topics، QoS، Retained، LWT، مقارنة REST 30 د
- 0:40 محاكاة محاكي المواضيع + تصميم شجرة مواضيع المزرعة 20 د
- 1:00 تطبيق Lab: تثبيت Mosquitto بأمان + MQTT Explorer 30 د
- 1:30 استراحة استراحة 10 د
- 1:40 محاكاة Wokwi: ESP32 ينشر قراءات على Broker حقيقي 30 د
- 2:10 ميداني ميداني: كل عقد المزرعة ترسل للـ Broker المحلي 40 د
- 2:50 تقييم اختبار سريع + الواجب 10 د
الأدوات والمكونات
قاموس المصطلحات (Glossary)
Message Queuing Telemetry Transport — مصمم لأجهزة ضعيفة وشبكات غير مستقرة.
الخادم الذي يستقبل الرسائل ويوزعها على المشتركين (Mosquitto).
إرسال رسالة إلى موضوع.
طلب استلام رسائل موضوع أو مجموعة مواضيع.
عنوان هرمي مثل kfs/farm/field_A/telemetry.
+ مستوى واحد، # كل المستويات التالية.
0: مرة على الأكثر، 1: مرة على الأقل، 2: مرة واحدة بالضبط.
الـ Broker يحفظ آخر رسالة ويعطيها لأي مشترك جديد فوراً.
رسالة ينشرها الـ Broker تلقائياً إذا انقطعت العقدة فجأة.
نبضة دورية تثبت أن العميل متصل.
طلب/استجابة: GET لجلب، POST لإرسال.
من يستطيع النشر/الاشتراك في أي موضوع.
Pub/Sub والـ Broker
30 دقيقةفي HTTP العميل يسأل والخادم يجيب (Request/Response). في MQTT لا أحد يعرف الآخر: العقدة تنشر قراءتها على موضوع، والـ Broker يوصلها لكل من اشترك فيه — لوحة التحكم، قاعدة البيانات، محرك التنبيهات — في نفس اللحظة.
| MQTT | HTTP / REST | |
|---|---|---|
| النمط | Publish/Subscribe عبر Broker | Request/Response مباشر |
| حجم الرأس (Header) | 2 Bytes | مئات البايتات |
| الاتصال | دائم (TCP مفتوح) — الأوامر تصل فوراً | جديد لكل طلب — الأوامر تحتاج Polling |
| الموثوقية | QoS + Retained + LWT | يعتمد على إعادة المحاولة في الكود |
| البساطة / الأدوات | يحتاج Broker | أي سيرفر ويب + متصفح + curl |
| في مشروعنا | العقد ↔ Broker ↔ السيرفر (mqtt_ingest.py) | Dashboard ↔ Flask API، والعقد البسيطة (POST) |
| QoS | الضمان | التكلفة | نستخدمه لـ |
|---|---|---|---|
| 0 | مرة على الأكثر (قد تضيع) | أقل طاقة | قراءات دورية كل دقيقة — ضياع واحدة مقبول |
| 1 | مرة على الأقل (قد تتكرر) | متوسطة | أوامر المضخة والتنبيهات — نتعامل مع التكرار في السيرفر |
| 2 | مرة واحدة بالضبط | الأعلى (4 رسائل) | نادراً في IoT — مثل عدادات الفوترة |
Last Will = جرس إنذار مجاني
عند الاتصال تخبر العقدة الـ Broker: «لو انقطعت فجأة، انشر offline على kfs/farm/field_A/status». البطارية فرغت؟ الحمار قطع السلك؟ لوحة التحكم تعرف خلال ثوانٍ بدون أي كود إضافي في السيرفر.
تصميم شجرة المواضيع
20 دقيقةkfs/farm/<node_id>/telemetry JSON readings (node -> broker, QoS 0)
kfs/farm/<node_id>/status online | offline (retained + Last Will)
kfs/farm/<node_id>/health fw, uptime, heap, rssi (retained, every 5 min)
kfs/farm/<node_id>/cmd {"actuator":"pump",...} (server -> node, QoS 1)
kfs/farm/<node_id>/cmd/ack command id (node -> server)
kfs/lab/weather/telemetry future: weather station on the faculty roof
قواعد تصميم المواضيع
- ابدأ بالعام ثم الخاص:
المؤسسة/الموقع/الجهاز/نوع البيانات. - لا تبدأ بـ
/ولا تستخدم مسافات أو حروفاً عربية في المواضيع. - أرسل JSON واحداً بكل القراءات بدلاً من موضوع لكل حساس — رسائل أقل وطاقة أقل.
- افصل الأوامر عن القراءات حتى نعطي صلاحيات مختلفة (ACL).
Lab: Mosquitto بأمان
30 دقيقةتثبيت Mosquitto بمستخدمين وصلاحيات
- التثبيت
حمّل Mosquitto من mosquitto.org/download (ويندوز) أو
sudo apt install mosquitto mosquitto-clientsعلى Raspberry Pi. - ملف الإعداد
انسخ
broker/mosquitto.confوbroker/aclمن مشروع Smart Farm إلى مجلد واحد. - المستخدمون
أنشئ المستخدمين بكلمات سر:
mosquitto_passwd -c passwd farmnodeثمmosquitto_passwd passwd dashboard. - التشغيل
mosquitto -c mosquitto.conf -vواترك النافذة مفتوحة لترى الاتصالات. - اختبار سطر الأوامر
نافذة 1:
mosquitto_sub -u dashboard -P *** -t "kfs/farm/#" -v— نافذة 2:mosquitto_pub -u farmnode -P *** -t kfs/farm/field_A/telemetry -m "{\"soil_moisture_pct\":31}" - اختبار الصلاحيات
حاول أن تنشر أمراً بمستخدم farmnode على
kfs/farm/field_A/cmd— يجب أن يرفضه الـ Broker بصمت (ACL). - MQTT Explorer
اتصل بـ localhost:1883 بمستخدم dashboard وشاهد شجرة المواضيع تتكون.
النتيجة المتوقعة: رسائل تظهر في mosquitto_sub و MQTT Explorer، والنشر غير المصرح به لا يصل.
# KFS Smart Farm - local Mosquitto broker (session 7)
# Windows: "C:\Program Files\mosquitto\mosquitto.exe" -c mosquitto.conf -v
# Create users: mosquitto_passwd -c passwd farmnode then mosquitto_passwd passwd dashboard
# NOTE: Mosquitto does not allow comments at the end of a setting line.
# Farm LAN only - the IoT router is isolated from the college network
listener 1883 0.0.0.0
allow_anonymous false
password_file passwd
acl_file acl
# Keep retained messages (node status, health) across restarts
persistence true
persistence_location ./data/
autosave_interval 60
log_type error
log_type warning
log_type notice
connection_messages true
# MQTT over WebSockets for browser dashboards (session 11, optional)
listener 9001
protocol websockets
# Field nodes may only publish their own data and read their own commands
user farmnode
topic write kfs/farm/+/telemetry
topic write kfs/farm/+/status
topic write kfs/farm/+/health
topic read kfs/farm/+/cmd
# The server / dashboard reads everything and sends commands
user dashboard
topic read kfs/farm/#
topic write kfs/farm/+/cmd
فخ شائع
Mosquitto 2.x يرفض أي اتصال من خارج الجهاز إذا لم تكتب listener 1883 0.0.0.0 صراحة، ولا يقبل التعليقات في نهاية سطر الإعداد. وجدار حماية ويندوز قد يمنع المنفذ 1883 — اسمح به للشبكات الخاصة فقط.
Wokwi: ESP32 ينشر عبر MQTT
30 دقيقةعقدة افتراضية تنشر حرارة ورطوبة على Broker عام
30 دقيقةفي المحاكي لا نصل للـ Broker المحلي، فنستخدم Broker عام للتجربة (broker.hivemq.com) بموضوع فريد لفريقك. لا ترسل بيانات حقيقية حساسة لـ Broker عام أبداً — هو للتعلم فقط.
المكونات (Components)
- ESP32 DevKit V1
- DHT22 (متاح في Wokwi!)
- Potentiometer (يحاكي حساس التربة)
التوصيل (Wiring)
| من | إلى | ملاحظة |
|---|---|---|
| DHT22 SDA | GPIO 4 | |
| DHT22 VCC / GND | 3V3 / GND | |
| Potentiometer SIG | GPIO 34 |
في Wokwi أضف المكتبات من تبويب Library Manager: PubSubClient و DHT sensor library و ArduinoJson.
غيّر
TEAMلاسم فريقك، والصق الكود وشغّل.على اللابتوب افتح MQTT Explorer واتصل بـ broker.hivemq.com:1883 واشترك في
kfs-course/#.غيّر حرارة الـ DHT22 من المحاكي (اضغط عليه) وشاهد القيمة تتغير في MQTT Explorer خلال ثوانٍ.
من MQTT Explorer انشر على
kfs-course/TEAM/cmdالرسالة{"pump":"on"}— سترى العقدة تطبعها.
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
const char* TEAM = "team1"; // <-- change me (unique topic)
WiFiClient net;
PubSubClient mqtt(net);
DHT dht(4, DHT22);
char tTele[64], tStatus[64], tCmd[64];
void onCmd(char* topic, byte* payload, unsigned int len) {
Serial.printf("command on %s: %.*s\n", topic, len, (char*)payload);
}
void connectMqtt() {
while (!mqtt.connected()) {
String id = String("kfs-") + TEAM + "-" + String(random(9999));
// Last Will: broker publishes "offline" (retained) if we disappear
if (mqtt.connect(id.c_str(), tStatus, 1, true, "offline")) {
mqtt.publish(tStatus, "online", true);
mqtt.subscribe(tCmd, 1);
} else delay(2000);
}
}
void setup() {
Serial.begin(115200);
dht.begin();
snprintf(tTele, 64, "kfs-course/%s/telemetry", TEAM);
snprintf(tStatus, 64, "kfs-course/%s/status", TEAM);
snprintf(tCmd, 64, "kfs-course/%s/cmd", TEAM);
WiFi.begin("Wokwi-GUEST", "");
while (WiFi.status() != WL_CONNECTED) delay(200);
mqtt.setServer("broker.hivemq.com", 1883);
mqtt.setCallback(onCmd);
}
void loop() {
connectMqtt();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last > 5000) {
last = millis();
JsonDocument doc;
doc["air_temp_c"] = dht.readTemperature();
doc["air_humidity_pct"] = dht.readHumidity();
doc["soil_moisture_pct"] = map(analogRead(34), 0, 4095, 0, 100);
char buf[128];
serializeJson(doc, buf);
mqtt.publish(tTele, buf);
Serial.println(buf);
}
}
ميداني: المزرعة على MQTT
40 دقيقة/*
KFS Smart Farm — field node (ESP32) — MQTT version (sessions 7-8, 20)
Publishes JSON to kfs/farm/<NODE_ID>/telemetry (QoS 0 with PubSubClient) every 60 s
Last Will: kfs/farm/<NODE_ID>/status = "offline" (retained) -> the dashboard knows when a node dies
Subscribes to: kfs/farm/<NODE_ID>/cmd payload {"actuator":"pump","action":"on","duration_min":5}
Libraries: PubSubClient (Nick O'Leary), DHT sensor library, ArduinoJson v7
*/
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
const char* WIFI_SSID = "KFS-Farm-IoT";
const char* WIFI_PASS = "change-me";
const char* BROKER = "192.168.1.10"; // Mosquitto on the farm laptop / Raspberry Pi
const char* MQTT_USER = "farmnode";
const char* MQTT_PASS = "change-me";
const char* NODE_ID = "greenhouse_01";
#define DHT_PIN 4
#define SOIL_PIN 34
#define PUMP_PIN 26
DHT dht(DHT_PIN, DHT22);
WiFiClient net;
PubSubClient mqtt(net);
String tTelemetry, tStatus, tCmd;
unsigned long last = 0, pumpOffAt = 0;
void onMessage(char* topic, byte* payload, unsigned int len) {
JsonDocument doc;
if (deserializeJson(doc, payload, len)) return;
bool on = doc["action"] == "on";
if (doc["actuator"] == "pump") {
digitalWrite(PUMP_PIN, on ? LOW : HIGH);
pumpOffAt = on ? millis() + min(doc["duration_min"] | 5, 30) * 60000UL : 0;
}
Serial.printf("cmd on %s: %.*s\n", topic, len, (char*)payload);
}
void reconnect() {
while (!mqtt.connected()) {
// clientId, user, pass, willTopic, willQoS, willRetain, willMessage
if (mqtt.connect(NODE_ID, MQTT_USER, MQTT_PASS, tStatus.c_str(), 1, true, "offline")) {
mqtt.publish(tStatus.c_str(), "online", true);
mqtt.subscribe(tCmd.c_str(), 1);
} else {
Serial.printf("MQTT failed rc=%d, retry in 5 s\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(PUMP_PIN, OUTPUT); digitalWrite(PUMP_PIN, HIGH);
dht.begin();
tTelemetry = String("kfs/farm/") + NODE_ID + "/telemetry";
tStatus = String("kfs/farm/") + NODE_ID + "/status";
tCmd = String("kfs/farm/") + NODE_ID + "/cmd";
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
mqtt.setServer(BROKER, 1883);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512);
}
void loop() {
if (!mqtt.connected()) reconnect();
mqtt.loop();
if (pumpOffAt && millis() > pumpOffAt) { digitalWrite(PUMP_PIN, HIGH); pumpOffAt = 0; }
if (millis() - last > 60000 || last == 0) {
last = millis();
JsonDocument doc;
float t = dht.readTemperature(), h = dht.readHumidity();
if (!isnan(t)) doc["air_temp_c"] = t;
if (!isnan(h)) doc["air_humidity_pct"] = h;
doc["soil_moisture_pct"] = constrain(map(analogRead(SOIL_PIN), 3200, 1300, 0, 100), 0, 100);
doc["rssi"] = WiFi.RSSI();
char buf[256];
size_t n = serializeJson(doc, buf);
mqtt.publish(tTelemetry.c_str(), (const uint8_t*)buf, n, false);
}
}
هذا هو الـ firmware الحقيقي للعقدة: JSON كل 60 ثانية، Last Will، واستقبال أوامر المضخة بمهلة أمان 30 دقيقة كحد أقصى.
إرسال قراءات كل عقد المزرعة إلى الـ Broker المحلي
لابتوب المزرعة (Broker) + راوتر شبكة الـ IoT المعزولة + العقد في الصوبة وحقل الخضر
- الشبكة
اتصل باللابتوب بشبكة
KFS-Farm-IoTواعرف عنوانه (ipconfig) وضعه فيBROKERفي كود العقد. - الرفع
كل فريق يرفع esp32_node_mqtt.ino بـ NODE_ID الخاص به وكلمة سر المستخدم farmnode.
- المراقبة
على الشاشة الكبيرة: MQTT Explorer يعرض شجرة
kfs/farm/#حية. - اختبار LWT
افصل بطارية عقدة واحدة: خلال ~25 ثانية (1.5 × keep-alive؛ الافتراضي في PubSubClient هو 15 ثانية) يتحول status إلى offline.
- اختبار الأوامر
انشر من MQTT Explorer (مستخدم dashboard) أمر
{"actuator":"pump","action":"on","duration_min":1}على cmd الخاص بعقدة الصوبة — يضيء LED الـ Relay دقيقة ثم يطفئ.
| العقدة | متصلة؟ | رسائل/10 دقائق | زمن ظهور offline (ث) | الأمر وصل؟ |
|---|---|---|---|---|
- في هذه السيشن الـ Relay يتحكم في LED أو مضخة 5V فقط.
وماذا عن HTTP؟
نفس القراءة يمكن إرسالها كـ POST لـ REST API (سنبنيه في السيشن 10). جرّب بعد تشغيل مشروع Smart Farm:
curl -X POST http://127.0.0.1:5000/api/v1/readings \
-H "Content-Type: application/json" -H "X-API-Key: kfs-farm-node-key" \
-d '{"node_id":"field_A","air_temp_c":29.4,"soil_moisture_pct":31}'
اختبر فهمك
6 أسئلة · نتيجة فوريةالواجب والتسليم
الواجب الأسبوعي (Weekly Assignment)
وثّق شجرة مواضيع فريقك وملف ACL في README على مستودع الفريق.
اكتب سكربت Python صغيراً بمكتبة paho-mqtt يشترك في
kfs/farm/#ويطبع الرسائل بالوقت — سنطوره في السيشن 8.سجل لقطة شاشة من MQTT Explorer تبين القراءات الحية و status لكل عقدة.
قائمة التسليم (Submission Checklist)
مراجع ومصادر
استعد للسيشن القادمة
السيشن القادمة نكتب سكربت السيرفر الذي يستقبل الرسائل ويخزنها في SQLite. ثبّت Python 3.10+ وحمّل مشروع Smart Farm من صفحة الكورس.