- KFS × Navera
- IoT & AI for Smart Agriculture
- السيشن 18
أنظمة الإنذار المبكر والأتمتة
Early Warning Systems & Automation
قواعد التنبيه (عتبات + مخرجات الذكاء الاصطناعي)، منع تكرار التنبيهات، الإشعارات عبر Telegram، التشغيل التلقائي للمضخة والمروحة، وسجل لكل قرار — ثم نشغّل ريّاً شبه ذاتي يعتمد على التوقعات، مع إبقاء الإنسان في الحلقة وتصميم آمن ضد الأعطال.
نظرة عامة على السيشن
بنهاية السيشن ستكون قادراً على
- تصمم قواعد تنبيه بمستويات (info / warning / critical) تجمع العتبات ومخرجات ML.
- تمنع Alert fatigue بإلغاء التكرار (De-duplication) و Hysteresis.
- ترسل إشعارات عبر Telegram Bot API (و SMTP بريدياً) بشكل آمن.
- تجدول المهام بـ Cron / Task Scheduler وتسجل كل قرار في Decision log.
- تطبق مبادئ Human-in-the-loop والتشغيل الآمن للأتمتة الزراعية.
التطبيق في مزرعة الكلية
تشغيل محرك الأتمتة على منصة المزرعة: تنبيه Telegram للمشرف عند الإجهاد الحراري أو رطوبة الصوبة العالية أو سقوط عقدة، وري تلقائي لأصيص التجربة في الصوبة عندما تقرر القواعد أو النموذج ذلك — مع تأجيل الري إذا كان المطر متوقعاً.
المُخرج (Deliverable)
بيئة زراعية شبه ذاتية: automation.py يعمل كل 10 دقائق، تنبيهات Telegram حقيقية، أوامر ري ومروحة تلقائية بحد أقصى، وسجل قرارات يوضح سبب كل فعل.
خطة السيشن (180 دقيقة)
- 0:00 مراجعة مراجعة: التوقعات على اللوحة ونتائج المقارنة الميدانية 10 د
- 0:10 شرح تصميم قواعد الإنذار + محاكي محرك القرارات 25 د
- 0:35 محاكاة Tinkercad: وحدة ري تلقائي مستقلة بـ Hysteresis وقفل أمان 30 د
- 1:05 استراحة استراحة 10 د
- 1:15 تطبيق Lab: automation.py + بوت Telegram + الجدولة 35 د
- 1:50 ميداني ميداني: ري شبه ذاتي يعمل بالتوقعات في الصوبة 35 د
- 2:25 نقاش مراجعة الأمان: الإنسان في الحلقة وأنماط الفشل 25 د
- 2:50 تقييم اختبار سريع + الواجب 10 د
الأدوات والمكونات
قاموس المصطلحات (Glossary)
يكتشف الخطر قبل حدوث الضرر.
شرط + مستوى + رسالة + فعل.
info / warning / critical.
كثرة التنبيهات تجعل المشرف يتجاهلها كلها.
لا نرسل نفس التنبيه أكثر من مرة في الساعة.
عتبتا تشغيل وإيقاف مختلفتان لمنع التذبذب.
Telegram يتيح إرسال رسائل عبر HTTPS برمز Token.
تشغيل سكربت دورياً (*/10 * * * * = كل 10 دقائق).
متى ولماذا ومن (قاعدة/نموذج/إنسان) اتخذ كل قرار.
النظام يقترح أو ينفذ مع إمكانية تدخل الإنسان.
منع إعادة التشغيل لفترة بعد حدث (مثل ري حديث).
آلية تعيد تشغيل النظام أو توقفه إذا علق.
تصميم قواعد الإنذار
25 دقيقة| الشرط | المستوى | الفعل | المصدر |
|---|---|---|---|
| العقدة صامتة > 15 دقيقة | warning | Telegram: افحص الطاقة/الشبكة | system |
| البطارية < 3.5 V | warning | Telegram | system |
| حرارة الهواء ≥ 38°C | critical | Telegram: إجهاد حراري | rule |
| صوبة + رطوبة هواء ≥ 90% | warning | تشغيل المروحة 15 د + Telegram | rule |
| تربة ≥ 75% | — | منع أي ري | rule |
| تربة < 30% أو النموذج ≥ العتبة | warning | ري 10 د تلقائياً + Telegram | rule / ml |
| نفس الحالة + مطر متوقع ≥ 5 مم | info | تأجيل الري + تسجيل السبب | ml |
Alert fatigue حقيقي
لو أرسل النظام «التربة جافة» كل 10 دقائق طوال الليل، سيكتم المشرف البوت — ثم يفوته تنبيه عطل المضخة الحقيقي. لذلك: already_sent() يمنع تكرار نفس الرسالة خلال ساعة، والمستويات تميز الحرج عن العادي.
Tinkercad: وحدة ري تلقائي
30 دقيقةوحدة ري تلقائي تعمل بدون إنترنت (احتياطي محلي)
30 دقيقةإذا انقطع الاتصال بالسيرفر أياماً، يجب ألا يموت المحصول عطشاً. العقدة نفسها تحمل قاعدة احتياطية بسيطة: ري عند الجفاف بـ Hysteresis، حد أقصى للتشغيل، وقفل 30 «دقيقة» بعد كل ري لتصل المياه للحساس قبل قرار جديد.
المكونات (Components)
- Arduino Uno R3
- Soil Moisture Sensor
- Relay SPDT + NPN + 1kΩ + Diode
- DC Motor + 9V battery
- LED أخضر (ري) + LED أحمر (عطل)
- Piezo buzzer
التوصيل (Wiring)
| من | إلى | ملاحظة |
|---|---|---|
| Soil SIG | A1 | |
| D7 | Relay driver (NPN) | المضخة على 9V منفصلة |
| D9 / D12 | LED أخضر / أحمر | 220Ω |
| D8 | Piezo | تنبيه عطل |
ابنِ الدائرة (مثل السيشن 12) وأضف الـ Buzzer.
خفّض رطوبة التربة تحت 30%: يبدأ الري. ارفعها لـ 40%: لا يتوقف (منطقة Hysteresis)، ثم لـ 55%: يتوقف.
أبقِ التربة جافة أثناء الري: بعد 20 «ثانية» (= حد أقصى) يتوقف ويطلق إنذار «ري بدون أثر» (تسريب/مضخة معطلة/خزان فارغ).
// Local fallback auto-irrigation (runs on the node even with no server)
const int SOIL = A1, RELAY = 7, LED_ON = 9, LED_FAULT = 12, BUZZ = 8;
const int DRY_PCT = 30, WET_PCT = 55; // hysteresis band 30..55 %
const unsigned long MAX_RUN = 20000; // 20 s here = 20 min in the field
const unsigned long LOCKOUT = 30000; // wait for water to reach the sensor
unsigned long startedAt = 0, lastStop = 0;
bool running = false, fault = false;
int startPct = 0;
int soilPct() { return constrain(map(analogRead(SOIL), 0, 876, 0, 100), 0, 100); }
void setRun(bool on) {
running = on; digitalWrite(RELAY, on); digitalWrite(LED_ON, on);
if (on) { startedAt = millis(); startPct = soilPct(); } else lastStop = millis();
}
void setup() {
pinMode(RELAY, OUTPUT); pinMode(LED_ON, OUTPUT); pinMode(LED_FAULT, OUTPUT); pinMode(BUZZ, OUTPUT);
setRun(false); lastStop = 0; // fail-safe start
Serial.begin(9600);
}
void loop() {
int s = soilPct();
if (fault) { tone(BUZZ, 2000, 100); delay(1000); return; } // latch until a human resets
if (!running && s < DRY_PCT && millis() - lastStop > LOCKOUT) {
setRun(true); Serial.println("AUTO irrigation start, soil=" + String(s));
}
if (running && s >= WET_PCT) { setRun(false); Serial.println("stop: target reached"); }
if (running && millis() - startedAt > MAX_RUN) {
setRun(false);
if (s - startPct < 5) { // pumped for the max time but soil did not change
fault = true; digitalWrite(LED_FAULT, HIGH);
Serial.println("FAULT: irrigation without effect (leak / empty tank / broken pump)");
}
}
delay(500);
}
Lab: محرك الأتمتة + Telegram
35 دقيقةإنشاء بوت Telegram (5 دقائق)
-
BotFather
في Telegram ابحث عن @BotFather ⇒
/newbot⇒ اسم البوت ⇒ تحصل على Token. -
Chat ID
أرسل أي رسالة لبوتك، ثم افتح في المتصفح
https://api.telegram.org/bot<TOKEN>/getUpdatesوانسخ قيمةchat.id. -
الإعداد الآمن
لا تكتب الـ Token في الكود! استخدم متغيرات البيئة:
set SF_TELEGRAM_TOKEN=...وset SF_TELEGRAM_CHAT_ID=.... -
اختبار
python -c "import automation; automation.notify('test from KFS farm')"
"""Early-warning & automation engine (session 18).
Run every 10 minutes (Windows Task Scheduler / cron: */10 * * * * python automation.py)
or keep it running: python automation.py --loop
Rules + ML output -> alerts (Telegram) + automatic pump/fan commands + decision log.
"""
import json
import sys
import time
import urllib.parse
import urllib.request
from datetime import datetime, timedelta
import config
import db
import predictor
def now():
return datetime.now().isoformat(timespec="seconds")
def notify(text):
"""Send a Telegram message if a bot token is configured (BotFather -> /newbot)."""
if not (config.TELEGRAM_TOKEN and config.TELEGRAM_CHAT_ID):
return False
url = f"https://api.telegram.org/bot{config.TELEGRAM_TOKEN}/sendMessage"
data = urllib.parse.urlencode({"chat_id": config.TELEGRAM_CHAT_ID, "text": text}).encode()
try:
urllib.request.urlopen(url, data=data, timeout=8)
return True
except Exception as exc: # never crash the loop because of the network
print("telegram error:", exc)
return False
def already_sent(node_id, message, minutes=60):
since = (datetime.now() - timedelta(minutes=minutes)).isoformat(timespec="seconds")
return bool(db.rows("SELECT 1 FROM decisions WHERE node_id=? AND message=? AND ts>=?", (node_id, message, since)))
def alert(node_id, level, message, source="rule"):
if already_sent(node_id, message):
return
db.log_decision(now(), node_id, source, level, message)
notify(f"🌱 KFS Smart Farm [{level.upper()}]\n{node_id}: {message}")
def queue_command(node_id, actuator, action, minutes, source):
open_cmd = db.rows("SELECT 1 FROM commands WHERE node_id=? AND actuator=? AND status IN ('pending','delivered')",
(node_id, actuator))
if open_cmd:
return None
minutes = min(minutes, config.PUMP_MAX_MIN)
cid = db.execute("INSERT INTO commands (node_id, actuator, action, duration_min, source, created_by, created_at) "
"VALUES (?,?,?,?,?,?,?)", (node_id, actuator, action, minutes, source, "automation", now()))
db.log_decision(now(), node_id, source, "info", f"auto {actuator} {action} for {minutes} min (command #{cid})")
return cid
def evaluate():
nodes = db.rows("SELECT * FROM nodes")
for n in nodes:
nid = n["node_id"]
last = db.rows("SELECT * FROM readings WHERE node_id=? ORDER BY ts DESC LIMIT 1", (nid,))
if not last:
continue
r = last[0]
age = (datetime.now() - datetime.fromisoformat(r["ts"])).total_seconds() / 60
if age > config.NODE_OFFLINE_MIN:
alert(nid, "warning", f"node offline for {int(age)} min - check power / Wi-Fi", "system")
continue
if r["battery_v"] is not None and r["battery_v"] < config.BATTERY_LOW_V:
alert(nid, "warning", f"battery low ({r['battery_v']} V)", "system")
if r["air_temp_c"] is not None and r["air_temp_c"] >= config.AIR_HOT_C:
alert(nid, "critical", f"heat stress: air {r['air_temp_c']} °C", "rule")
if n["zone"] == "greenhouse" and (r["air_humidity_pct"] or 0) >= config.GREENHOUSE_HUMID_PCT:
alert(nid, "warning", f"humidity {r['air_humidity_pct']}% - fungal risk, fan ON", "rule")
queue_command(nid, "fan", "on", 15, "rule")
soil = r["soil_moisture_pct"]
if soil is None or soil >= config.SOIL_WET_PCT:
continue
pred = predictor.predict(nid)
ml_says = pred.get("ok") and pred["recommendation"] == "irrigate"
if soil < config.SOIL_DRY_PCT or ml_says:
reason = f"soil {soil}% < {config.SOIL_DRY_PCT}%" if soil < config.SOIL_DRY_PCT else \
f"model p={pred['probability']}"
if pred.get("note"):
alert(nid, "info", f"irrigation postponed: {pred['note']}", "ml")
continue
if queue_command(nid, "pump", "on", 10, "ml" if ml_says else "rule"):
alert(nid, "warning", f"irrigation started automatically ({reason})", "ml" if ml_says else "rule")
if __name__ == "__main__":
db.init_db()
if "--loop" in sys.argv:
while True:
evaluate()
time.sleep(600)
else:
evaluate()
print(json.dumps(db.rows("SELECT * FROM decisions ORDER BY id DESC LIMIT 10"), ensure_ascii=False, indent=1))
دورة تقييم واحدة على بيانات اختبار
عقدتان صمتتا 15 دقيقة، وعقدة field_B جافة والنموذج قال p=0.88 ⇒ هذا ما سجله المحرك فعلياً في جدول decisions و commands:
python automation.py # one evaluation
python automation.py --loop # every 10 minutes
المخرجات الفعلية عند التشغيل (Real output)
decisions:
('field_B', 'ml', 'warning', 'irrigation started automatically (model p=0.883)')
('field_B', 'ml', 'info', 'auto pump on for 10 min (command #2)')
('field_A', 'system', 'warning', 'node offline for 15 min - check power / Wi-Fi')
('greenhouse_01', 'system', 'warning', 'node offline for 15 min - check power / Wi-Fi')
commands:
('field_B', 'pump', 'pending', 'ml')| النظام | الطريقة |
|---|---|
| Raspberry Pi / Linux | crontab -e ثم */10 * * * * cd /home/pi/smart-farm && python3 automation.py >> automation.log 2>&1 |
| Windows | Task Scheduler ⇒ Create Basic Task ⇒ Daily ⇒ Repeat every 10 minutes ⇒ Program: python Arguments: automation.py Start in: مجلد smart-farm |
| أي نظام | python automation.py --loop داخل نافذة تبقى مفتوحة (للمعمل فقط) |
ميداني: ري شبه ذاتي
35 دقيقةنظام ري ذكي يعمل تلقائياً بالتوقعات
الصوبة التجريبية: أصيصان متماثلان من نفس المحصول — «ذكي» و «تقليدي»
- التجربة
الأصيص الذكي متصل بعقدة + مضخة يتحكم فيها automation.py. الأصيص التقليدي يُروى يدوياً بالجدول المعتاد.
- التشغيل
شغّل المحرك بالحلقة كل دقيقتين أثناء الجلسة (عدّل sleep مؤقتاً) وفعّل Telegram على هاتف المشرف.
- الاختبارات
(1) جفف الأصيص ⇒ ري تلقائي + رسالة. (2) غطِّ DHT22 بيد رطبة ⇒ رطوبة 90% ⇒ مروحة. (3) افصل العقدة ⇒ رسالة offline.
- التجربة الممتدة
اترك النظام أسبوعاً: قارن كمية الماء المستخدمة (عداد أو حجم الخزان) ونمو النبات بين الأصيصين.
| الوقت | الحدث المختبر | الفعل التلقائي | رسالة Telegram وصلت؟ (ث) | ملاحظة |
|---|---|---|---|---|
- المضخة 5V من دلو فقط.
- المشرف يستطيع إيقاف كل شيء من اللوحة (Human-in-the-loop).
الإنسان في الحلقة وأنماط الفشل
25 دقيقة| ماذا لو… | الأثر | الحماية في نظامنا |
|---|---|---|
| السيرفر توقف | لا أوامر ولا تنبيهات | وحدة الري الاحتياطية المحلية على العقدة (Tinkercad أعلاه) + Watchdog |
| الحساس سقط من التربة (يقرأ جاف دائماً) | ري مستمر | حد أقصى للتشغيل + إنذار «ري بدون أثر» + قفل |
| الحساس في بركة ماء (مبلل دائماً) | لا ري أبداً | مقارنة مع العقد المجاورة + توقع ET0 (النموذج يلاحظ التناقض) |
| تنبؤ مطر خاطئ | تأجيل ري غير لازم | التأجيل 24 ساعة فقط ثم إعادة التقييم + تنبيه info |
| أمر مكرر من QoS 1 | ري مضاعف | queue_command يرفض إذا يوجد أمر مفتوح لنفس المشغل |
| اختراق الحساب | تشغيل مضخات | السيشن 12: كلمات سر قوية، CSRF، حدود زمنية |
فكّر وناقش
متى نسمح للنظام بالري دون انتظار موافقة المشرف؟ ومتى يكتفي بالاقتراح؟
من المسؤول إذا أتلف نظام آلي محصولاً؟ كيف يحمي سجل القرارات الفريق والمزرعة؟
لو صممنا النظام لمزارع صغير في قرية بالدلتا: ما أهم 3 تنبيهات له؟ وبأي لغة؟
اختبر فهمك
5 أسئلة · نتيجة فوريةالواجب والتسليم
الواجب الأسبوعي (Weekly Assignment)
أضف قاعدة جديدة من اختيار فريقك (مثلاً: صقيع < 3°C، أو رياح شديدة توقف الري بالرش) مع اختبارها.
شغّل التجربة الممتدة (أسبوع) ووثق كمية المياه وصور النبات يومياً.
اكتب جدول FMEA لعقدة فريقك (5 أنماط فشل على الأقل).
قائمة التسليم (Submission Checklist)
استعد للسيشن القادمة
السيشن القادمة الخرائط وتحليل الاتجاهات: سجّل إحداثيات GPS الدقيقة لكل عقدة (Google Maps) وأحضرها.