خمول أم عمل؟ سطر واحد من JEXL يفرّق بينهما

    شاحنة متوقفة مزوّدة بمأخذ قدرة؛ ينتقل بت حالة واحد من بوابة المركبة إلى مسار قياس عن بُعد موحّد، حيث يفصل مسارٌ واحد الخمول المُهدِر عن العمل المُنتِج.

    خلاصة سريعة. شاحنة متوقفة ومحرّكها يعمل: هذا إما وقود مُهدَر، أو عمل مأخوذ القدرة (PTO) مدفوع الأجر، والسرعة تُسجِّل صفرًا في الحالتين. سطر واحد من تعبير IoT Logic يقرأ بت الحالة الذي يفرّق بينهما. وفي هذا الاستعراض، يجمِّع وكيلٌ القاعدة كاملةً عبر API — يقرأ المخطط المباشر، ويبني الرسم، ويُفعِّله، ويستقبل بيانات القياس — ثم تؤكِّد قناة مستقلة أن القاعدة حسبت ما قصدته بالضبط. وتبقى اللوحة المرئية هي المكان الذي يراجع فيه الإنسان العمل ويُجيزه ويُصحِّح أخطاءه.

    تقف شاحنة عشر دقائق ومحرّكها يعمل. أهذا هدر أم عمل؟ فقد يكون السائق يُدفئ المقصورة ويحرق الديزل بلا فائدة. أو قد يكون المحرّك يُشغِّل مأخذ قدرة (PTO) — يُشغِّل مضخّة، أو يُحرِّك رافعة — وهذا عمل فعلي يُدفع للأسطول أجره.

    والسرعة وحدها لا تفرِّق بين الحالتين: كلاهما يُسجِّل صفرًا. واستهلاك الوقود بالكاد يفرِّق بينهما هو أيضًا؛ فهو يبدو متشابهًا في الحالتين. والفرق الحقيقي يكمن في بتٍ واحد من كلمة حالة ترسلها بوّابة المركبة.

    والخلاصة قبل التفاصيل: تعبير واحد في IoT Logic يقرأ ذلك البت، فيُحوِّل "متوقفة، والمحرّك يعمل" إلى حكمين متعاكسين تمامًا — وقناة مستقلة، هي نفسها التي لم تكتب القاعدة أبدًا، تُثبت أن القاعدة تحسب ما قصدته فعلًا.

    نقطة واحدة أولًا. تملك IoT Logic محرِّرًا مرئيًّا بالسحب والإفلات، تضع فيه العُقَد على لوحة عمل منخفضة الكود وتصلها ببعضها — وهذا مريح، وميزة حقيقية بالفعل. لكنّها تملك أيضًا واجهة برمجية موثَّقة: فالتدفّق مخطط ذو أنواع محددة، تقرأه وتكتبه عبر REST، وتحمل الوثائق دليل توليد التدفّق بالذكاء الاصطناعي، وتُوفِّر المنصّة خادم MCP مخصَّصًا للقراءة فقط لأغراض التدقيق. وهذه الواجهة هي ما يسمح لوكيل أن يعمل وفق عقد محدَّد، لا وفق شاشة.

    وفي التشغيل الموصوف أدناه، ينجز الوكيل الحلقة كاملة على حساب اختباري قابل للتخلص منه: يقرأ المخطط المباشر، ويجمِّع الرسم، ويُفعِّل القاعدة للتجربة، ويستقبل بيانات القياس، ثم يُعطِّلها من جديد. واللوحة لا تغيب: يفتح إنسانٌ التدفّق نفسه وأداة Data Stream Analyzer لمراجعة الرسم، وإجازة التفعيل في بيئة الإنتاج، وفحص التثبيت على الشاحنة، وتصحيح الأخطاء. الوكيل يُجمِّع؛ والإنسان يحتفظ ببوّابة التفعيل والتحقّق.

    الإجابة تكمن في بتٍ واحد من كلمة الحالة

    هذا ما يصل فعليًّا عبر السلك — عدد صحيح واحد، هو كلمة حالة مُجمَّعة:

    can_status_word = 4   # 0b100 — PTO off
    can_status_word = 5   # 0b101 — PTO engaged (bit 0 set)
    

    يصل هذا العدد إلى المنصّة على هيئة حقل قياس رقمي عادي، شأنه شأن أي حقل آخر. فالبوابة — أي بوابة الجهاز المُركَّبة على المركبة — هي التي تُجمِّع كلمة الحالة؛ وتحليل ناقل CAN وتجميع بتّاته يحدثان على الجهاز نفسه، فلا تتعامل القاعدة إلا مع القيمة التي وصلتها فعلًا، دون حاجة إلى أي برمجية ثابتة (firmware) أو خدمة خارجية.

    وكلمة الحالة هذه مُصطنَعة، جُمِّعت بالطريقة نفسها التي تعتمدها بوابة حقيقية؛ فخُذ التوزيع الفعلي للبتّات من مواصفات جهازك أو من ملف DBC الخاص بك، لا من هذا المقال. والبت 0 هنا موضع توضيحي اختير لعرض الأسلوب فقط — وعليك أن تضع مكانه إزاحة (offset) إشارة PTO الحقيقية في جهازك.

    util:checkBit — عملية فكّ الترميز كاملة في سطر واحد

    تملك IoT Logic لغة تعبير خاصة بها — نفس JEXL التي رأيتها في عشرات الأدوات الأخرى، مع إضافة بضع دوالّ للتعامل مع البتّات. واستخراج البت الذي تحتاجه يبدو على هذا الشكل:

    util:checkBit(can_status_word, 0)
    

    وبإضافة السرعة، تحصل على ثلاث خصائص محسوبة، ومعنى كل واحدة منها عبارة واحدة لا أكثر:

    pto_engaged     = util:checkBit(can_status_word, 0)
    wasteful_idle   = speed < 3 && !util:checkBit(can_status_word, 0)   # parked, PTO off
    productive_idle = speed < 3 &&  util:checkBit(can_status_word, 0)   # parked, PTO on
    

    وفي بيئة الإنتاج، يجدر أن تجمع بين speed < 3 وإشارة تشغيل محرك أو إشعال حقيقية بعامل AND، لتضمن أن "التوقّف" يعني أن المحرك يعمل فعلًا؛ وهنا يقوم speed < 3 بديلًا مؤقتًا عنها، ليبقى التركيز على بت PTO — وهو الجزء الذي لا تستطيع السرعة أن تكشفه بمفردها.

    هذا هو "فكّ الترميز" بأكمله: ثلاثة تعبيرات داخل عقدة قاعدة واحدة، بلا أي خدمة مصغّرة (microservice) أو مُحلِّل خلفي يحتاج إلى صيانة. وتُنتج كل عبارة قيمة منطقية (boolean) تتفرّع القاعدة عليها — فصحّة wasteful_idle هي الشرط الذي تتصرّف بناءً عليه. أما تحويل تلك القيمة المنطقية إلى إشعار فعلي، فخطوة مستقلة تمامًا (مجس أو قاعدة إضافية فوقها)؛ فهذا التدفّق يحسب ويوجِّه فقط. وصياغة اللغة والقائمة الكاملة لعمليات البتّات موجودة في مرجع تعبيرات Navixy.

    والقاعدة نفسها رسمٌ من أربع عُقَد فقط، يُبنى عبر REST: مصدر بيانات، وعقدة للخصائص المحسوبة، وعقدة منطقية، ونقطة نهاية واحدة للخرج. ولا تحمل أي إجراء (action) ولا webhook — فالقاعدة تحسب وتوجِّه، لكنها لا تأمر بشيء. وتُكتب أول نسخة منها بقيمة enabled:false، فلا يلمس الرسمُ الصحيحُ نفسه أي بيانات إلى أن يأتي تفعيل مستقل ومفحوص لاحقًا.

    الوكيل يبني الرسم، وأنت تفتحه للتحقّق

    الرسمُ نفسه الذي جمّعه الوكيل عبر API يُفتح في المحرِّر المرئي بوصفه تدفّقًا عاديًّا تمامًا — عُقدة بعُقدة:

    رسمٌ من أربع عُقَد بناه وكيل عبر API، مفتوح في محرِّر IoT Logic المرئي من Navixy — مصدر بيانات NGP، وعقدة لفكّ ترميز كلمة الحالة، وتفرّع IF/THEN لحالة الخمول المُهدِر تلتقي فيه حافتا «then» و«else» عند نقطة نهاية واحدة للخرج — عمل الوكيل بأكمله قابل للمراجعة على اللوحة منخفضة الكود

    لم يسحب أحد عقدة بيده. لكن لا شيء مخفيّ أيضًا: يفتح إنسان هذه الشاشة، فيرى العُقَد الأربع نفسها والتفرّع، ويعدِّل أيًّا منها، أو يعطِّل التدفّق بمفتاح واحد. اللوحة هي المكان الذي يتحوّل فيه عمل الوكيل إلى شيء قابل للمراجعة.

    بتٌ واحد يُقلب الحكم عند سرعة الصفر

    بناء الرسم لا يكفي وحده — عليك أن تتحقّق من أنه يحسب ما قصدته بالضبط. وسؤال "هل نجا الرسم من عملية الكتابة؟" تُجيب عنه إعادة قراءته؛ أما سؤال "كيف تتصرّف القاعدة مع بيانات حقيقية مباشرة؟" فتُجيب عنه قناة مستقلة تمامًا، هي Data Stream Analyzer (DSA). اشترك في تيار WebSocket الخاص بـ DSA، وأرسل ثلاث حزم قياس — حزمة لكل حالة، ولكل واحدة سرعتها وكلمة حالتها الخاصتان — وسيُظهر DSA، بشكل مستقل تمامًا، ما حسبته القاعدة فعلًا.

    ثلاث بطاقات تقارن بين ما أرسلته حزمة قياس واحدة وما حسبته القاعدة على نبضة Data Stream Analyzer نفسها: تحرّك وPTO غير مُفعَّل (لا شيء يُرصَد)؛ توقّف وPTO غير مُفعَّل (خمول مُهدِر، يُرصَد)؛ توقّف وPTO مُفعَّل (خمول مُنتِج، لا يُرصَد) — نفس سرعة الصفر، وحكمان متعاكسان

    ما أرسلته الحزمة ما حسبته القاعدة على النبضة نفسها
    speed=45, can_status_word=4 — متحرك، PTO غير مُفعَّل pto=false, wasteful=false, productive=false, flagged=false
    speed=0, can_status_word=4 — متوقف، PTO غير مُفعَّل pto=false, wasteful=true, productive=false, flagged=true
    speed=0, can_status_word=5 — متوقف، PTO مُفعَّل pto=true, wasteful=false, productive=true, flagged=false

    اقرأ من الأعلى إلى الأسفل. الحركة لا تُعدّ خمولًا بالمرّة، فلا يُرصَد أي شيء. تتوقّف الشاحنة وPTO غير مُفعَّل — فتصبح wasteful صحيحة وflagged معها: هذا هو الخمول المُهدِر بلا عمل. غيِّر بتًّا واحدًا من كلمة الحالة، وأبقِ السرعة عند الصفر، تنقلب الصورة تمامًا: يُصبح flagged خاطئًا فيما تصبح productive صحيحة. نفس التوقّف تمامًا، وحكمٌ معكوس بالكامل — وهو تحديدًا الفرق الذي لا تستطيع السرعة رؤيته.

    وهناك تفصيل واحد يجعل هذا دليلًا لا مجرّد مصادفة. تحمل كل حزمة علامة فريدة، والفحص لا يبحث إلا عن نبضة واحدة في DSA تحمل تلك العلامة بالضبط، ثم يقارن — داخل تلك النبضة نفسها — بين الحقول المُرسَلة والحقول المحسوبة. فسرعة من رسالة واحدة لا يمكن أن تلتصق خطأً بكلمة حالة من رسالة أخرى. وهذه هي العادة الأساسية هنا: الدليل يأتي من قناة لم تشارك في الكتابة قط.

    وهذا هو التشغيل كلّه، مُختصَرًا في أربع قيم — تشغيل على بيانات مُصطنَعة وحساب اختباري قابل للتخلص منه، طابقت فيه الحالاتُ الثلاث نبضاتها المتوقّعة، وتُركت القاعدة معطَّلة بعد الفحص:

    {
      "status": "succeeded",
      "dsa_verified": true,
      "final_disabled_verified": true,
      "inventory_verified": true
    }
    

    من يفعل ماذا: الوكيل يكتب، واللوحة تتحقّق

    كل فاعل يتحمّل جزءًا واحدًا محددًا، فلا تُؤخَذ إجابة فاعل واحد على الثقة لتغطي البقية:

    • الوكيل، عبر REST، يكتب الإعداد ويتأكّد — بإعادة قراءته — من أن الرسم وقيمة enabled:true استقرّا فعليًّا في المنصّة.
    • NGP يستقبل بيانات القياس؛ ورمز استجابته HTTP 200 لا يشهد إلا على وصول البيانات، لا أكثر.
    • DSA يُظهر النتيجة المحسوبة بشكل مستقل تمامًا على التيار.
    • User MCP يبقى في جهة القراءة وحدها — خادم User MCP يقرأ حالة التدفّق ويُدقِّقها، بينما يمرّ كل تعديل فعلي عبر REST.
    • الإنسان يعمل داخل المحرِّر المرئي: يتحقّق من صحّة التدفّق الذي بناه الوكيل، ويُجيز التفعيل، ويفحص البيانات على الشاحنة المُجهَّزة فعليًّا، ويُصحِّح الأخطاء عبر DSA، ويملك مفتاح التبديل، وهو صاحب مفتاح API. هذه هي نقطة التحكّم — إنسانٌ ضمن الحلقة فوق الوكيل.

    والافتراض العملي الذي أتمسّك به هو هذا: لا تُعامل القاعدة على أنها تعمل حين تنجح عملية create فحسب، بل حين تُظهر قناة لم تشارك في الكتابة النتيجةَ التي توقّعتها. ولمسار REST المباشر هذا نظير داخل المنتج نفسه — Navixy AI Assistant، حيث يجمِّع المساعد التدفّق ويؤكّده إنسان. وكلاهما ينتهي إلى الكائن نفسه على اللوحة نفسها.

    متى لا تحتاج إلى فكّ ترميز أي بت

    هذا الأسلوب يُثمر في نطاق محدود لا أكثر. فإن كانت بوابتك توفِّر أصلًا حقلًا جاهزًا لعلامة "PTO مُفعَّل" بوصفه حقلًا مستقلًا بذاته، فخُذ هذا الحقل كما هو، ولا تفكّ ترميز أي شيء. وإن كان السؤال عارضًا لمرّة واحدة والبيانات محدودة الحجم، فتصدير جدول بيانات وعشر دقائق من العمل اليدوي يتفوّقان على أي قاعدة.

    وفكّ ترميز البتّات في IoT Logic يستحقّ مكانه حين تصل الإشارة مُجمَّعة، ضمن تيار مستمر، وأنت تريد أن تفرِّق بين الحالات لحظيًّا، لا أن تعيد بناءها من السجلّات بعد وقوع الأمر.

    إشارتك أنت: ما تُغيّره، وما تُبقيه

    استبدل can_status_word وبت العرض التجريبي بحقل رقمي حقيقي من جهاز التتبّع أو البوابة الخاصة بك، وضع الإزاحة (offset) التي تخصّصها مواصفات جهازك أو ملف DBC للإشارة التي تهمّك فعلًا. واحتفظ بترتيب الخطوات نفسه: المخطط المباشر أولًا، ثم البناء بقيمة enabled:false، فإعادة القراءة، فتحديث كامل عند التفعيل، فأخيرًا فحص عبر DSA مرتبط بعلامة فريدة.

    وأول أمر يستحقّ التشغيل اليدوي هو فحص تمهيدي: قراءة المخطط المباشر والتأكّد من أن بيئتك تحتوي على أنواع العُقَد التي تحتاجها. ويُنشأ المفتاح مرة واحدة من إعدادات الحساب ← مفاتيح API، ويُقرأ من متغيّر بيئة:

    curl -fsS \
      -H "Authorization: NVX ${NAVIXY_API_KEY}" \
      -H 'Accept: application/json' \
      'https://api.us.navixy.com/v2/iot/logic/flow/schema'
    

    وبعد ذلك، تأتي التعبيرات الثلاثة أعلاه، مع إعادة قراءة بعد كل عملية كتابة. فإعادة القراءة تؤكّد أن التدفّق استقرّ كما كُتب؛ وفحص DSA المستقل يؤكّد أنه يحسب الحالات التي قصدتَها بالضبط.

    وكل هذا يبقى في صورة استدعاءات منفصلة، مع إعادة قراءة وفحص DSA، لسبب محدد: فكل خطوة تحصل بذلك على نتيجة نجاح أو فشل يمكن لأي آلة أن تتحقّق منها — تطابق واحد عند البحث عن مسمّى، ونبضة واحدة في DSA تحمل علامتها الفريدة، وقيمة enabled:false نهائية تؤكّدها قراءة. والمخطط المصنَّف بالأنواع، ودليل توليد التدفّق بالذكاء الاصطناعي، وخادم MCP المخصَّص للقراءة فقط — كل ذلك موجود لكي يستطيع وكيل أن يُشغِّل تلك الحلقة بنفسه.

    والمحرِّر يبقى الطريقة المألوفة لبناء تدفّق، وهذا أمرٌ مريح بالفعل. لكنّني أُراهن أن تجميع الرسوم سيستمر في الانتقال إلى الوكلاء، بينما تبقى اللوحة وDSA حيث يتحقّق الإنسان، ويفحص التثبيت، ويُصحِّح الأخطاء — تاركًا عملية واحدة لا يمكن تفويضها: إنشاء المفتاح، وإلغاؤه.

    مشاركة المقال