Hasan Mahmud
Müh. Hasan MahmudMekatronik & Gömülü Sistemler
← العودة إلى المقالات

Hasan TechLab / دراسة حالة تقنية

معمارية OTA الآمنة للأجهزة الصناعية: تحديث ESP32 مع التحقق والاسترجاع

دليل تقني متقدم لبناء نظام تحديث Firmware عن بعد عبر Wi-Fi وBLE باستخدام تقسيم Dual-Bank والتحقق بالتوقيع والاسترجاع الآمن.

مقدمة: التحديث عن بعد ليس رفع ملف فقط

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

في هذه الحالة يصبح تحديث البرنامج الثابت Firmware وظيفة تشغيلية حرجة، وليس ميزة إضافية.

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

لذلك صممت معمارية OTA باعتبارها سلسلة ثقة كاملة:

  1. اكتشاف إصدار جديد.
  2. تنزيل الملف على دفعات.
  3. استكمال النقل بعد الانقطاع.
  4. التحقق من الحجم والهاش والتوقيع.
  5. الكتابة إلى قسم غير نشط.
  6. الإقلاع التجريبي.
  7. اعتماد الإصدار أو تنفيذ Rollback.
  8. تسجيل النتيجة وإبلاغ الخادم.

1. متطلبات النظام

قبل اختيار مكتبة OTA، يجب تعريف القيود.

نسأل أولًا:

  • كم حجم Flash المتاح؟
  • هل توجد مساحة لقسمين من التطبيق؟
  • ما سرعة الشبكة في الموقع؟
  • ماذا يحدث عند انقطاع الطاقة؟
  • هل الجهاز يعمل 24/7؟
  • هل التحديث يجب أن يكون مشفرًا؟
  • من يملك صلاحية نشر Firmware؟
  • هل الإصدار الجديد متوافق مع مخطط البيانات؟
  • ما الحد الأقصى المقبول للتوقف؟

الإجابة عن هذه الأسئلة تحدد partition table والبروتوكول وسياسة الاسترجاع.

2. لماذا Dual-Bank؟

في تصميم A/B أو Dual-Bank يوجد قسمان للتطبيق على الأقل.

+------------------+
| Bootloader       |
+------------------+
| OTA Metadata     |
+------------------+
| App A            |  running
+------------------+
| App B            |  download target
+------------------+
| Filesystem       |
+------------------+

إذا كان App A يعمل، نكتب الإصدار الجديد في App B. لا نلمس البرنامج العامل أثناء التنزيل.

بعد اكتمال التحقق، يغير bootloader مؤشر الإقلاع إلى App B. إذا فشل App B في الاختبار الأول، يعود الجهاز إلى App A.

هذه البنية تقلل خطر تلف البرنامج بسبب انقطاع الطاقة، لكنها تحتاج مساحة Flash أكبر.

3. حالات التحديث

استخدم آلة حالات واضحة بدل مجموعة أعلام مبعثرة.

typedef enum {
    OTA_IDLE,
    OTA_CHECKING,
    OTA_DOWNLOADING,
    OTA_VERIFYING,
    OTA_PENDING_REBOOT,
    OTA_TRIAL_BOOT,
    OTA_CONFIRMED,
    OTA_ROLLBACK,
    OTA_FAILED
} ota_state_t;

لكل حالة انتقالات محددة وأسباب فشل مسجلة.

مثلًا، لا تنتقل من OTA_DOWNLOADING إلى OTA_PENDING_REBOOT إلا بعد تحقق الحجم والهاش والتوقيع.

ولا تنتقل إلى OTA_CONFIRMED إلا بعد تشغيل الخدمات الضرورية ومرور اختبار الصحة.

4. اكتشاف الإصدار

يمكن للجهاز الاستعلام عن manifest عبر HTTPS.

مثال مبسط:

{
  "product": "kts200-controller",
  "version": "2.4.1",
  "min_bootloader": "1.3.0",
  "size": 786432,
  "sha256": "...",
  "signature": "...",
  "url": "https://updates.example.com/kts200-2.4.1.bin"
}

لا تثق في version وحده. يجب التحقق من المنتج والطراز والـ hardware revision.

جهاز Rev A قد لا يملك نفس تعريفات GPIO الموجودة في Rev B.

يجب كذلك منع downgrade غير المصرح به إذا كانت النسخة القديمة تحتوي ثغرة أمنية.

5. HTTPS ليس بديلًا عن التوقيع

يحمي HTTPS النقل بين الجهاز والخادم، لكنه لا يثبت وحده أن الملف صدر من فريقك إذا تعرض الخادم أو حساب النشر للاختراق.

يجب توقيع Firmware بمفتاح خاص خارج الجهاز، والتحقق من التوقيع بمفتاح عام مضمّن أو محفوظ في منطقة محمية.

Firmware bytes -> SHA-256 -> digital signature
                                      |
                             public-key verification

المفتاح الخاص لا يوضع في المستودع ولا في جهاز المطور ولا داخل CI logs.

إذا انكشف المفتاح الخاص، نحتاج إلى خطة تدوير مفاتيح وإبطال الإصدارات.

6. التحقق من SHA-256

الهاش يكشف التلف أو التغيير في الملف.

sha256_init(&context);

while (download_has_data()) {
    size_t received = download_chunk(buffer, sizeof(buffer));
    sha256_update(&context, buffer, received);
    flash_write(target_offset, buffer, received);
    target_offset += received;
}

sha256_final(&context, digest);

لا يكفي مقارنة أول بضعة بايتات أو حجم الملف.

يجب مقارنة digest كاملًا مع القيمة الموجودة في manifest الموثوق.

إذا فشل التحقق، لا تجعل القسم الجديد قابلاً للإقلاع.

7. تقسيم التنزيل إلى Chunks

التنزيل دفعة واحدة يستهلك RAM ويصعب استئنافه.

الأفضل استخدام chunks بحجم يناسب TCP وFlash، مثل 4 أو 16 كيلوبايت حسب المنصة.

كل دفعة تحتاج:

  • offset.
  • length.
  • رقم تسلسلي.
  • checksum اختياري.
  • مهلة.
  • عدد محاولات.

عند انقطاع الشبكة، يقرأ الجهاز آخر offset مؤكد ويكمل من هناك.

لكن الاستئناف الآمن يتطلب أن يكون الخادم قادرًا على خدمة HTTP Range أو بروتوكول مكافئ.

8. الكتابة إلى Flash

Flash لا يتعامل مثل RAM.

غالبًا نحتاج إلى مسح sector كامل قبل إعادة الكتابة، ولا يمكن تحويل البت من 0 إلى 1 دون erase.

لذلك يجب:

  1. التأكد من أن العنوان محاذٍ للحد المطلوب.
  2. مسح القطاعات المستهدفة فقط.
  3. الكتابة على دفعات مناسبة.
  4. التحقق من القراءة بعد الكتابة عند الحاجة.
  5. عدم قطع الطاقة أثناء تحديث metadata الحساسة.

لا تسجل عدادًا في نفس القطاع آلاف المرات دون wear leveling.

9. حماية البيانات المحلية

Firmware وبيانات الجهاز شيئان مختلفان.

معالجة التحديث يجب ألا تمس calibration أو counters أو سجلات الأعطال دون خطة ترحيل.

أضف رقم إصدار لمخطط البيانات:

typedef struct {
    uint32_t magic;
    uint16_t schema_version;
    uint16_t payload_length;
    uint32_t crc32;
} settings_header_t;

عند الإقلاع، يقرأ الإصدار الجديد المخطط القديم ويجري migration مرة واحدة.

إذا فشل migration، يجب استخدام نسخة احتياطية أو القيم الافتراضية الآمنة.

10. Bootloader وسجل الإقلاع

يحتاج bootloader إلى metadata مقاومة للانقطاع.

مثال منطقي:

typedef struct {
    uint32_t magic;
    uint8_t active_slot;
    uint8_t pending_slot;
    uint8_t boot_attempts;
    uint8_t confirmed;
    uint32_t metadata_crc;
} boot_record_t;

لا تكتب السجل بطريقة قد تتركه نصف محدث.

استخدم نسختين من السجل أو journal صغيرًا مع sequence number وCRC.

عند الإقلاع، يختار bootloader أحدث سجل صالح.

11. Trial Boot

بعد تثبيت الإصدار الجديد، لا نعلنه مستقرًا فورًا.

يبدأ الجهاز في وضع trial مع عدد محاولات محدود.

خلال هذه المرحلة يتحقق التطبيق من:

  • تشغيل scheduler.
  • توفر الذاكرة الأساسية.
  • قراءة إعدادات صحيحة.
  • الاتصال بالحساسات الحرجة.
  • سلامة watchdog.
  • نجاح self-test.
  • قدرة الجهاز على إرسال heartbeat.

إذا مر كل شيء، يستدعي التطبيق دالة اعتماد الإصدار.

إذا أعيد التشغيل قبل الاعتماد، يزيد bootloader عداد المحاولات.

بعد تجاوز الحد، يعود إلى القسم السابق.

12. Watchdog وRollback

Watchdog ليس مجرد مؤقت لإعادة التشغيل؛ في OTA هو جزء من سياسة الاسترجاع.

إذا علق التطبيق الجديد قبل إعلان نجاحه، يعيد watchdog تشغيل الجهاز.

عند الإقلاع التالي يرى bootloader أن النسخة pending ولم تؤكد، فيختار النسخة المستقرة السابقة.

يجب منع حلقة rollback لا نهائية عبر عداد ومحاولة واضحة.

سجل سبب الاسترجاع:

  • crash.
  • watchdog timeout.
  • failed self-test.
  • invalid signature.
  • missing configuration.
  • network confirmation timeout.

13. التشفير والهوية

هناك فرق بين تشفير الاتصال وتشفير Firmware.

HTTPS يحمي المسار، أما تشفير الملف نفسه فيحمي السرية عند تخزينه أو نسخه.

ليس كل Firmware يحتاج سرية، لكنه يحتاج أصالة وسلامة غالبًا.

إذا كان الملف مشفرًا، يجب إدارة مفتاح فك التشفير داخل الجهاز بعناية، وعدم وضعه في manifest مكشوف.

كما يجب أن يملك كل جهاز هوية فريدة وشهادة أو token قابلًا للإبطال.

14. أقل صلاحيات للخادم

خادم التحديث لا ينبغي أن يملك صلاحية تعديل قواعد البيانات كلها.

قسّم النظام إلى:

  • خدمة بناء وتوقيع.
  • مخزن ملفات read-only بعد النشر.
  • API لإصدار manifest.
  • خدمة telemetry.
  • لوحة صلاحيات للمراجعة.

سجل من نشر النسخة ومتى ولأي مجموعة أجهزة.

التحديث التدريجي أفضل من إرسال Firmware إلى كل الأجهزة دفعة واحدة.

15. Canary وStaged Rollout

ابدأ بنسبة صغيرة من الأجهزة.

راقب crash rate وrollback rate ووقت الإقلاع واستهلاك الذاكرة.

إذا تجاوزت المؤشرات الحدود المسموحة، أوقف النشر تلقائيًا.

مثال لخطة:

1%   لمدة ساعتين
10%  لمدة ست ساعات
30%  لمدة يوم
100% بعد اعتماد النتائج

يجب أن تكون المجموعة الأولى ممثلة للمواقع والشبكات والأجهزة المختلفة، لا مجرد أول عشرة أجهزة متصلة.

16. تحديث BLE وWeb Serial

في بعض البيئات لا يتوفر الإنترنت، لذلك قد تحتاج إلى قناة محلية.

BLE مناسب للإعداد الأولي أو ملف صغير، لكن سرعة النقل والطاقة والمدى محدودة.

Web Serial يسمح لفني متصل عبر USB أو محول مناسب بنقل Firmware من المتصفح.

يجب أن يتطلب التحديث المحلي إثبات صلاحية الملف، لا أن يتجاوز التوقيع لأنه محلي.

المستخدم المحلي قد يملك وصولًا فيزيائيًا، لكنه ليس بالضرورة جهة موثوقة.

17. التعامل مع انقطاع الطاقة

اختبر انقطاع الطاقة في كل مرحلة:

  • أثناء erase.
  • أثناء كتابة chunk.
  • بعد آخر chunk وقبل الهاش.
  • بعد الهاش وقبل تغيير boot flag.
  • أثناء trial boot.
  • أثناء migration للبيانات.

كل حالة يجب أن تنتهي إما باستكمال آمن أو عودة إلى نسخة صالحة.

لا تكتب active slot قبل اكتمال كل عمليات التحقق.

18. اختبارات الأمان

اختبر رفض:

  • ملف بتوقيع خاطئ.
  • ملف معدل بعد التوقيع.
  • منتج غير مطابق.
  • إصدار أقل من minimum version.
  • ملف أكبر من القسم.
  • manifest ناقص.
  • شهادة منتهية.
  • خادم غير موثوق.
  • إعادة إرسال قديمة Replay.

يجب أيضًا اختبار امتلاء Flash وفشل DNS وانتهاء المهلة وتذبذب الطاقة.

19. اختبارات التوافق

احتفظ بمصفوفة توافق بين:

Bootloader Firmware Schema النتيجة
1.3 2.4 7 مدعوم
1.2 2.4 7 مرفوض
1.3 2.3 6 مدعوم
1.3 1.1 2 Downgrade مرفوض

لا تعتمد على رقم الإصدار النصي وحده. قارن major وminor وhardware revision وسياسة الترحيل.

20. المراقبة بعد النشر

أرسل telemetry صغيرة وآمنة:

  • إصدار bootloader.
  • إصدار Firmware.
  • القسم النشط.
  • آخر سبب إقلاع.
  • عدد محاولات trial.
  • آخر نتيجة self-test.
  • إصدار schema.
  • وقت آخر heartbeat.

لا ترسل أسرارًا أو بيانات شخصية ضمن سجلات التحديث.

21. أخطاء شائعة

الكتابة إلى القسم العامل

قد تنجح في المختبر، لكنها تفشل عند انقطاع الطاقة.

الثقة في اسم الملف

اسم firmware-final.bin لا يثبت شيئًا. التوقيع هو المرجع.

عدم اختبار Rollback

الميزة غير المختبرة ليست خطة استرجاع.

خلط Firmware بالبيانات

مسح قسم واحد دون تصميم قد يحذف calibration أو هوية الجهاز.

عدم وجود حد للمحاولات

قد يدخل الجهاز في reboot loop ويستنزف البطارية.

نشر شامل بلا Canary

خطأ واحد قد يعطل أسطولًا كاملًا.

22. نموذج تدفق كامل

Device -> fetch manifest
Device -> verify product and version
Device -> download chunks to inactive slot
Device -> calculate SHA-256
Device -> verify signature
Device -> mark slot pending
Device -> reboot
Bootloader -> start trial slot
Firmware -> run self-test
Firmware -> confirm slot
Device -> report success

إذا فشل أي شرط قبل إعادة التشغيل، يبقى القسم القديم فعالًا.

إذا فشل التطبيق بعد الإقلاع، يتدخل bootloader في الإقلاع التالي.

23. الخلاصة

OTA الآمن هو نظام حياة كامل للبرنامج، وليس endpoint لتحميل ملف.

المعمارية الجيدة تفصل بين التنزيل والتحقق والكتابة والإقلاع والاعتماد.

Dual-Bank يحمي النسخة العاملة، والتوقيع يثبت المصدر، وSHA-256 يكشف التلف، وwatchdog يدعم الاسترجاع، وCanary يقلل أثر الخطأ.

عند اختبار انقطاع الطاقة والملفات الخبيثة وتوافق البيانات والنشر التدريجي، يصبح تحديث الجهاز خدمة يمكن الوثوق بها حتى في المواقع الصناعية البعيدة.