0Pricing
AI Agents · درس

الاستجابة الآلية لأحداث المستشعرات

إذا تجاوزت درجة الحرارة الحد ← تنبيه ← تشغيل المشغّل: حلقات تحكم IoT يقودها الوكيل.

الاستجابة الآلية لأحداث المستشعرات درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.

استجابات الوكلاء الآلية المدفوعة بالأحداث

عندما يتجاوز مستشعر ما عتبة معينة، يجب على الوكيل الاستجابة تلقائيًا من دون تدخل بشري. وتتمثل التحديات الأساسية في تحديد الإجراء المطلوب، وضمان ألا يؤدي الحدث نفسه إلى تشغيل إجراءات مكررة، واحترام فترة تهدئة حتى لا يغمر الوكيل المشغلات بالأوامر.

تعريف سياسات الإجراءات

تربط سياسة الإجراء بين حالات المستشعر واستجابات الوكيل. عرّف السياسات بطريقة تصريحية لتكون سهلة القراءة والتعديل دون المساس بشيفرة المنطق. وتتضمن كل سياسة شرطًا وأولوية وإجراءً واحدًا أو أكثر.

ACTION_POLICIES = [
    {
        'name': 'HIGH_TEMP_ALERT',
        'topic': 'sensors/temperature',
        'condition': lambda v: v > 38,
        'priority': 'critical',
        'actions': ['TURN_ON_COOLING', 'ALERT_MAINTENANCE', 'LOG_EVENT']
    },
    {
        'name': 'HIGH_TEMP_WARNING',
        'topic': 'sensors/temperature',
        'condition': lambda v: 35 < v <= 38,
        'priority': 'warning',
        'actions': ['ALERT_MAINTENANCE', 'LOG_EVENT']
    },
    {
        'name': 'LOW_HUMIDITY',
        'topic': 'sensors/humidity',
        'condition': lambda v: v < 30,
        'priority': 'warning',
        'actions': ['TURN_ON_HUMIDIFIER', 'LOG_EVENT']
    }
]

def match_policies(topic: str, value: float) -> list:
    return [
        p for p in ACTION_POLICIES
        if p['topic'] == topic and p['condition'](value)
    ]

if __name__ == '__main__':
    matches = match_policies('sensors/temperature', 39)
    print('Matched policies for temperature=39:')
    for p in matches:
        print(f"  {p['name']} ({p['priority']}): {p['actions']}")

قائمة انتظار الإجراءات

تفصل قائمة انتظار الإجراءات بين اكتشاف الأحداث وتنفيذ الإجراءات. تُضاف الأحداث إلى قائمة الانتظار، ثم يسحبها عامل وينفذها. ويمنع ذلك حجب حلقة استقبال MQTT، كما يتيح إعادة المحاولة عند فشل أحد الإجراءات.

import queue
import threading
from datetime import datetime

action_queue: queue.Queue = queue.Queue(maxsize=500)

def enqueue_action(action_name: str, context: dict, priority: str = 'normal'):
    item = {
        'action': action_name,
        'context': context,
        'priority': priority,
        'enqueued_at': datetime.utcnow().isoformat()
    }
    try:
        action_queue.put_nowait(item)
        print(f'Enqueued: {action_name}')
    except queue.Full:
        print(f'WARNING: Action queue full, dropping {action_name}')

def action_worker(executor_fn):
    """Run in a background thread, executing actions from the queue."""
    while True:
        item = action_queue.get()
        try:
            executor_fn(item['action'], item['context'])
        except Exception as e:
            print(f'Action failed: {item["action"]} — {e}')
        finally:
            action_queue.task_done()

# Start worker thread:
# worker_thread = threading.Thread(target=action_worker, args=(execute_action,), daemon=True)
# worker_thread.start()

if __name__ == '__main__':
    enqueue_action('TURN_ON_COOLING', {'zone': 'server-room'}, priority='critical')
    enqueue_action('LOG_EVENT', {'msg': 'temperature nominal'})
    print('Queue size:', action_queue.qsize())

إزالة تكرار الأحداث

من دون إزالة التكرار، تؤدي درجة حرارة تبقى أعلى من 38°C لمدة 10 دقائق بمعدل قراءة واحدة في الثانية إلى إنشاء 600 حدث متطابق. وتضمن إزالة التكرار تشغيل التركيبة نفسها من (الموضوع، الشرط، الإجراء) مرة واحدة فقط لكل حدث، مع إعادة ضبطها عند زوال الشرط.

class EventDeduplicator:
    def __init__(self):
        # active_events: (topic, policy_name) -> event_start_time
        self._active: dict = {}

    def is_new_event(self, topic: str, policy_name: str) -> bool:
        key = (topic, policy_name)
        return key not in self._active

    def mark_active(self, topic: str, policy_name: str):
        self._active[(topic, policy_name)] = datetime.utcnow()

    def clear_event(self, topic: str, policy_name: str):
        key = (topic, policy_name)
        if key in self._active:
            duration = (datetime.utcnow() - self._active.pop(key)).seconds
            print(f'Event cleared: {policy_name} (lasted {duration}s)')

    def clear_topic_if_normal(
        self, topic: str, value: float, normal_fn
    ):
        if normal_fn(value):
            keys = [k for k in self._active if k[0] == topic]
            for k in keys:
                self.clear_event(k[0], k[1])

dedup = EventDeduplicator()
dedup.mark_active('sensors/temperature', 'HIGH_TEMP_ALERT')
print('New event?', dedup.is_new_event('sensors/temperature', 'HIGH_TEMP_ALERT'))

فترة التهدئة

حتى بعد زوال الحدث ثم تشغيله من جديد، تمنع فترة التهدئة إعادة التشغيل بسرعة. اجمع بين إزالة التكرار (التشغيل مرة واحدة ما دام الشرط قائمًا) وفترة التهدئة (الانتظار N من الدقائق بعد زوال الشرط قبل السماح بتشغيل التنبيه نفسه مرة أخرى).

from datetime import datetime, timedelta

class CoolDownManager:
    def __init__(self, cool_down_minutes: int = 15):
        self.cool_down = timedelta(minutes=cool_down_minutes)
        self._cleared_at: dict = {}  # (topic, policy) -> cleared_datetime

    def is_in_cool_down(self, topic: str, policy_name: str) -> bool:
        key = (topic, policy_name)
        cleared_at = self._cleared_at.get(key)
        if cleared_at is None:
            return False
        return datetime.utcnow() - cleared_at < self.cool_down

    def record_clear(self, topic: str, policy_name: str):
        self._cleared_at[(topic, policy_name)] = datetime.utcnow()

    def time_remaining(self, topic: str, policy_name: str) -> int:
        key = (topic, policy_name)
        cleared_at = self._cleared_at.get(key)
        if cleared_at is None:
            return 0
        elapsed = datetime.utcnow() - cleared_at
        remaining = self.cool_down - elapsed
        return max(0, int(remaining.total_seconds()))

cooldown = CoolDownManager(cool_down_minutes=15)
cooldown.record_clear('sensors/temperature', 'HIGH_TEMP_ALERT')
print('In cool-down?', cooldown.is_in_cool_down('sensors/temperature', 'HIGH_TEMP_ALERT'))

اتخاذ قرار الإجراء بمساعدة LLM

في المواقف المعقدة — مثل التنبيهات المتزامنة المتعددة، أو السياسات المتعارضة، أو مجموعات القراءات غير المعتادة — فوّض اتخاذ القرار إلى LLM. ويتلقى LLM السياق الكامل للمستشعر ويقترح خطة إجراءات مرتبة حسب الأولوية.

import anthropic
import json

def llm_decide_actions(
    sensor_readings: dict,
    active_policies: list
) -> list:
    client = anthropic.Anthropic(api_key='YOUR_API_KEY')
    context = json.dumps({
        'readings': sensor_readings,
        'triggered_policies': [p['name'] for p in active_policies]
    }, indent=2)
    prompt = (
        f'Current sensor state:\n{context}\n\n'
        'Multiple alert policies are active. '
        'Recommend an ordered list of actions to take. '
        'Consider conflicting effects (e.g., humidifier and cooling may conflict).\n'
        'Return JSON: {"recommended_actions": [str], "reasoning": str}'
    )
    response = client.messages.create(
        model='claude-opus-4-5', max_tokens=512,
        messages=[{'role': 'user', 'content': prompt}]
    )
    return json.loads(response.content[0].text)

تنفيذ الإجراءات عبر MQTT

تُنفَّذ الإجراءات من خلال نشر رسائل أوامر إلى موضوعات MQTT الخاصة بكل جهاز. ويتبع حمْل الأمر مخططًا قياسيًا يتضمن اسم الإجراء، والمعلمات، ومعرّف الطلب لتأكيد الاستلام، وTTL (تنتهي صلاحية الأمر إذا ظل الجهاز غير متصل مدة طويلة).

import json
import uuid
from datetime import datetime, timedelta

ACTION_TOPICS = {
    'TURN_ON_COOLING': 'devices/hvac/commands',
    'TURN_OFF_COOLING': 'devices/hvac/commands',
    'TURN_ON_HUMIDIFIER': 'devices/humidifier/commands',
    'ALERT_MAINTENANCE': 'notifications/maintenance',
    'LOG_EVENT': 'logs/agent_events'
}

def execute_action(action_name: str, context: dict, mqtt_client) -> str:
    topic = ACTION_TOPICS.get(action_name)
    if not topic:
        print(f'No topic defined for action: {action_name}')
        return 'unknown_action'

    request_id = str(uuid.uuid4())[:8]
    ttl = (datetime.utcnow() + timedelta(minutes=5)).isoformat()
    payload = json.dumps({
        'action': action_name,
        'request_id': request_id,
        'context': context,
        'ttl': ttl
    })
    mqtt_client.publish(topic, payload, qos=1)
    print(f'Executed {action_name} -> {topic} (req={request_id})')
    return request_id

if __name__ == '__main__':
    class FakeMQTT:
        def publish(self, topic, payload, qos=1):
            pass

    execute_action('TURN_ON_COOLING', {'zone': 'server-room'}, FakeMQTT())

تأكيد استلام الإجراء

ينبغي للأجهزة تأكيد استلام الأوامر من خلال النشر في موضوع تأكيد الاستلام. ويشترك الوكيل في موضوعات ack ويمكنه إعادة المحاولة إذا لم يتلقَّ تأكيدًا خلال مدة زمنية محددة.

import threading
from collections import defaultdict

class AckTracker:
    def __init__(self, timeout_seconds: int = 30):
        self.timeout = timeout_seconds
        self._pending: dict = {}  # request_id -> {'action', 'send_time', 'ack_event'}

    def register(self, request_id: str, action_name: str):
        event = threading.Event()
        self._pending[request_id] = {
            'action': action_name,
            'send_time': datetime.utcnow(),
            'ack_event': event
        }
        # Schedule timeout check
        t = threading.Timer(self.timeout, self._on_timeout, args=[request_id])
        t.daemon = True
        t.start()

    def acknowledge(self, request_id: str):
        entry = self._pending.pop(request_id, None)
        if entry:
            entry['ack_event'].set()
            print(f'Ack received for {entry["action"]} (req={request_id})')

    def _on_timeout(self, request_id: str):
        if request_id in self._pending:
            action = self._pending.pop(request_id)['action']
            print(f'TIMEOUT: No ack for {action} (req={request_id}) — retry?')

مسار معالجة أحداث المستشعر بالكامل

تجميع جميع المكونات: استقبال MQTT ← مطابقة السياسة ← التحقق من إزالة التكرار وفترة التهدئة ← وضع الإجراءات في قائمة الانتظار ← تنفيذ العامل للإجراءات عبر النشر باستخدام MQTT ← تتبع تأكيدات الاستلام. تتعامل هذه البنية مع آلاف أحداث المستشعرات في الدقيقة من دون حجب التنفيذ.

class IoTAgentPipeline:
    def __init__(self, mqtt_client):
        self.mqtt = mqtt_client
        self.dedup = EventDeduplicator()
        self.cooldown = CoolDownManager(cool_down_minutes=15)
        self.ack_tracker = AckTracker(timeout_seconds=30)

    def on_sensor_message(self, topic: str, value: float):
        policies = match_policies(topic, value)
        self.dedup.clear_topic_if_normal(
            topic, value,
            normal_fn=lambda v: v <= 35  # below warning threshold
        )

        for policy in policies:
            name = policy['name']
            if not self.dedup.is_new_event(topic, name):
                continue  # already active, skip
            if self.cooldown.is_in_cool_down(topic, name):
                print(f'In cool-down: {name}')
                continue

            self.dedup.mark_active(topic, name)
            ctx = {'topic': topic, 'value': value, 'policy': name}
            for action in policy['actions']:
                enqueue_action(action, ctx, policy['priority'])

مسار التصعيد

تتطلب بعض الحالات تصعيدًا إلى الإنسان، مثل الفشل المتكرر في تأكيد استلام أمر، أو استمرار الحالات الحرجة، أو تعارض سياسات متعددة. عرّف مسار تصعيد يرسل إشعارًا فوريًا أو ينشئ تذكرة.

import requests

def escalate_to_human(
    reason: str,
    sensor_data: dict,
    webhook_url: str = 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
):
    message = {
        'text': (
            f'*IoT Agent Escalation* \n'
            f'Reason: {reason}\n'
            f'Sensor data: {sensor_data}\n'
            f'Time: {datetime.utcnow().isoformat()}'
        )
    }
    try:
        response = requests.post(webhook_url, json=message, timeout=5)
        response.raise_for_status()
        print(f'Escalation sent: {reason}')
    except requests.RequestException as e:
        print(f'Escalation failed: {e}')
        # Fall back: log to file
        with open('escalations.log', 'a') as f:
            import json
            f.write(json.dumps({'reason': reason, 'data': sensor_data}) + '\n')

اختبار مسار معالجة الأحداث

قبل نشر مسار معالجة الأحداث في بيئة الإنتاج، اكتب اختبارات آلية تحاكي أحداث المستشعرات وتتحقق من وضع الإجراءات الصحيحة في قائمة الانتظار. اختبر كل سياسة على حدة، وسلوك إزالة التكرار، وانتهاء فترة التهدئة.

import time

def test_high_temp_policy_fires_once():
    dedup = EventDeduplicator()
    cooldown = CoolDownManager(cool_down_minutes=0)  # disable cooldown for test
    pipeline = IoTAgentPipeline(None)
    pipeline.dedup = dedup
    pipeline.cooldown = cooldown

    actions_fired = []
    action_queue.queue.clear()

    # Fire same event 5 times in a row
    for _ in range(5):
        pipeline.on_sensor_message('sensors/temperature', 40.0)

    # Only 1 set of actions should have been enqueued
    actions = list(action_queue.queue)
    assert len(actions) > 0, 'At least one action should fire'
    print(f'Actions enqueued: {len(actions)} (expected: just 1 event worth)')
    return True

result = test_high_temp_policy_fires_once()
print('Test passed:', result)

اختبار المعرفة

ما الغرض الأساسي من إزالة تكرار الأحداث في مسار معالجة أحداث المستشعرات؟

مراجعة: الاستجابة الآلية لأحداث المستشعرات

ممتاز! إليك ما تعلمته:

  • سياسات الإجراءات: ربط تصريحي بين الشروط والإجراءات مع تحديد الأولويات
  • قائمة انتظار الإجراءات: فصل الاكتشاف عن التنفيذ؛ إذ يعالج خيط العامل الإجراءات
  • إزالة التكرار: التشغيل مرة واحدة لكل حدث، وليس مرة واحدة لكل قراءة
  • فترة التهدئة: منع إعادة التشغيل مباشرةً بعد زوال الشرط
  • تصعيد LLM: تفويض الحالات المعقدة متعددة السياسات إلى استدلال LLM
  • تتبع تأكيدات الاستلام: اكتشاف الأوامر التي لم يُؤكَّد استلامها وإعادة المحاولة

التالي: نشر وكلاء خفيفي الوزن على الأجهزة الطرفية مثل Raspberry Pi.

الأسئلة الشائعة

هل درس «الاستجابة الآلية لأحداث المستشعرات» مجاني؟

نعم — نص درس «الاستجابة الآلية لأحداث المستشعرات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.

ماذا ستتعلم في «الاستجابة الآلية لأحداث المستشعرات»؟

إذا تجاوزت درجة الحرارة الحد ← تنبيه ← تشغيل المشغّل: حلقات تحكم IoT يقودها الوكيل. تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟

لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «الاستجابة الآلية لأحداث المستشعرات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟

نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. بروتوكول MQTT لتكامل الوكلاء
  2. معالجة بيانات السلاسل الزمنية لدى الوكلاء
  3. الاستجابة الآلية لأحداث المستشعرات
  4. نشر الوكلاء خفيفي الوزن على الحافة
← العودة إلى AI Agents