پلن پایداری فریم‌ور CM

پلن پایداری و ارتقای کیفیت فریم‌ور CM

کنترلر صنعتی IoT روی STM32F103RET6 با embOS، مودم Quectel M66 (MQTT/FOTA)، RS-485 و دو سنسور SHT30. این سند خروجی ممیزی خط‌به‌خط اپلیکیشن v1.8.0 و بوت‌لودر v1.7.0، تحلیل استاتیک، داده‌ی مصرف استک کامپایلر و تست‌های زنده روی شبیه‌ساز Renode است، و یک برنامه‌ی سه‌مرحله‌ای برای بستن ریسک‌های میدانی می‌دهد.

تاریخ ممیزی: ۱۳۹۹/۰۶/۲۹ – ۲۰۲۶‑۰۹‑۲۰ سورس: cm_fw/STM32_CM_V_1_8_Application، cm_fw/STM32_CM_V_1_8_Bootloader گزارش انگلیسی با شماره‌ی خط: cm_sim/docs/firmware-audit-2026-09-19.md

۱. خلاصه‌ی وضعیت

9
یافته‌ی سطح A: می‌تواند دستگاه را برای همیشه از کار بیندازد
15
یافته‌ی سطح B: رفتار غلط، قطعی، خرابی داده
6
دسته‌ی سطح C: نگه‌داری‌پذیری و بیلد
۶ روز
کار مهندسی هفته‌ی اول که همه‌ی مسیرهای بریک دائمی را می‌بندد
۱۰–۱۱
هفته‌-مهندس برای کل پلن (سه مرحله)
موضوعوضعیت فعلیپیامد
بیلد Release-O0، کتابخانه‌ی embOS نسخه‌ی Debug+Profiling، فقط -Wallسرریزهای بافر پنهان می‌مانند؛ اپ ۱۱۵ کیلوبایت به‌جای ۶۰ تا ۸۰
تسک‌ها۴ تسک، همه اولویت ۲ (round-robin)، استک ۲/۲/۴/۲ کیلوبایتکار بلاک‌کننده‌ی هر تسک زمان بقیه را می‌خورد؛ VLA روی استک ۲ کیلوبایتی
واچ‌داگ۳ ثانیه، refresh از حدود ۶۰ نقطه (حتی داخل uart_debug_print)؛ بوت‌لودر واچ‌داگ نداردتسک گیرکرده دیده نمی‌شود؛ HardFault در بوت‌لودر یعنی توقف دائمی
پیکربندی در فلشرشته‌ی CSV بدون CRC، ۳۰ فراخوانی strtok بدون بررسی NULL، بازنویسی در هر بوترکورد ناقص = HardFault؛ فرسایش صفحه در چند روز
FOTACRC فقط برای هر فایل؛ بدون CRC کل ایمیج، بدون rollback؛ پرش کور بوت‌لودردر حالت‌های رایج نصب نمی‌شود یا اپ پاک‌شده می‌ماند
حافظهحدود ۴۰ از ۶۴ کیلوبایت RAM؛ heap و استک اصلی مشترکحاشیه‌ی کم برای بافرهای بزرگ و پیام‌های بلند

۲. شاهد زنده از شبیه‌ساز: FOTA کامل می‌شود ولی نصب نمی‌شود

بازتولید شد. با دو IOX پیکربندی‌شده، یک FOTA کامل اجرا شد: ۲۱۷ فایل از FTP دانلود و در فلش staging نوشته شد، اپ {Installing...} فرستاد، ریست کرد و به سرور FOTA >> Successfully done گزارش داد. بوت‌لودر اما flash_fota_flag: 0 خواند، نوشت No New Program to write! و نسخه‌ی قبلی را بالا آورد. سرور فکر می‌کند دستگاه آپدیت شده؛ نشده است.

رکورد واقعی خوانده‌شده از فلش شبیه‌ساز (آدرس 0x08007800)، بعد از FOTA ...,00000004, 01 , 16 , 00 , 10 , 02 , 00 , 00000000 , {"st":0}& ,... آنچه اپ نوشته (flash.c، شاخه‌ی iox_num=2) io_en outs pp_en pin iox=2 FOTA=1 size recipe هیچ شناسه‌ی IOX نوشته نمی‌شود آنچه بوت‌لودر می‌خواند (main.c:520-575): به تعداد iox_num شناسه می‌خورد io_en outs pp_en pin iox=2 id[0] id[1] FOTA=0 پرچم FOTA به‌عنوان شناسه‌ی IOX مصرف می‌شود؛ strtoul روی {"st":0} صفر برمی‌گرداند؛ نصب انجام نمی‌شود. با iox_num>3 هیچ شاخه‌ای در نویسنده نیست و بافر مقداردهی‌نشده نوشته می‌شود؛ رکورد ناقص = strtok→NULL = HardFault در بوت‌لودر بدون واچ‌داگ.
شکل ۱. ناسازگاری فرمت رکورد پیکربندی بین اپ و بوت‌لودر. همین ناسازگاری در حالت‌های دیگر به توقف دائمی می‌رسد (یافته‌ی A1).

۳. ماتریس ریسک

اول این‌ها: شدت بالا، هزینه‌ی کم A بریک دائمی B قطعی / داده‌ی غلط C نگه‌داری کمتر از یک روز ۱ تا ۳ روز یک هفته یا بیشتر هزینه‌ی رفع A6 A8 A4 A7 A9 A3 A1 A2 A5 B3 B2 B8 B15 B9 B4 B10 B11 B1 B5 B6 B13 B14 C1 C2 C3
شکل ۲. شدت در برابر هزینه‌ی رفع. ناحیه‌ی رنگی بالا-راست ترتیب شروع است: A6، A8، A4، A7، A9، A3 هر کدام کمتر از یک روز کار دارند. C1 = بیلد/هشدارها، C2 = حذف کد مرده و کپی‌ها، C3 = بازسازی ماژولی.

۴. یافته‌های سطح A: می‌تواند دستگاه را بریک یا قفل کند

#مشکلمحلچطور رخ می‌دهداصلاحزمان
A1پارس متادیتا بدون بررسی NULL؛ فقط ۸ بایت اول اعتبارسنجی می‌شود؛ فرمت نویسنده و خواننده ناسازگارflash.c:439-675
boot main.c:415-645
قطع برق حین نوشتن در هر بوت؛ رکورد بلندتر از ۲۰۰ بایت (SET_LOG و SET_MSV)؛ iox_num ۱ یا ۲ (بدون شناسه) یا بیشتر از ۳ (بدون شاخه)؛ در بوت‌لودر while(1) بدون واچ‌داگساختار باینری نسخه‌دار + CRC32، دو صفحه‌ی A/B، fallback به پیش‌فرض، یک فایل مشترک اپ/بوت‌لودر۲ روز
A2سرریز بافر استک در مسیر بوتflash.c:167-183, 506, 660
boot 482, 631
bf[32] در هر بوت ۴۳+ بایت می‌گیرد؛ bf[50] با recipe تا ۱۰۰؛ received_data[130] با حلقه‌ی بی‌حد؛ فقط با چیدمان -O0 زنده استsnprintf/strlcat، حلقه‌های کران‌دار، terminator صریح، -Wstringop-overflow۱ روز
A3لیست سنسور قبل از اعتبارسنجی در فلش نوشته می‌شودmodbus.c:150-166
flash.c:170-186
پیام ["05","A1","A2"] از سرور = strncpy(…,NULL) در بوت بعدی = بوت‌لوپ تا فلش مجدد؛ نوشتن بیرون از پیام داخل ring buffer صفپارس به ساختار RAM با حد، اعتبارسنجی، سپس ذخیره با CRC۰٫۵ روز
A4فرسایش فلش صفحه‌ی پیکربندیproc.c:37, boot 1159
io.c:598, quectel.c:3656
پاک/نوشتن در هر بوت و هر فرمان؛ بدون سیم‌کارت هر ۵ تا ۱۰ ثانیه یک سیکل → ۱۰ هزار سیکل (حداقل دیتاشیت) در حدود یک روزنوشتن فقط در صورت تغییر؛ شمارنده‌ها در رجیستر BKP یا .noinit؛ دو صفحه با شماره‌ی توالی؛ عدم ذخیره‌ی toggle سیم‌کارت۱ روز
A5بوت‌لودر بدون اعتبارسنجی می‌پرد و واچ‌داگ نداردboot main.c:1166-1209شکست کپی FOTA: اپ پاک شده و پرچم پاک می‌شود → پرش به 0xFFFFFFFF؛ SCB->VTOR = مقدار SP؛ __disable_irq تا اپ می‌ماند و timeoutهای HAL نمی‌گذرندبررسی SP/reset vector، IWDG در بوت‌لودر، VTOR درست، __enable_irq، CRC ایمیج قبل از پاک کردن، حفظ پرچم تا معتبر شدن اپ۲ روز
A6اشاره‌گرهای آویزان اعتبار FTPproc.c:1012-1024
quectel.c:3123-3245
اشاره به داخل ring buffer صفی که پاک می‌شود؛ رشته‌ی بدون پایان با strcat → خرابی استک Task_Quectelstrlcpy به بافر استاتیک۱ ساعت
A7VLA روی استک ۲ کیلوبایتی Task_Operatorproc.c:569, 689
quectel.c:610, 1339
با بیش از ۲۰ سنسور char bf[len] از ۲۰۴۸ می‌گذرد → OS_Error → IWDG → بوت‌لوپ؛ payload_buffer[1024] و fota_buff[975] از پیام ۱۵۰۰ بایتیحذف VLA؛ ساخت پیام در بافر استاتیک؛ اندازه‌ی استک از .su + ۲۵٪۰٫۵ روز
A8پاک کردن ۱۰۰ صفحه در یک فراخوانی در برابر واچ‌داگ ۳ ثانیهflash.c:46-82
quectel.c:3819-3823
۲ تا ۴ ثانیه توقف هسته؛ ریست وسط FOTA؛ بازنویسی بوت‌لودر از داخل اپ بدون fallback = بریک با قطع برقپاک کردن صفحه‌به‌صفحه با refresh؛ ممنوعیت آپدیت بوت‌لودر از اپ تا طراحی دومرحله‌ای۰٫۵ روز
A9کپی پاسخ RS-485 بدون حد در اسلات ۲۲ بایتیmodbus.c:101-136
io.c:413
نویز باس modbus_num را خراب می‌کند → حلقه روی ۲۵۵ سنسور → سرریز all_buffer[1800]؛ strstr()+1 روی NULLکران کپی، اعتبارسنجی LRC/CRC پاسخ، بررسی NULL۰٫۵ روز

۵. یافته‌های سطح B: رفتار غلط، قطعی، خرابی داده

#مشکلمحلاصلاح
B1پارس JSON سرور با آفست ثابت (msg[40]، msg+26، &msg[20]io_set_out بدون حد روی Out_Array[32]proc.c:421-455, io.c:533, modbus.c:153توکنایزر jsmn + جدول فرمان با فیلدهای تایپ‌دار و کران‌دار
B2۶ پارسر پاسخ مودم و {SET_LOG} روی NULL دیرفرنس می‌کنند → HardFault → ۱ تا ۲ دقیقه آفلاینquectel.c:961, 1020, 1807, 1852; proc.c:921بررسی NULL، خروج ایمن
B3data_index در FOTA هرگز ریست نمی‌شود؛ FOTA دوم بدون ریبوت در آفست غلط می‌نویسدproc.c:16, quectel.c:3756ریست در qctl_ftp_reset؛ کران fota_data_bytes
B4دریافت UART با DMA یک‌بایتی و خواندن دوباره‌ی DR؛ حین پاک/نوشتن فلش ۱۰۰ تا ۲۳۰ بایت گم می‌شود؛ تایمر ۱۰ میلی‌ثانیه‌ای بدون critical sectionmain.c:532-569, 139-151ring buffer دایره‌ای ۲ کیلوبایت + وقفه‌ی IDLE، جداسازی فریم در تسک
B5ماشین‌های حالت MQTT و FTP بدون timeout؛ تنها فرار تایمر ۱۸۵ ثانیه‌ی «هیچ بایتی» است که هر URC آن را زنده نگه می‌دارد؛ FOTA گیرکرده ذخیره‌ی پیکربندی را در ۱۲ نقطه غیرفعال می‌کندquectel.c:2744-3618deadline هر درخواست + نردبان retry → CFUN → PWRKEY → reset
B6یکپارچگی FOTA فقط XOR16 + CRC16 هر فایل؛ بدون هدر، CRC32، بررسی اندازه/مقصد/نسخه، rollback؛ staging (۲۰۴ کیلوبایت) کوچک‌تر از اسلات اپ (۲۲۵) بدون بررسیproc.c:1004-1008هدر ایمیج + CRC32 + boot-confirm/rollback (مرحله‌ی سوم)
B7هر ERROR یا +CME ERROR ناشناخته = AT+CFUN=1,1 و ۳۰ تا ۶۰ ثانیه قطعیquectel.c:2003, 2034جفت‌کردن درخواست/پاسخ در لایه‌ی AT
B8بعد از هر ریست خروجی‌ها و رله‌ها صفر می‌شوند تا سرور دوباره SET_OUT بفرستدdefines.c:201, io.c:166تصمیم محصولی: حفظ در BKP یا مستندسازی fail-safe-off
B9درایور فلش از سه تسک بدون mutex؛ مقدار بازگشتی نادیده گرفته می‌شود → لیست سنسور نیمه‌کاره → A3 در بوت بعدیmodbus.c:165, proc.c:873mutex فلش؛ تغییرات پیکربندی از طریق تسک مالک
B10واچ‌داگ نمی‌تواند تسک گیرکرده را ببیند (refresh داخل توابع کتابخانه‌ای)؛ led_blink حلقه‌ی بی‌نهایت HAL_Delayquectel.c:2266supervisor با بیت زنده‌بودن هر تسک
B11چاپ دیباگ بلاک‌کننده (۱۷ میلی‌ثانیه به‌ازای ۱۰۰ کاراکتر) داخل callback تایمرهای embOS و ۴۰۰ نقطه‌ی دیگر؛ چرخه‌های «۳۰ ثانیه‌ای» در شبیه‌ساز ۳۸ تا ۵۵ ثانیه شدندdefines.c:593, main.c:142-210لاگر غیربلاک‌کننده با DMA و سطح لاگ
B12مسیر merge-recipe: strncpy تا ۱۷۰۰ کاراکتر در بافر ۵۰۰ بایتی؛ malloc از heap مشترک با استک اصلیmicrochipMonitoring.c:860حذف مسیر آزمایشی یا کران‌دار کردن
B13I2C بدون بازیابی باس؛ timeout ۵۰۰ و ۱۰۰۰ میلی‌ثانیه؛ HAL_Delay(20) داخل تسکsht3x.c:69-105bus recovery با کلاک SCL؛ timeout کوتاه؛ OS_TASK_Delay
B14درخواست Modbus بدون LRC/CRC؛ پاسخ اعتبارسنجی نمی‌شود؛ پنجره‌ی ۱۰۰ میلی‌ثانیه پاسخ دیر را به سنسور بعدی نسبت می‌دهد؛ منطق terminator در RTU مردهmodbus.c:29-136, proc.c:825لایه‌ی Modbus با checksum و timeout هر اسلیو و تطبیق آدرس+تابع
B15موارد کوچک: qctl_check_crc با len<4؛ bf[128] سرریز؛ تقسیم بر صفر در cm_get_chip_temp؛ df_hex_hexstring(7) غلطquectel.c:564, proc.c:712, microchipMonitoring.c:444, defines.c:485رفع نقطه‌ای

۶. یافته‌های سطح C: نگه‌داری‌پذیری

  • C1 بیلد: Release با -O0؛ آبجکت‌های stale (e2.*)؛ بدون -Wextra -Wshadow -Wvla -Wformat=2؛ سورس تحت git نیست؛ نسخه‌ی اپ و بوت‌لودر ناهمخوان.
  • C2 کد مرده و کپی: ۴ نسخه‌ی ۴۵ خطی نوشتن متادیتا (و آینه‌اش در بوت‌لودر)، ۵ نسخه از io_write_io_expanders، ۵ بلوک انتشار MQTT؛ لاگ روی فایل، SMS، cJSON آزمایشی و my_malloc کامپایل می‌شوند.
  • C3 معماری: حدود ۲۵۰ متغیر سراسری extern؛ include دایره‌ای؛ دو فرمت واگرای متادیتا؛ رمزها در سورس (defines.h:193, 237)؛ SSL موجود ولی خاموش.

یافته‌های تحلیل استاتیک (cppcheck): modbus.c:130 شرط همیشه غلط؛ defines.c:485 مقدار برگشتی غلط؛ microchipMonitoring.c:74 malloc بدون بررسی؛ سایه‌اندازی متغیرها در io.c:405 و quectel.c:578, 1812, 3837 (شامل former_percent که گزارش پیشرفت را عوض می‌کند)؛ کد غیرقابل‌دسترس بعد از NVIC_SystemReset. برای اجرا: -D__GNUC__=10 -D__arm__ لازم است وگرنه cppcheck در cmsis_compiler.h متوقف می‌شود.

۷. معماری هدف

هر ماژول مالک وضعیت خودش است، API کوچک دارد و بین تسک‌ها فقط با mailbox/queue متعلق به مصرف‌کننده صحبت می‌شود. اولویت تسک‌ها Sep > Quectel > Operator > Io به‌جای round-robin. فقط supervisor واچ‌داگ را می‌زند. هیچ I/O بلاک‌کننده‌ای در callback تایمر.

app
app/منطق کسب‌وکار، زمان‌بند انتشارio/ورودی/خروجی، رله، IOXsensors/SHT3x، پولینگ Modbus
پروتکل
proto/jsmn + جدول فرمان {topic, handler, schema}mqtt/ماشین حالت نشست، صف انتشار با id، reconnectfota/هدر ایمیج، نویسنده‌ی staging با CRC32modbus/ASCII/RTU با LRC/CRC، timeout هر اسلیو
مودم
at/framer خط، جفت درخواست/پاسخ با deadline، جدول URC، نگاشت CME/CMS
سیستم
os/جدول تسک، supervisor → IWDG، لاگر غیربلاک‌کنندهcfg/ساختار نسخه‌دار + CRC32، صفحات A/B، مشترک اپ/بوت‌لودر
BSP
bsp/uart_dmaring buffer + IDLEbsp/i2cبا bus recoverybsp/flashpage ops + mutexbsp/iwdg, clock, gpio

۸. نقشه‌ی فلش: فعلی و پیشنهادی

فعلی (اپ با -O0، ۱۱۶ کیلوبایت) boot app slot 220 KB (used 116 KB) FOTA staging 255 KB (firmware allows 200 KB) 0x08000000780088000x080404000x08080000 قرمز: صفحه‌ی متادیتا (بازنویسی در هر بوت)؛ نارنجی: لیست سنسور. بوت‌لودر واچ‌داگ و اعتبارسنجی ندارد؛ rollback وجود ندارد. پیشنهادی (اپ با -Os، حدود ۶۰ تا ۸۰ کیلوبایت) boot 32K app 160K backup 160K (last known good) staging 156K 0x08000000cfg A/B 4K+192K+352K0x08080000 سبز: cfg با CRC32 روی دو صفحه‌ی متناوب. backup امکان rollback واقعی با copy-to-run را می‌دهد (بدون نیاز به کد PIC).
شکل ۳. نقشه‌ی فلش. جابه‌جایی به نقشه‌ی جدید نیازمند بوت‌لودری است که هر دو فرمت متادیتا را بپذیرد (مسیر مهاجرت دستگاه‌های نصب‌شده).

۹. زمان‌بندی سه‌مرحله‌ای

w1w2w3w4w5w6w7w8w9w10w11w12 هفته‌ی اول: بستن مسیرهای بریک پارسرهای متادیتا/سنسور (A1 A2 A3) بوت‌لودر: اعتبارسنجی، IWDG، VTOR (A5) A4 A6 A7 A8 A9 + B2 B3 B12 بیلد -Os، هشدارها، git، baseline استاتیک سناریوهای شبیه‌ساز برای موارد بالا ماه اول: استحکام supervisor واچ‌داگ (B10) + mutex فلش (B9) ring buffer UART + IDLE (B4)، لاگر غیربلاک‌کننده (B11) لایه‌ی AT با deadline و نردبان retry (B5 B7) پارسر فرمان jsmn (B1)، I2C و Modbus (B13 B14) تست واحد هاست + CI (cppcheck، clang-tidy، بودجه‌ی استک) ماه دوم و سوم: معماری ماژول cfg با CRC32 و صفحات A/B FOTA v2: هدر، CRC32، backup، rollback، مهاجرت بازسازی ماژولی، حذف کد مرده، TLS، مستندات
شکل ۴. توالی کارها برای یک مهندس تمام‌وقت. سبز = تست/تأیید. مرحله‌ی سوم با HIL در CI (هفته‌ی ۱۲) بسته می‌شود. جمع ۱۰ تا ۱۱ هفته‌-مهندس.

۱۰. هفته‌ی اول، مورد به مورد

#تغییرمی‌بنددزمانتأیید در شبیه‌ساز
1پارسرهای متادیتا/سنسور: بررسی NULL هر توکن، کران هر کپی، خواندن به اندازه‌ی طول واقعی، terminator صریح؛ عیناً در بوت‌لودر؛ رفع ناسازگاری فیلدهای IOX/FOTAA1 A2 A3۱٫۵ روزبوت با متادیتای بریده، iox_num=1/2/4، SET_SEN با تعداد غلط، FOTA با ۲ IOX
2بوت‌لودر: اعتبارسنجی وکتور تیبل قبل از پرش، فعال کردن IWDG، اصلاح VTOR و PRIMASK، حفظ پرچم FOTA تا معتبر شدن اسلات، بررسی اندازه قبل از پاک کردنA5۱ روزکپی FOTA با ایمیج بزرگ‌تر از اسلات؛ قطع برق (pause+reset) وسط کپی
3کپی اعتبار FTP به بافر استاتیک؛ ریست data_index؛ کران fota_data_bytesA6 B3۰٫۲۵ روزدو FOTA پشت‌سرهم بدون ریبوت
4نوشتن متادیتا فقط در صورت تغییر؛ حذف نوشتن‌های زمان بوت؛ عدم ذخیره‌ی toggle سیم‌کارت؛ rate-limit برای CPIN NOT INSERTEDA4۰٫۵ روزیک ساعت بدون سیم‌کارت، شمارش سیکل‌های پاک کردن
5حذف VLA؛ اندازه‌ی استاتیک بافرها؛ کران کپی‌های payload_buffer/fota_buff/پاسخ Modbus؛ بررسی NULL در ۶ پارسر URC و {SET_LOG}A7 A9 B2 B12۱ روز۳۲ سنسور؛ payload بدفرم روی هر topic؛ نویز RS-485
6پاک کردن صفحه‌به‌صفحه با refresh واچ‌داگ؛ غیرفعال کردن آپدیت بوت‌لودر از داخل اپA8۰٫۲۵ روزFOTA کامل با IWDG فعال
7بیلد: -Os، -Wextra -Wshadow -Wvla -Wformat=2 -Wstringop-overflow، لینک embOS نسخه‌ی Release، پاک کردن آبجکت‌های stale، git، baseline cppcheckC1۰٫۵ روزبوت و ۳۵ سناریوی موجود با بیلد جدید
8سناریوهای شبیه‌ساز برای موارد ۱ تا ۶ در cm_sim/testsتأیید۱ روز

۱۱. تست و CI

تست واحد روی هاست

پارسرها بعد از جداسازی توابع خالص‌اند: cfg، proto، framer خط AT، framing Modbus، هدر/CRC FOTA، دیکد hex در io_set_out. با Unity/Ceedling یا CMake+ctest و mock کوچک HAL_*. قاعده: هر تابعی که بایت سرور یا مودم را لمس می‌کند تست‌های خالی/بریده/بزرگ/تعداد غلط دارد.

گیت‌های CI

  • کامپایل با -Werror و هشدارهای کامل؛ cppcheck با --error-exitcode=1؛ clang-tidy bugprone-*,cert-*
  • اسکریپتی که اگر ورودی .su از بودجه‌ی تسک بیشتر شد یا dynamic داشت بیلد را شکست می‌دهد
  • لینک با کتابخانه‌ی Release embOS؛ بررسی اندازه‌ی ایمیج نسبت به اسلات

رگرسیون HIL با شبیه‌ساز cm_sim

سناریوپوشش
بوت با هر گونه‌ی متادیتا (پیش‌فرض، ۱/۲/۳/۴ IOX، recipe بلند، ۳۲ سنسور، صفحه‌ی بریده)A1 A2 A3 A7
قطع MQTT، خروج سیم‌کارت، بدون سیم‌کارت یک ساعت با شمارش سیکل پاک کردن فلشA4 B5 B7
FOTA: مسیر موفق، chunk خراب، توقف FTP، قطع برق در هر فازA5 A8 B3 B6
payload بزرگ و بدفرم روی هر topicB1 B2
نویز RS-485 و پاسخ دیرA9 B14
soak ۲۴ ساعته: pace، شمارنده‌ی واچ‌داگ، high-water حافظه، دبی UART5B10 B11

شبیه‌ساز الان ELF بدون تغییر را با سرعت واقعی اجرا می‌کند، مودم به بروکر واقعی وصل است، RS-485 و SHT30 و IWDG مدل شده‌اند و ۳۵ سناریو به‌علاوه‌ی FOTA سرتاسری آماده است.

۱۲. معیارهای پذیرش هر مرحله

پایان هفته‌ی اولهیچ ورودی سرور یا رکورد فلشی نمی‌تواند HardFault بدهد؛ بوت‌لودر با اپ نامعتبر ریست می‌کند نه توقف؛ بدون سیم‌کارت هیچ نوشتن فلشی رخ نمی‌دهد؛ ۳۵ سناریوی شبیه‌ساز و FOTA با ۲ IOX سبز.
پایان ماه اولهیچ بایتی حین نوشتن فلش گم نمی‌شود؛ هر درخواست مودم deadline دارد؛ تسک گیرکرده در ۳ ثانیه با واچ‌داگ ریست می‌شود و علتش لاگ می‌شود؛ CI روی هر commit اجرا می‌شود.
پایان ماه سومFOTA با تصویر خراب یا قطع برق در هر فاز به نسخه‌ی قبلی برمی‌گردد؛ پیکربندی با قطع برق خراب نمی‌شود (A/B + CRC)؛ soak ۲۴ ساعته بدون ریست ناخواسته؛ رمزها خارج از سورس و TLS فعال.

تصمیم محصولی باز: رفتار خروجی‌ها و رله‌ها بعد از ریست (B8): حفظ آخرین وضعیت یا fail-safe-off. این تصمیم باید قبل از مرحله‌ی دوم گرفته شود.