کتابخانه GSM
درایور غیرمسدودکننده و کاملاً ماژولار برای مودمهای سلولارSIM800 / SIM900 / Quectel M08 / M08R
این سایت، مستندات یکپارچه و کامل کتابخانه است: از معماری لایهای و ماشین حالت گرفته تا جزئیات هر فایل، هر تایمر و هر ترفند میدانی. ساختهشده برای سیستمهای امبدد جدی — بدون heap، بدون STL، بدون استثنا، با پشتیبانی کامل از پیامک فارسی.
static port::Stm32Uart uart(&huart2);static port::Stm32Clock clk;static port::Stm32PowerControl pwr(GPIOB, GPIO_PIN_12);static Modem modem(uart, clk, kSim800, &pwr);modem.set_on_sms(...);modem.begin();while (true) modem.tick(); // همین!اصول طراحی — چرا این کتابخانه متفاوت است؟
کتابخانه بر اساس اصول مهندسی سیستمهای امبدد صنعتی طراحی شده است؛ هر تصمیم معماری به یک محدودیت واقعی تولید پاسخ میدهد: حافظه کم، حلقه اصلی حساس به تأخیر، و نیاز به کارکرد ۲۴/۷ بدون دخالت انسان.
Zero Dynamic Allocation — بدون heap
هیچ جایی از کد از malloc، new یا کانتینرهای پویا استفاده نمیکند. تمام بافرها (بافر حلقوی RX، خطوط AT، متن پیامک، صف کارها) در زمان کامپایل و بهصورت استاتیک رزرو میشوند. نتیجه: هیچ تکهتکه شدن حافظه، هیچ شکست تخصیص در زمان اجرا، و رفتار قطعی و قابل پیشبینی.
بدون وابستگی به STL
از std::vector، std::string و std::function خبری نیست. جایگزینها ابزارهای اختصاصی همین کتابخانهاند: StaticString با اندازه ثابت، RingBuffer ایستا و Delegate سبکوزن. کامپایل روی هر کامپایلر C++17 بدون نیاز به کتابخانه استاندارد اجرایی.
Non-blocking Tick Architecture
کل کتابخانه با فراخوانی ساده modem.tick() در هر چرخه زنده میماند. هیچ sleep، هیچ wait فعال و هیچ بلاکشدنی وجود ندارد — حلقه اصلی یا FreeRTOS هرگز متوقف نمیشود. حتی ارسال پیامک که چند مرحله AT دارد، همیشه با ماشین گامبهگام اداره میشود.
No Exceptions / No RTTI
کاملاً سازگار با -fno-exceptions -fno-rtti. خطاها با کدهای عددی (kErrSmsTooLong، kErrJobStalled و کدهای CME/CMS مودم) و کالبکها گزارش میشوند؛ نه با پرتاب استثنا. این یعنی سربار باینری حداقل و رفتار قابل پیشبینی در محیطهای حساس.
پیامک فارسی بهصورت پیشفرض سیستم
پیشفرض کل سیستم روی UCS2 در Text Mode قفل شده است: ارسال، خواندن، اسکن و پاسخ USSD همه با رمزگذاری خودکار UTF-8↔UCS2 انجام میشود. حتی شماره گیرنده به هگز UCS2 تبدیل میشود چون مودمها در حالت UCS2 شماره ASCII را قبول نمیکنند — درسی که از لاگهای میدانی آموخته شد.
معماری لایهای با قاعده طلایی
هیچ لایهای به لایه بالاتر وابسته نیست و فقط AtEngine با UART حرف میزند. اضافه کردن مودم جدید = تعریف یک DeviceTraits؛ اضافه کردن سکوی جدید = پیادهسازی ۳ اینترفیس HAL. هسته هرگز برای توسعه دست نمیخورد.
کتابخانه در یک نگاه
اعداد کلیدی که مقیاس و طراحی کتابخانه را نشان میدهند.
ماژولهای پشتیبانیشده
SIM800/SIM900 با پروفایل kSim800، Quectel M08/M66 با kQuectelM08 و نسخه اصلاحشده M08R با پروفایل اختصاصی.
بیلد چندسکویی
CMake برای میزبان/STM32 و library.json برای PlatformIO (ststm32، atmelavr، espressif32، native).
تست دود میزبان
host_smoke.cpp همه چیز را بدون سختافزار میآزماید: کدک، بوت، USSD، صف، تماس، بازیابی SIM و واچداگ.
معماری لایهای — از سختافزار تا اپلیکیشن
کتابخانه در پنج لایه L0 تا L4 سازمان یافته است. روی هر لایه کلیک یا هاور کنید تا توضیح و جریان داده همان لایه را ببینید. قاعده طلایی: هیچ لایهای به لایه بالاتر وابسته نیست و فقط AtEngine با UART حرف میزند.
مدیریتگر مودم و سرویسها
هوش کتابخانه اینجاست: ماشین حالت اصلی + زیرحالتها داخل سرویسها، صف کار با اولویت و retry، گیت تماس و نگهبانی صنعتی.
پورت جدید = سه اینترفیس
برای انتقال به هر سختافزار تازه فقط باید IUart، IClock و (اختیاری) IPowerControl را پیادهسازی کنید. پورت STM32 از DMA با حالت ReceiveToIdle استفاده میکند، پورت Arduino روی هر Stream و پورت Host برای تست دسکتاپ طراحی شده است.
ماژول جدید = یک DeviceTraits
هر مودم با یک ساختار traits توصیف میشود: فرمان قطع تماس، پارامتر CSMP، مخزن پیامک، تایماوت بوت و رفتارهای خاص (مثل اطمینان به +CLCC در M08R). اضافه کردن M66 یا SIM7600 یعنی فقط تعریف یک constexpr جدید — دست زدن به هسته لازم نیست.
ماشین حالت اصلی — قلب کتابخانه
مودم همیشه دقیقاً در یکی از ۱۱ حالت زیر است. زیرحالتها (مثل مراحل ارسال پیامک) داخل سرویسها زندگی میکنند و از این ماشین جدا هستند. نمودار زیر مسیر کامل از روشنشدن تا آمادهسازی و کار را نشان میدهد.
| حالت | نام رشتهای | معنی | خروج |
|---|---|---|---|
| Off | "off" | مودم شروع نشده یا خودش را خاموش اعلام کرده است. هیچ فعالیتی وجود ندارد. | فراخوانی begin() |
| Booting | "booting" | ارسال مکرر پروب «AT» تا جواب دادن ماژول؛ در صورت بیپاسخی، عبور به PowerPulsing. | دریافت OK از پروب |
| PowerPulsing | "pwrkey-pulsing" | پالس زدن پایه PWRKEY (حداکثر ۲ بار) تا ماژول سختافزاری روشن شود. | بازگشت به Booting یا Fault |
| WaitingRegistration | "waiting-reg" | هر ۲ ثانیه استعلام AT+CREG? یا گوشدادن به URC شبکه؛ تا اتصال به شبکه صبر میکند. | رجیستر شدن (1/5) یا تایماوت ۴۵s |
| Configuring | "configuring" | اجرای جدول بوت: ATE0، CMEE، CLIP، CNMI، CMGF، CSCS، CSMP، CPMS. | پایان جدول |
| Ready | "ready" | بیکار؛ فقط اینجا housekeeping (CSQ، Sweep، اسکن پیامک، پاکسازی) اجرا میشود. | آمدن Job یا تماس ورودی |
| Working | "working" | یک Job از صف در جریان است (ارسال پیامک، USSD، استعلام وضعیت، تماس خروجی و ...). | پایان Job |
| Ringing | "ringing" | تماس ورودی در انتظار پاسخ است؛ اگر ۲۰ ثانیه جواب داده نشود خودکار رد میشود. | answer() / hang_up() / NO CARRIER |
| InCall | "in-call" | تماس صوتی برقرار است؛ جابهای غیرتماسی در صف نگه داشته میشوند (گیت تماس). | قطع تماس از هر طرف |
| SimMissing | "sim-missing" | سیمکارت تشخیص داده نشده؛ پروب CPIN و چرخه CFUN=0/1 برای بازیابی اجرا میشود. | بازگشت سیمکارت (+CPIN: READY) |
| Fault | "fault" | تسلیم؛ مثلا تایماوت رجیستر یا شکست پروبها. برای خروج، reset() یا power_cycle() لازم است. | reset() / ریست خودکار ۹۰ ثانیهای |
راهاندازی هوشمند، رجیستر شبکه و بازیابی سیمکارت
فرآیند بوت هرگز به بنرهای بوت مودم (RDY، Call Ready، SMS Ready) اعتماد نمیکند؛ خودش با پروب AT مودم را میآزماید، در صورت نیاز PWRKEY را پالس میزند، صبر میکند شبکه رجیستر شود و فقط بعد از آن پیکربندی را آغاز میکند.
۱) بوت و مکانیزم PWRKEY
- ۱ مودم تا ۱۰ بار پروب
ATمیفرستد و پاسخ میگیرد. - ۲ اگر ماژول جواب ندهد (مثلاً خاموش باشد) و
IPowerControlمتصل باشد، وارد حالت PowerPulsing میشود و پایه PWRKEY را پالس میزند. - ۳ دوباره پروب ارسال میشود؛ در صورت بیپاسخی یک پالس دیگر و سپس وضعیت Fault.
- ۴ اگر
powerبرابر nullptr باشد، همه این مراحل نرمافزاری و بدون خطا هستند.
۲) انتظار رجیستر شبکه
- ۱ هر ۲ ثانیه دستور
AT+CREG?ارسال یا به URC شبکه گوش داده میشود. - ۲ کد 1 یعنی شبکه خانگی و کد 5 یعنی رومینگ — هر دو موفقیتاند.
- ۳ اگر تا ۴۵ ثانیه رجیستر نشد، مودم وارد Fault میشود و لاگ خطای شبکه ثبت میکند.
- ۴ فقط بعد از رجیستر شدن، مرحله پیکربندی آغاز میشود — نه یک لحظه زودتر.
| فرمان | چرا؟ | وضعیت شکست |
|---|---|---|
| ATE0 | خاموش کردن اکو — پارسر حتی با اکو روشن هم مقاوم است | Fault |
| AT+CMEE=2 | گزارش خطاهای CME/CMS با متن راهنما (اختیاری) | ادامه میدهد |
| AT+CLIP=1 | نمایش شماره تماسگیرنده (اختیاری) | ادامه میدهد |
| AT+CNMI=2,1,0,0,0 | اعلان پیامک جدید با +CMTI (اختیاری) | ادامه میدهد |
| AT+CMGF=1 | حالت متنی پیامک (اجباری — شکست یعنی Fault) | Fault |
| AT+CSCS="UCS2" | قفل charset روی UCS2 برای پیامک فارسی (اجباری) | Fault |
| AT+CSMP=… | پارامترهای پیامک از DeviceTraits هر ماژول (اجباری) | Fault |
| AT+CPMS=… | انتخاب مخزن پیامک SM یا ME از DeviceTraits (اجباری) | Fault |
۳) تشخیص زنده سیمکارت
روتر URC بهصورت پویا وضعیت سیمکارت را پایش میکند. با دیدن +CPIN: NOT INSERTED مودم وارد حالت SimMissing میشود، کالبک on_sim با SimState::Removed صدا زده میشود و فرآیندهای پسزمینه متوقف میشوند. با برگشتن سیمکارت (+CPIN: READY) بهصورت خودکار به WaitingRegistration برمیگردد و چرخه را کامل میکند.
۴) بازیابی صنعتی با چرخه CFUN
در Quectel و SIM800 بعد از جا زدن مجدد سیمکارت، ماژول تا ریست لایه بیسباند خطایSIM not inserted میدهد. کتابخانه هر ۱۰ ثانیه پروب CPIN میزند و اگر سیم نیامده باشد، هر ۲۵ ثانیه چرخه AT+CFUN=0 سپسAT+CFUN=1 را اجرا میکند تا ماژول مجبور به اسکن فیزیکی پینهای سیمکارت شود.
یک تیک چگونه میگذرد؟ — حلقه حیاتی ۹ مرحلهای
هر فراخوانی Modem::tick() یک مسیر دقیق و ترتیبدار را طی میکند. این ترتیب تضمین میکند سرپرستی تماس همیشه بهموقع باشد، صف هرگز بند نیاید و پروبهای سلامت فقط در بیکاری کامل اجرا شوند.
چرا ترتیب مهم است؟
- سرپرستی تماس قبل از FSM و pump_services صدا زده میشود تا تایمرهای قطع و پرسوجوی +CLCC همیشه بهموقع برسند، حتی وقتی صف شلوغ است.
- پروبهای سلامت watchdog فقط وقتی اجرا میشوند که همهچیز بیکار است — هیچ تداخلی با تماس یا Job جاری رخ نمیدهد.
- پاکسازی Jobهای کهنه قبل از FSM انجام میشود تا صف همیشه پیش از شروع کار جدید تمیز باشد.
- خانهداری (CSQ، اسکن پیامک و …) فقط در حالت Ready و وقتی گیت تماس باز است اجرا میشود.
صف کارها — ستون فقرات اجرای عملیات
هر عملیات (ارسال پیامک، تماس، USSD، استعلام وضعیت) یک Job است که در صف FIFO ایستا با ظرفیت ثابت GSM_QUEUE_DEPTH (پیشفرض ۶) قرار میگیرد. هیچ تخصیص حافظهای در صف رخ نمیدهد و هیچ Jobای بیجواب نمیماند.
اولویت Urgent
کارهای اضطراری مثل hang_up() و answer() از جلو صف وارد میشوند تا حتی وقتی صف شلوغ است هم فوراً اجرا شوند. بقیه کارها از انتهای صف.
تلاش مجدد هوشمند
خطای موقت ماژول یا شبکه باعث retry تا max_attempts میشود (ارسال پیامک پیشفرض ۲ بار). اما خطاهای سیاستی مثل kErrSmsTooLong و kErrJobStalled هرگز retry نمیشوند.
ضد انباشت و گیرکردن
Jobهای معلق بیش از ۴۵ ثانیه با purge_stale() لغو میشوند. اگر سرویسی از شروع Job امتناع کند، بعد از ۳ تلاش با kErrJobStalled تمام میشود — صف هرگز نمیایستد.
انواع Job (JobKind)
هر Job شامل kind، وضعیت (Queued/Running/Succeeded/Failed/Cancelled)، بودجه تلاش (attempts)، زمان ورود، پرچم deferred و نتیجه (کد خطا + خروجی مثل شماره مرجع پیامک یا پاسخ USSD) است.
کنترل جامع صف
modem.pause_queue(); // توقف پردازش صفmodem.resume_queue(); // ادامه از همانجاmodem.stop_all(); // لغو جاری با ESC/ATH + پاکسازی صفmodem.cancel(id); // لغو تکی با فراخوانی کالبک// مشاهده:modem.queue_size();modem.queued(i); // i-امین Job در انتظارmodem.current_job(); // Job در حال اجراگیت تماس — حل مشکل «مرگ صف»
- ✔ جابهای تماس (Dial/Answer/Hangup) همیشه از گیت عبور میکنند.
- ✔ تماس ورودی وسط ارسال پیامک →
preempt_for_call()جاب را با ESC بهصورت امن لغو وdeferredمیکند؛ بعد از تماس بدون مصرف بودجه retry ادامه مییابد. - ✔ دقیقاً همانجایی است که M08R قبلاً هنگ میکرد و «پیام خالی» میفرستاد.
مسیر یک Job از enqueue تا callback
اگر start سه بار شکست بخورد، Job با kErrJobStalled تمام میشود؛ اگر finish با خطای retry-پذیر روبهرو شود، Job دوباره به صف برمیگردد. پس از نهایی شدن، watchdog_.note_operation() بودجه ریست دوم را تغذیه میکند.
هسته AT — موتور تکفرمانی غیرهمزمان
AtEngine زیربنای همه سرویسهاست: هر لحظه فقط یک فرمان در پرواز (in-flight) دارد، پاسخها را خطبهخط از LineReader میگیرد، نتیجه نهایی را دستهبندی میکند و از طریق IAtObserver به سرویس فراخوان اطلاع میدهد. URCها قبل از رسیدن به observer توسط UrcRouter مصرف میشوند.
خطلوله پردازش هر بایت
دستهبندی پاسخهای نهایی
| خط پاسخ | AtStatus | کد | معنی |
|---|---|---|---|
| OK | Ok | -1 | موفقیت |
| ERROR | Error | -1 | خطای عمومی |
| +CME ERROR: n | CmeError | n | خطای تجهیز (با متن راهنما لاگ میشود) |
| +CMS ERROR: n | CmsError | n | خطای پیامک (با متن راهنما لاگ میشود) |
| NO CARRIER | NoCarrier | -1 | اتصال حامل قطع شد |
| BUSY | Busy | -1 | طرف مقابل اشغال بود |
| NO ANSWER | NoAnswer | -1 | جواب نداد |
| NO DIALTONE | NoDialtone | -1 | تن کنون نداشت |
نکات ظریف موتور
- Guard Time: حداقل ۳۰ms بین دو فرمان پشتسرهم از تداخل پاسخ مودمهای کند جلوگیری میکند — یک execute ممکن است بهظاهر بیدلیل false برگرداند!
- پرامپت «>»: فقط وقتی بافر خط خالی باشد Prompt تولید میشود؛ «>» وسط متن پیامک پرامپت تلقی نمیشود.
- abort ایمن: اگر در انتظار پرامپت باشیم، ابتدا ESC (0x1B) ارسال میشود تا مودم از data-entry بیرون بیاید.
- سرریز خط: خطوط بلندتر از GSM_LINE_MAX (۵۱۲) با Token::Overflow بیسروصدا دور ریخته میشوند.
سرویس پیامک — فارسی، UCS2، با محافظ صنعتی
ارسال، خواندن، اسکن و حذف پیامک در حالت متنی (CMGF=1) با رمزگذاری خودکار UCS2. سرویس تنظیمات جلسه را کش میکند تا فرمانهای تکراری نفرستد و متنهای بلند را قبل از هر فرمان AT رد میکند.
جریان ارسال پیامک (Send Flow)
گامهایی که به لطف کش جلسه (SessionState) قبلاً اعمال شدهاند، بدون ترافیک AT رد میشوند؛ یعنی پیامک دوم فقط AT+CMGS میفرستد. هر خطا SessionState را بیاعتبار میکند تا دفعه بعد کانفیگ از نو اعمال شود.
گارد سختگیرانه طول (v0.4)
- طول با تعداد نویسههای UTF-8 سنجیده میشود، نه بایت — «سلام» ۴ نویسه است، نه ۸ بایت.
- پیامک UCS2 تکبخشی حداکثر ۷۰ نویسه است؛ پیشفرض کتابخانه ۶۰ نویسه برای حاشیه امن.
- متن بلندتر اصلاً ارسال نمیشود و بلافاصله با
kErrSmsTooLong = -100رد میگردد (تلاش مجدد نمیشود). - دو لایه دفاع: رد قبل از ورود به صف در
Modem::send_sms+ رد هنگام اجرا درSmsService::start_send. - در زمان اجرا قابل تنظیم است:
modem.sms().set_max_chars(45)و ۰ یعنی غیرفعال.
چرا این گارد وجود دارد؟ ماژولهای M08R با payload بزرگ هنگ میکنند یا پیامک خالی میفرستند. این محافظ از ریشه جلوی این خرابی میدانی را میگیرد.
API کامل پیامک
// ارسال با کالبک نتیجهmodem.send_sms("0912...", "سلام دنیا", [](void*, const Job& j) { if (j.state == JobState::Succeeded) printf("ref=%d\n", j.reference); });// خواندن / اسکن / حذفmodem.read_sms(index, cb); // AT+CMGRmodem.scan_sms(cb); // AT+CMGL="REC UNREAD"modem.delete_read_sms(cb); // AT+CMGD=1,1// شمارش نویسههاuint16_t n = SmsService::count_chars("سلام"); // 4// تنظیم سقف در زمان اجراmodem.sms().set_max_chars(45);پیام دریافتی با کالبک set_on_sms تحویل میشود؛ شماره فرستنده و متن بهصورت خودکار از UCS2-hex به UTF-8 رمزگشایی میشوند (با پشتیبانی جفت سورگیت UTF-16).
سرویس تماس — سرپرستی مستقل از صف
نسخه 0.4 مدیریت تماس را از پایه بازنویسی کرد: سه مشکل میدانی (مرگ صف با ورود تماس، تشخیص نشدن رد تماس، نبود تایماوت قطع) با یک سرپرستی تماس جدید حل شدهاند.
قطع خودکار ۲۰ ثانیهای
تماس خروجی که در dial_timeout_ms وصل نشود و زنگ ورودی که درring_timeout_ms جواب داده نشود، خودکار قطع/رد میشود (پیشفرض ۲۰s، قابل تنظیم در زمان اجرا و کامپایل).
تور ایمنی +CLCC
هر ۲ ثانیه یک AT+CLCC ارسال میشود. اگر فهرست تماسها ۲ بار پیاپی خالی باشد و تماس قبلاً دیده شده باشد، «مرده» اعلام و خط فعالانه آزاد میشود — حتی اگر مودم هیچ URC پایان تماسی نفرستد (رفتار شناختهشده M08R).
قطع دو مرحلهای
فرمان اختصاصی هر دستگاه (ATH در SIM800، AT+CHUP در Quectel) اول امتحان میشود؛ اگر جواب ندهد، یک بار ATH جایگزین اجرا میگردد (HangupFallback) تا حلقه بیپایان قطع رخ ندهد.
ماشین حالت تماس
هر گذار با CallEndReason گزارش میشود
نکته کلیدی: monitor() در هر تیک از Modem::tick() صدا زده میشود، حتی وقتی هیچ جاب تماسی در صف نیست — ردیابی تماس هرگز به وضعیت صف وابسته نیست.
سیاست تماس (CallPolicy)
struct CallPolicy { uint32_t dial_timeout_ms = 20000; // قطع خودکار MO uint32_t ring_timeout_ms = 20000; // رد خودکار MT uint32_t clcc_poll_ms = 2000; // فاصله +CLCC bool auto_hangup = true; // کلید اصلی};// تنظیم زنده:modem.call_policy().dial_timeout_ms = 30000;modem.call_policy().ring_timeout_ms = 15000;// گزارش دلیل پایان:modem.set_on_call_ended(...);| مقدار | معنی |
|---|---|
| LocalHangup | خودمان قطع کردیم |
| RemoteHangup | طرف مقابل قطع کرد |
| Busy | اشغال بود |
| NoAnswer | جواب نداد |
| NoCarrier | بدون حامل — اتصال قطع شد |
| NoDialtone | تن کنون نداشت |
| Timeout | تایمر قطع خودکار فعال شد |
| Error | خطای دیگر |
سرویس USSD — استعلام شارژ و کدهای دلخواه
سرویس اختصاصی USSD با دستور استاندارد AT+CUSD: تشخیص خودکار اپراتور ایرانی، استعلام شارژ با یک متد، و رمزگشایی خودکار پاسخ (حتی اگر شبکه پاسخ را در UCS2-hex بفرستد).
تشخیص خودکار اپراتور از AT+COPS?
modem.check_balance(); // کد هوشمند بر اساس اپراتور تشخیصیmodem.ussd("*140#"); // یا هر کد دلخواهmodem.set_on_ussd(...); // پاسخ شبکه — خودکار decode شده به UTF-8modem.ussd_service().send_reply("*1#"); // نشست تعاملیmodem.ussd_service().end_session(); // بستن جلسه با AT+CUSD=2جریان یک استعلام
جزئیات فنی
- ✔ پاسخ
+CUSDبا تحلیل فیلد DCS و در صورت نیاز «بو کردن» محتوا decode میشود. - ✔ تایماوت شبکه ۱۰ ثانیه؛ نتیجه حتی در صورت Timeout هم گزارش میشود.
- ✔ رمزنگاری UCS2/GSM7 بهصورت خودکار انتخاب میشود؛ اگر GSM7 انتخاب شده باشد ولی متن غیر-ASCII باشد، با هشدار به UCS2 ارتقا مییابد.
- ✔ نشستهای USSD تعاملی پشتیبانی میشوند: reply و بستن جلسه.
سرویس وضعیت و خانهداری — مودم همیشه دیدهبان
در حالت Ready، ماشین حالت بیکار نمینشیند؛ طبق HousekeepingConfig بهصورت دورهای سیگنال، رجیستر، سیمکارت، مخزن پیامک و اپراتور را استعلام میکند و همه چیز را در ModemStatus زنده نگه میدارد.
برنامه زمانی خانهداری
خانهداری فقط در Ready و فقط وقتی گیت تماس باز است اجرا میشود؛ هیچوقت با تماس یا Job جاری تداخل پیدا نمیکند.
ModemStatus زنده — همه چیز یکجا
const ModemStatus& st = modem.status();printf("rssi=%d dBm bars=%d op=%s\n", st.rssi_dbm, st.signal_bars(), st.operator_name.c_str());نگهبانی صنعتی — دو ریستکننده + بررسی سلامت
ماژول سلولار در کار طولانیمدت (چند روز تا چند ماه) به مرور کند میشود یا هنگ میکند. بهجای انتظار برای خرابی، دو ریستکننده مستقل بهصورت دورهای ماژول را کامل ریست و سپس سلامت آن را بررسی میکنند — پایداری ۲۴/۷ بدون دخالت انسان.
ریستکننده اول — بودجه زمانی
3600000msهر ۱ ساعت (قابل تنظیم) یک ریست کامل ماژول انجام میشود. با GSM_PERIODIC_RESET_MS=0 خاموش میشود. در زمان اجرا هم با wd.policy().periodic_ms قابل تغییر است.
ریستکننده دوم — بودجه عملیات
50 opsبعد از هر ۵۰ عملیات کامل (هر جاب تمامشده: پیامک، تماس، USSD، استعلام) ریست کامل انجام میشود. هر کدام زودتر فعال شود، ریست اجرا و هر دو بودجه از نو شروع میشوند.
فرصت امن و حفظ صف
- ریست فقط در فرصت امن اجرا میشود: بدون تماس فعال، بدون فرمان AT در پرواز، بدون Job جاری.
- اگر تا
grace_ms(پیشفرض ۱۰ ثانیه) فرصت پیدا نشود، ریست اجباری میشود. - جابهای صفکشیده در طول ریست نگه داشته میشوند و بعد از بالا آمدن مودم ادامه پیدا میکنند —
full_restart(keep_jobs=true). - سیاست سختافزاری: اگر
IPowerControlموجود باشد ریست با چرخه PWRKEY انجام میشود، وگرنه نرمافزاری.
بررسی سلامت چهارمرحلهای بعد از هر ریست
پاسخهای +CPIN و +CREG توسط UrcRouter بلعیده میشوند و وضعیت زنده ModemStatus را بهروز میکنند؛ برای همین verdict سلامت از همان وضعیت زنده خوانده میشود. نتیجه با کالبک set_on_health گزارش میشود.
modem.set_on_health( [](void*, const HealthReport& r) { if (!r.ok()) { /* اقدام اضطراری */ } });const HealthReport& h = modem.maintenance().health();// h.state: Unknown / Checking / Healthy / Unhealthy// h.at_ok, h.sim_ok, h.net_ok, h.rssi_dbm, h.resetsتفاوت با واچداگهای قدیمی
| قابلیت | واچداگ صنعتی قبلی | ریستکنندههای v0.4 |
|---|---|---|
| تایماوتهای متوالی AT | ✔ دارد | ✔ حفظ شده |
| گیرکردن در Fault/SimMissing | ✔ دارد | ✔ حفظ شده |
| ریست دورهای زمانی | ✘ | ✔ پیشفرض ۱ ساعت |
| ریست بعد از N عملیات | ✘ | ✔ پیشفرض ۵۰ |
| بررسی سلامت بعد از ریست | ✘ | ✔ چهار پروب |
| حفظ صف در حین ریست | ✘ صف پاک میشد | ✔ keep_jobs=true |
کدک و ابزارها — بلوکهای سازنده صفر-تخصیص
لایه L1 مجموعهای از ابزارهای خالص و مستقل از سختافزار است: تبدیلهای رمزگذاری برای فارسی، نرمالسازی شماره، رشته ثابت، بافر حلقوی، کالبک بدون heap و سامانه لاگ سبک.
codec/ucs2 — رمزگذاری فارسی
تبدیلهای UTF-8 ↔ hex-UCS2 با پشتیبانی کامل جفت سورگیت UTF-16، تشخیص looks_like_ucs2_hex و شمارش واحدها. پایه رمزنگاری پیامک، USSD و شمارهها.
codec/phone_number — E.164
نرمالسازی شماره به E.164 با کد کشور پیشفرض +98: فرم بینالمللی، داخلی ۰۹…، و بدون صفر همگی به یک شکل استاندارد میرسند.
util/static_string — رشته ثابت
StaticString<N> با ابزارهای پارس AT: parse_int، csv_field، str_starts_with و ... بدون هیچ تخصیصی. هسته پارسینگ خطوط مودم.
util/ring_buffer — بافر حلقوی
بافر حلقوی بایتی ایستا با پرچم overflow؛ مناسب بافر UART در حالت وقفه. RX پیشفرض ۱۰۲۴ بایت و قابل کوچکسازی.
util/delegate — کالبک بدون heap
Delegate سبکوزن جایگزین std::function با bind ایمن به متدهای عضو: Delegate<T>(cb_fn, ctx). همه کالبکهای کتابخانه با همین نوع کار میکنند.
log — لاگ سبکوزن
سطحهای Off..Trace، کانالهای تگدار، sinkهای قابل تعویض (stdio / writer / null)، رنگ ANSI قابل خاموشی در runtime، timestamp، bytes() برای دیدن CR/LF/CtrlZ و hexdump. با GSM_LOG_LEVEL_MAX کامپایل حذف میشود.
log::Registry::set_color(true); // رنگهای ANSIlog::Registry::set_timestamps(true); // برچسب [ms]log::Registry::set_global_level(log::Level::Debug);لایه انتزاع سختافزار و پورتها
تنها نقطه وابستگی به پلتفرم، سه اینترفیس L0 است. پورت جدید یعنی پیادهسازی همین سه اینترفیس؛ هیچ چیز از هسته تغییر نمیکند. شمارنده میلیثانیه با hal::elapsed بهصورت مقاوم به سرریز مقایسه میشود.
پورت STM32
- ✔UART مبتنی بر DMA با حالت ReceiveToIdle — بدون از دست دادن بایت
- ✔زمانسنج HAL_GetTick با تابع elapsed مقاوم به سرریز
- ✔Stm32PowerControl برای پالس PWRKEY و RESET مودم
- ✔همراه با examples/stm32_main.cpp و user_code/cli.cpp
پورت Arduino
- ✔پیادهسازی IUart روی هر Stream سریال (Serial، Serial1، ...)
- ✔زمانسنج millis() آردوینو
- ✔همان API دقیقاً — فقط دو خط setup فرق میکند
- ✔مثال arduino_basic.ino: دریافت و ارسال پیامک فارسی
پورت Host (میزبان)
- ✔شبیهساز UART/Clock/Power با بافر تزریق/بازرسی
- ✔پایه تستهای قطعی host_smoke.cpp بدون سختافزار
- ✔قابل استفاده در CI و توسعه سریع منطق
اینترفیسهای HAL
class IUart { public: virtual uint16_t read(uint8_t* buf, uint16_t len) = 0; virtual uint16_t write(const uint8_t* data, uint16_t len) = 0;};class IClock { public: virtual uint32_t millis() = 0; };class IPowerControl { public: virtual void power_key(bool press) = 0; // پالس PWRKEY};ماجرای elapsed ضد سرریز
شمارنده millis بعد از ~۴۹ روز سرریز میکند؛ مقایسه ساده now - start > timeoutآن لحظه خراب میشود. تابع hal::elapsed(now, start, dur) با ریاضیات حسابی بدون علامت همیشه درست کار میکند و همه تایمرهای کتابخانه (تایماوتهای AT، تایمر تماس، خانهداری، واچداگ) از همین مسیر عبور میکنند.
bool elapsed(uint32_t now, uint32_t since, uint32_t dur) { return (uint32_t)(now - since) >= dur; // درست حتی بعد از سرریز}کنسول تعاملی gsm::cli::Console
در نسخه 0.4 کنسول از اپلیکیشن به هسته کتابخانه منتقل شد — حالا هر پروژهای (STM32، Arduino، تست میزبان) با چند خط، همان کنسول صنعتی را دارد. بدون heap، بدون STL — مانند بقیه کتابخانه.
راهاندازی سریع
static gsm::cli::Console console(modem, clock);console.set_output(TextSink{&uart_write, nullptr});console.init(); // ثبت کالبکها + رسم منو// در حلقه اصلی:modem.tick();console.tick();while (uart.readable()) console.feed(uart.read_byte());پل وب (Web Bridge)
کنسول میتواند همزمان رویدادها و لاگها را به یک پنل وب آینه کند:set_web_output برای خروجی خطبهخط، exec_web برای اجرای دستور از پنل، وstatus_line برای وضعیت فشرده CSV با ۱۵ فیلد (fsm|act|step|ready|sim|net|op|rssi|bars|…).
رویدادهای خودکار
کنسول در init() این کالبکها را روی مودم نصب و رنگی چاپ میکند:
کلیدها و دستورات کنسول
دستورات متنی
پروفایل ماژولها (DeviceTraits)
هر مودم با یک ساختار constexpr توصیف میشود: فرمان قطع تماس، پارامترهای پیامک، مخزن پیامک، تایماوتها و رفتارهای خاص. اضافه کردن ماژول جدید یعنی فقط تعریف یک پروفایل تازه — هسته دستنخورده میماند.
| فیلد | معنی | kSim800 | kQuectelM08 | kQuectelM08R |
|---|---|---|---|---|
| ucs2_recipient_required | شماره گیرنده هم باید UCS2-hex باشد | ✔ | ✔ | ✔ |
| csmp_ucs2 | پارامتر CSMP برای پیامک UCS2 | AT+CSMP=17,71,0,25 | AT+CSMP=17,167,0,8 | AT+CSMP=17,167,0,8 |
| hangup_cmd | فرمان قطع تماس | ATH | AT+CHUP | AT+CHUP |
| storage_cmd | مخزن پیامک | SM,SM,SM | ME,ME,ME | ME,ME,ME |
| boot_timeout_ms | مهلت بوت | 15s | 20s | 20s |
| clcc_authoritative | اتکا به +CLCC بهجای URC پایان تماس | ✘ | ✘ | ✔ |
چرا M08R پروفایل جدا دارد؟
M08R گاهی هیچکدام از URCهای پایان تماس (NO CARRIER / BUSY / NO ANSWER) را نمیفرستد و بعد از ارسال پیامکِ نیمهکاره هم هنگ میکند. فیلد clcc_authoritative = true یعنی پرسوجوی +CLCC منبع اصلی حقیقت است و نباید به URCهای پایان تماس تکیه کرد.
انتخاب پروفایل در کد
static Modem modem1(uart, clk, kSim800, &pwr);static Modem modem2(uart, clk, kQuectelM08, &pwr);static Modem modem3(uart, clk, kQuectelM08R, &pwr);پیکربندی کامل — همه ثابتها یکجا
همه ثابتهای کتابخانه با پیشفرض منطقی تعریف شدهاند و با #ifndef قابل بازنویسی در gsm_config.hpp کاربر (GSM_USER_CONFIG) یا پرچمهای کامپایلر هستند. جستجو کنید تا ماکرو مورد نظر را پیدا کنید.
GSM_RX_RING_SIZE = 1024 | اندازه بافر حلقوی دریافت UART (بایت) |
GSM_LINE_MAX = 512 | حداکثر طول یک خط AT |
GSM_CMD_MAX = 256 | حداکثر طول فرمان ارسالی |
GSM_SMS_TEXT_BYTES = 512 | بافر متن پیامک |
GSM_SMS_NUMBER_BYTES = 24 | بافر شماره گیرنده/فرستنده |
GSM_SMS_INBOX_SLOTS = 4 | تعداد اسلات صندوق ورودی پیامک |
GSM_USSD_RESPONSE_BYTES = 512 | بافر پاسخ USSD |
GSM_QUEUE_DEPTH = 6 | ظرفیت صف Job (بدون تخصیص پویا) |
GSM_JOB_MAX_AGE_MS = 45000 | حداکثر عمر Job در صف؛ کهنهها لغو میشوند |
GSM_JOB_START_RETRIES = 3 | تعداد تلاش شروع Job قبل از kErrJobStalled |
GSM_AT_DEFAULT_TIMEOUT = 1000 | تایماوت پیشفرض فرمانهای AT |
GSM_AT_PROMPT_TIMEOUT = 5000 | انتظار پرامپت «>» بعد از CMGS |
GSM_AT_CMGS_TIMEOUT = 10000 | انتظار نتیجه نهایی ارسال پیامک |
GSM_AT_USSD_TIMEOUT = 10000 | انتظار پاسخ شبکه برای USSD |
GSM_AT_GUARD_TIME = 30 | حداقل فاصله بین دو فرمان پشتسرهم |
GSM_REGISTRATION_TIMEOUT = 45000 | مهلت رجیستر شدن در شبکه |
GSM_PWRKEY_PULSE_MS = 500 | طول پالس پایه PWRKEY |
GSM_CALL_ANSWER_TIMEOUT_MS = 20000 | تماس خروجی: قطع خودکار اگر وصل نشد |
GSM_CALL_RING_TIMEOUT_MS = 20000 | تماس ورودی: رد خودکار اگر جواب داده نشد |
GSM_CALL_CLCC_POLL_MS = 2000 | فاصله پرسوجوی تور ایمنی +CLCC |
GSM_CALL_CLCC_MISSES = 2 | تعداد پاسخ خالی پیاپی برای اعلام «تماس مرده» |
GSM_SMS_MAX_CHARS = 60 | سقف سختگیرانه طول پیامک (نویسه UTF-8) |
GSM_CONSECUTIVE_TIMEOUTS_RESET = 3 | تایماوتهای متوالی AT قبل از بازیابی |
GSM_SIM_RETRY_INTERVAL_MS = 10000 | فاصله پروب CPIN در وضعیت بیسیمکارت |
GSM_CFUN_CYCLE_INTERVAL_MS = 25000 | فاصله چرخه CFUN=0/1 برای بازیابی سیمکارت |
GSM_HARDWARE_REBOOT_TIMEOUT_MS = 90000 | گیرکردن در Fault/SimMissing → ریست سختافزاری |
GSM_PERIODIC_RESET_MS = 3600000 | ریستکننده اول: ریست کامل هر ۱ ساعت (0=خاموش) |
GSM_OPS_RESET_COUNT = 50 | ریستکننده دوم: ریست بعد از هر ۵۰ عملیات (0=خاموش) |
GSM_WATCHDOG_GRACE_MS = 10000 | مهلت یافتن فرصت امن برای ریست؛ بعدش اجباری |
GSM_LOG_LEVEL_MAX = 4 | سقف سطح لاگ هنگام کامپایل (0=خاموش تا 5=Trace) |
GSM_LOG_BUFFER_SIZE = 180 | اندازه بافر فرمتدهی هر خط لاگ |
GSM_LOG_COLOR_DEFAULT = 1 | حالت پیشفرض رنگهای ANSI |
// gsm_user_config.hpp — فقط آنچه میخواهید عوض کنید:#define GSM_QUEUE_DEPTH 10 // صف بزرگتر#define GSM_SMS_MAX_CHARS 70 // سقف کامل UCS2#define GSM_PERIODIC_RESET_MS (30*60*1000) // ریست هر ۳۰ دقیقه#define GSM_USER_CONFIG "gsm_user_config.hpp" // در پرچمهای کامپایلرمرجع فایلبهفایل — 50 فایل، بدون حذف جزئیات
هر فایل کتابخانه: نقش، لایه معماری و خلاصه رفتاری. گروهبندی بر اساس نوع فایل؛ با جستجو سریع به فایل مورد نظر برسید.
مرکز پیکربندی کتابخانه: ماکروهای بافر، timeout، نگهبان طول پیامک، نظارت تماس و ریستهای تعمیراتی.
لایه انتزاع سختافزار: اینترفیسهای IUart، IClock، IPowerControl و تابع مقاوم به سرریز elapsed.
انواع پایه AT: AtStatus، AtResult، کدهای خطای kErr* و اینترفیس IAtObserver.
موتور اجرای فرمانهای AT: ارسال/دریافت، تشخیص prompt و خطوط نهایی، timeout و لغو تراکنش.
تبدیل جریان بایت به خطوط AT و نشان «>» با آشکارسازی overflow.
قرارداد مشترک سرویسها (IService) و وضعیت نشست مشترک (SessionState).
طبقهبندی و مسیریابی پیامهای ناخواسته (URC) مثل RING و +CMTI به handler.
توصیف ماژولهای SIM800/M08/M08R؛ ویژگی v0.4: clcc_authoritative برای M08R.
سامانه لاگ سبکوزن: سطحها، کانالهای تگدار، sinkهای قابل تعویض و ScopedSink.
ساختار Job (واحد کاری صف)، JobKind/JobState و فیلد deferred برای وقفه تماس.
صف FIFO ایستا با لغو، تعلیق و purge_stale برای جلوگیری از انباشت صف.
سرویس پیامک: ارسال/خواندن/حذف؛ ویژگی v0.4: نگهبان سختگیرانه طول (max_chars، kErrSmsTooLong).
انواع داده پیامک: SmsEncoding، SmsMessage، SmsSendReport و صندوق حلقوی SmsInbox.
سرویس وضعیت مودم: سیگنال، ثبت شبکه، SIM، ذخیرهسازی و اپراتورهای ایرانی در ModemStatus.
پل سریال برای اتصال کنسول به مودم: حالت managed (خطفرمان) و passthrough (بایت خام).
سرویس USSD با نشست تعاملی (send_reply/end_session) و پرسوجوی خودکار موجودی.
callback سبکوزن بدون heap (Delegate) با bind ایمن به متدهای عضو.
بافر حلقوی بایتی ایستا با پرچم overflow؛ مناسب بافر UART در حالت وقفه.
رشته با اندازه ثابت StaticString<N> و ابزارهای پارس AT (parse_int، csv_field).
سرویس تماس با نظارت v0.4: CallPolicy، CallEndReason، قطع/رد خودکار و polling +CLCC.
کلاس اصلی Modem: چرخه عمر، صف Jobها، housekeeping، بازیابی SIM و گیت تماس.
نرمالسازی شماره تلفن به E.164 با کد کشور پیشفرض +98.
تبدیلهای UTF-8 ↔ hex-UCS2 برای پیامک فارسی و تشخیص رمزگذاری.
کنسول تعاملی cli::Console (منتقلشده از برنامه به هسته کتابخانه) با منو، وضعیت زنده و خروجی وب.
نگهبان تعمیراتی v0.4: دو بازنشان (دورهای + بودجه عملیات)، ResetPolicy و بررسی سلامت.
شروع سریع — از صفر تا اولین پیامک فارسی
کتابخانه با CMake و PlatformIO بیلد میشود. سه مسیر رایج: STM32 با CubeHAL، آردوینو و تست میزبان بدون سختافزار.
مسیر STM32 (CMake)
# بیلد کتابخانه برای میزبان/تستcmake -B build -DGSM_BUILD_TESTS=ONcmake --build build./build/host_smoke # تست دود# برای STM32: پورت HAL را فعال کنیدcmake -B build-stm32 -DGSM_PORT_STM32=ON \ -DCMAKE_TOOLCHAIN_FILE=arm-gcc.cmakestatic port::Stm32Uart uart(&huart2); // UART مودمstatic port::Stm32Clock clk;static port::Stm32PowerControl pwr(GPIOB, GPIO_PIN_12);static Modem modem(uart, clk, kQuectelM08R, &pwr);void AppInit() { modem.set_on_sms(...); // کالبک پیامک دریافتی modem.begin();}void AppRun() { modem.tick(); } // در while(1)مسیر Arduino
#include <gsm/modem/modem.hpp>#include <gsm_port_arduino.hpp>port::ArduinoUart modem_uart(Serial1);port::ArduinoClock clk;Modem modem(modem_uart, clk, kSim800);void onSms(void*, const SmsMessage& m) { Serial.println(m.text.c_str()); // متن فارسی UTF-8}void setup() { Serial.begin(115200); Serial1.begin(115200); modem.set_on_sms(SmsService::Received(&onSms, nullptr)); modem.begin();}void loop() { modem.tick(); if (modem.ready()) modem.send_sms("09037744527", "سلام از آردوینو");}PlatformIO
library.json آماده است: پلتفرمهای ststm32، atmelavr، espressif32 و native با پرچمهای -std=gnu++17 -fno-exceptions -fno-rtti. کافی است مسیر کتابخانه را به lib_deps اضافه کنید.
تست بدون سختافزار
پورت Host با شبیهساز UART اجازه میدهد سناریوها (بوت، USSD، تماس، صف، واچداگ) کاملاً قطعی روی دسکتاپ تست شوند — همان چیزی که host_smoke.cpp انجام میدهد.
چه چیزی را تنظیم کنم؟
معمولاً هیچ! پیشفرضها برای پیامک فارسی UCS2 و پایداری ۲۴/۷ قفل شدهاند. فقط اگر روی شبکه/سیمکارت متفاوت رفتار عوض شد، رشته CSMP را در DeviceTraits تغییر دهید.
سیمکشی سختافزار (پیشنهادی)
| پین مودم | اتصال MCU | توضیح |
|---|---|---|
| VCC 5V/4V | منبع تغذیه 2A | جریان پیکی تماس/انتقال بالاست |
| TXD | RX UART (115200) | خط داده از مودم |
| RXD | TX UART | خط داده به مودم |
| PWRKEY | GPIO (مثلاً PB12) | اختیاری — برای پالس/ریست سختافزاری |
| GND | GND مشترک | حتماً مشترک با MCU |
گامهای اولیه پیشنهادی
- 1 کتابخانه را روی میزبان بیلد و host_smoke را اجرا کنید تا با رفتار آشنا شوید.
- 2 مثال STM32/Arduino را با کنسول تعاملی (کلید m برای منو) روی بورد بیندازید.
- 3 با کلید i وضعیت زنده را ببینید و با b استعلام شارژ بگیرید.
- 4 پیامک فارسی را با دستور
sms 09… متنتست کنید. - 5 اگر نیاز به دیباگ AT داشتید، با کلید P به پل Passthrough بروید.
درسهای میدانی — تجربههای واقعی که در کد قفل شدند
این بخش خلاصه چیزهایی است که از لاگهای واقعی مودمها آموخته شد؛ رفتارهایی که مستند نمیشوند اما کار شما را خراب میکنند. همه اینها الان بهصورت خودکار در کتابخانه هندل میشوند.
با CSCS="UCS2" شماره هم باید UCS2 باشد
وقتی charset روی UCS2 قفل شده، اگر شماره را ASCII بفرستید مودم خطای +CMS ERROR: 3518 میدهد. شماره باید به هگز UCS2 تبدیل شود — این رفتار در DeviceTraits::ucs2_recipient_required قفل شده و SmsService خودکار تبدیل میکند.
AT+CSCS="UCS2" → OKAT+CMGS="+989037744527" → +CMS ERROR: 3518 ← شماره ASCII!AT+CMGS="002B0039...3237" → > ← همان شماره، UCS2-hex ✔قبل از Ctrl+Z هیچوقت CRLF نفرست
اگر بعد از payload پیامک CRLF (\r\n) بفرستید و بعد Ctrl+Z، مودم خطای +CMS ERROR: 520 میدهد. AtEngine::write_raw هرگز CRLF اضافه نمیکند؛ پایان payload فقط با write_ctrl_z() اعلام میشود.
"0633064406270645\r\n" + 1A → +CMS ERROR: 520"0633064406270645" + 1A → +CMGS: 3 OK ✔گیرکردن در پرامپت «>» با ESC حل میشود
تا وقتی مودم در حالت prompt/data-entry است، به هیچ فرمان ATای جواب نمیدهد و +CME ERROR: 58 میدهد. هر لغو یا خطای payload در کتابخانه ابتدا بایت 0x1B (ESC) میفرستد تا مودم بیرون بیاید.
AT+CMGS="..." → >"0633..." (بدون 1A)at → +CME ERROR: 58 ← مودم گیر کرده!<ESC 0x1B> → آزاد میشود ✔ترتیب امن ارسال پیامک
ترتیب استاندارد: CMGF=1 → CSCS="UCS2" → CSMP → CPMS → CMGS. پارامتر CSMP برای SIM800 روی «17,71,0,25» و برای M08 روی «17,167,0,8» قفل شده؛ هر دو در DeviceTraits قابل تغییرند.
بنر بوت و URCها قابل اعتماد نیستند
پیامهایی مثل «DY» (نسخه بریده RDY)، «Call Ready»، «SMS Ready» و «+CPIN: READY» همه در UrcRouter هندل میشوند، اما ماشین حالت هرگز منتظرشان نمیماند و خودش با پروب AT مودم را میآزماید.
قطع تماس در هر ماژول فرق دارد
فرمان تماس صوتی «ATD09037744527;» (سمیکولن = صوتی) است. قطع تماس در SIM800 با ATH و در Quectel M08/M08R با AT+CHUP انجام میشود — این تفاوت در DeviceTraits::hangup_cmd ذخیره شده و اگر فرمان اصلی جواب ندهد، ATH بهعنوان جایگزین یکبار امتحان میشود.
اکو روشن مودم گیجکننده نیست
لاگهای میدانی نشان داد اکوی مودم روشن است. ATE0 در بوت خاموشش میکند، اما حتی اگر روشن بماند، پارسر AtEngine خط اکو را با فرمان ارسالی مقایسه و دور میریزد.
M08R: پایان تماس بیصدا
M08R هنگام رد یا بیجوابی تماس، هیچکدام از NO CARRIER / BUSY / NO ANSWER را نمیفرستد. راهحل: پرسوجوی دورهای AT+CLCC بهعنوان منبع حقیقت (clcc_authoritative=true) بهعلاوه تایمر قطع خودکار ۲۰ ثانیهای.
M08R: هنگ و ارسال پیام خالی
ارسال متن بلند یا ارسال در میانه عملیات دیگر، M08R را به حالت prompt میاندازد و گاهی پیامک خالی میفرستد. چهار محافظ در برابرش: گارد طول ۶۰ نویسه، گیت تماس، لغو امن با ESC، و ریستهای دورهای نگهبانی.
نکته مشترک همه این درسها: هیچچیز مودم را باور نکن. ماشین حالت همیشه خودش میآزماید، تایمر دارد، تور ایمنی دارد و در بدترین حالت خودش را ریست میکند. این فلسفه «بیاعتمادی نظاممند» است که پایداری ۲۴/۷ را ممکن میکند.
مصرف حافظه — بودجهبندی دقیق بایتبهبایت
با تنظیمات پیشفرض، کل کتابخانه حدود ۳ تا ۴ کیلوبایت RAM مصرف میکند — همه استاتیک، بدون هیچ تخصیصی در زمان اجرا. همه اجزا با ماکروهای GSM_* قابل کوچک یا بزرگ شدن هستند.
| مؤلفه | بودجه پیشفرض | ماکرو تنظیم |
|---|---|---|
| بافر حلقوی RX | 1024 بایت | GSM_RX_RING_SIZE |
| DMA/بافر پورت STM32 | 256 بایت | در پورت |
| صف کارها | ۶ × ~۲۳۰ بایت ≈ 1.4KB | GSM_QUEUE_DEPTH |
| بافرهای متن پیامک | 512 + 24 بایت | GSM_SMS_TEXT_BYTES |
| صندوق ورودی | ۴ اسلات پیام | GSM_SMS_INBOX_SLOTS |
| بافر USSD | 512 بایت | GSM_USSD_RESPONSE_BYTES |
| بافرهای لاگ/خط | 180 + 512 بایت | GSM_LOG_BUFFER_SIZE |
برای MCUهای کوچک، کم کردن GSM_RX_RING_SIZE به ۵۱۲ و GSM_QUEUE_DEPTH به ۴ مصرف را زیر ۲ کیلوبایت میبرد؛ برای کارهای سنگین (اسپم پیامک، صف تماس) میتوانید صف را تا ۱۲ بالا ببرید بدون نگرانی از heap.