اگر یک تبلت اندرویدی ردهبالا دارید، احتمالاً کمتر از ۱۰ درصد توان پردازشی واقعی آن را به کار میگیرید. یک توسعهدهنده بهتازگی نشان داد که چگونه میتوان با تبدیل یک تبلت Lenovo Legion Tab TB321FU به یک داشبورد کامل OBD-II، تمام این پتانسیل را آزاد کرد؛ آن هم در حالی که سیستم کاملاً از طریق یک شل لینوکس اجرا میشود و حتی یک اپلیکیشن جانبی روی دستگاه نصب نشده است.
بیشتر کاربران تبلتها را ابزاری برای مصرف رسانه میبینند. اما برای کسانی که از طریق Magisk دسترسی روت (Root) دارند و از ترمینالهایی مثل Termux استفاده میکنند، این دستگاهها در واقع کامپیوترهای لینوکسی قابل حمل هستند. توسعهدهنده احساس کرد استفاده از یک دستگاه با مشخصات فنی بالا و CPU قدرتمند صرفاً برای تماشای ویدیو و اسکرول کردن، اتلاف منابع است. چالش اصلی در اینجا، خواندن دادههای واحد کنترل موتور (ECU) — مانند دور موتور (RPM)، سرعت، دمای مایع خنککننده و موقعیت دریچه گاز — با استفاده از یک دانگل ارزانقیمت بلوتوث ELM327 بود، در حالی که تمام این مسیر باید با دور زدن کامل اکوسیستم اپلیکیشنهای استاندارد اندروید طی میشد.
کمکخلبان هوش مصنوعی و دسترسی روت
این پروژه با کمک Claude Code CLI ساخته شد؛ یک عامل کدنویسی هوش مصنوعی که مستقیماً در Termux اجرا میشود. این دستیار در ساختاردهی به برنامههای بازتابی (Reflection) جاوا، ردیابی خطاهای عجیب و کشف ترفندهایی که در آموزشهای استاندارد یافت نمیشدند، نقش کلیدی داشت.
اگرچه Claude Code میتواند روی دستگاههای بدون روت نیز اجرا شود، اما توسعهدهنده خاطرنشان کرد که دسترسی روت برای این پروژه خاص حیاتی بود. هوش مصنوعی برای اجرای دستورات با سطح دسترسی su و دستکاری فایلهای سیستمی، نیاز به مجوز کامل داشت. بهطور مشخص، روت برای اجرای app_process با شناسهی کاربری (UID) ۲۰۰۰ و دسترسی به فایلهای سیستمی که معمولاً برای اپلیکیشنهای استاندارد بسته هستند، ضروری بود. تبلت مورد استفاده از اندروید ۱۶ بهره میبرد و روت کردن آن باعث شد دستیار AI بتواند بهجای ارائه صرفِ پیشنهاد، از ابتدا تا انتها در پیادهسازی عملی کمک کند.
دیوار بلوتوث
با توجه به پیشینه لینوکسی توسعهدهنده، اولین غریزه او این بود که با استفاده از پایتون یک سوکت Bluetooth RFCOMM باز کند. در یک محیط استاندارد لینوکس، این یک فرآیند ساده در پنج خط کد با استفاده از socket.AF_BLUETOOTH ،socket.SOCK_STREAM و socket.BTPROTO_RFCOMM است. با این حال، این اقدام منجر به خطای AttributeError شد، زیرا پایتون در Termux از AF_BLUETOOTH پشتیبانی نمیکند.
تلاشها برای استفاده از ابزارهای خط فرمان مانند rfcomm ،hcitool یا sdptool نیز شکست خورد. بررسی مسیر /dev نشان داد که /dev/rfcomm اصلاً وجود ندارد. توسعهدهنده متوجه شد که اندروید از پشته استاندارد BlueZ لینوکس استفاده نمیکند؛ بلکه از یک پشته اختصاصی به نام Fluoride بهره میبرد.
دسترسی به Fluoride معمولاً محدود به اپلیکیشنهای اندرویدی است که مجوز BLUETOOTH_CONNECT را دارند. برای دور زدن این محدودیت بدون ساخت APK یا نصب نرمافزارهایی مانند Torque، توسعهدهنده از app_process استفاده کرد؛ یک درگاه پشتی به محیط اجرای اندروید (Android Runtime). این روش به او اجازه داد تا برنامههای کوچک جاوا بنویسد، آنها را با استفاده از javac و d8 به فایلهای DEX کامپایل کند و مستقیماً از طریق شل اجرا نماید.
سازوکار پل ارتباطی
این اقدام یک پل ارتباطی ایجاد کرد که در آن برنامه جاوا اتصال بلوتوث را مدیریت میکرد و بایتهای دریافتی را به یک سوکت TCP محلی در آدرس 127.0.0.1:35000 آینه (Mirror) میکرد. این معماری بهطور مؤثر پیچیدگیهای پشته بلوتوث اندروید را پشت یک سوکت TCP استاندارد پنهان کرد و باعث شد دانگل بلوتوث دقیقاً مانند یک آداپتور WiFi ELM327 رفتار کند. مسیر جریان داده به این شکل بود: ELM327 $
ightarrow$ BT SPP/RFCOMM $
ightarrow$ Bridge (uid 2000, app_process + su) $
ightarrow$ TCP 127.0.0.1:35000 $
ightarrow$ سرور Node + داشبورد وب.
عبور از ۶ تله فنی
مسیر رسیدن به یک اتصال پایدار نیازمند حل ۶ مانع فنی متمایز بود که اغلب یکییکی و از طریق آزمون و خطا کشف شدند:
- تضاد UID: اجرای برنامه با روت (uid 0) منجر به بازگشت مقدار null برای آداپتور بلوتوث میشد، زیرا روت فاقد هویت پکیج (Package Identity) است. توسعهدهنده کشف کرد که مجوز
BLUETOOTH_CONNECTبه کاربر شل (uid 2000) گره خورده است. راه حل، اجرای فرآیند دقیقاً با دستورsu 2000 -c '...'بود. یک اشتباه رایج استفاده ازsu -c '...' 2000بود که Magisk آن را به عنوان اجرای روت با آرگومان '2000' تفسیر میکرد. علاوه بر این، فایل JAR باید بهجای دایرکتوری خانگی Termux، در مسیر/data/local/tmpقرار میگرفت تا توسط uid 2000 قابل خواندن باشد. - بلاکهای API پنهان: APIهای داخلی اندروید بهطور پیشفرض مسدود بودند. این مشکل با فراخوانی
dalvik.system.VMRuntime.getRuntime().setHiddenApiExemptions("L")برای باز کردن توابع ضروری حل شد. - خطای Looper: فرآیندهای شل فاقد رشته (Thread) اصلی هستند، که باعث میشد فراخوانیهای Context و Adapter با خطا مواجه شوند. توسعهدهنده مجبور شد با استفاده از
android.os.Looper.prepareMainLooper()یک لوپر اصلی را بهصورت دستی مقداردهی کند. - باگ اندروید ۱۴ به بالا: در اندروید ۱۴ و نسخههای بالاتر، یک مدیر استاتیک خاص در فرآیندهای غیر-اپلیکیشنی null است که منجر به خطای
NullPointerExceptionدرgetBluetoothManagerServiceRegisterer()میشد. توسعهدهنده با بررسی سورسکد AOSP راه حل را یافت: نمونهسازی دستیandroid.bluetooth.BluetoothFrameworkInitializer.setBluetoothServiceManager(new android.os.BluetoothServiceManager()). - دسترسی به Context: آداپتور بلوتوث باید از طریق یک مسیر سیستمی خاص بازیابی میشد:
ActivityThread.systemMain().getSystemContext().getSystemService("bluetooth").getAdapter(). - سوکتهای ناامن: متدهای اتصال استاندارد اغلب توسط دانگلهای کپی (Clone) رد میشدند. پایدارترین نتیجه با استفاده از
device.createInsecureRfcommSocketToServiceRecordو UUID خاص00001101-0000-1000-8000-00805F9B34FBبه دست آمد.

معمای جمعکننده زباله (GC)
حتی پس از رسیدن به وضعیت "RFCOMM CONNECTED"، لینک هر ۰.۹ ثانیه یکبار قطع میشد. سیستم دوباره متصل میشد و بلافاصله قطع میگشت؛ درست مانند یک مترونوم. توسعهدهنده ابتدا گمان کرد دانگل ارزانقیمت معیوب است و ساعتها وقت صرف تغییر زمانهای انتظار (Timeout) و ارسال دستورات Keep-alive کرد.
با این حال، دقت شدید بازه ۰.۹ ثانیهای نشان داد که این یک مشکل در چرخه حیات نرمافزار است، نه شکست سختافزاری. مقصر، جمعکننده زباله (Garbage Collector یا GC) اندروید بود. در یک اپلیکیشن عادی، اشیاء فریمورک بلوتوث تا زمانی که اپلیکیشن زنده است، باقی میمانند. اما در این فرآیند شل، به محض اینکه تابع تنظیمات به پایان میرسید و connect() مقدار بازمیگرداند، JVM اشیاء ActivityThread ،Context ،BluetoothManager و BluetoothAdapter را به عنوان موارد بدون استفاده شناسایی و حذف میکرد.
وقتی این اشیاء پاک میشدند، فرآیند بلوتوث اندروید تقریباً یک ثانیه بعد لینک RFCOMM را قطع میکرد. راه حل، جلوگیری از حذف این اشیاء توسط GC از طریق نگه داشتن آنها در ارجاعات استاتیک قوی (Static Strong-references) بود:
static ActivityThread sActivityThread;
static Context sContext;
static BluetoothManager sBtManager;
static BluetoothAdapter sAdapter;

مقابله با رفتارهای عجیب دانگلهای کپی
فراتر از مشکل GC، سختافزارهای کپی ELM327 رفتارهای خاصی داشتند که نیازمند راهکارهای نرمافزاری بود:
- شکست دستور ATZ: دستور استاندارد ریست
ATZباعث میشد تراشه ریست شده و لینک بلوتوث با خطای "Broken pipe" قطع شود. سرور طوری برنامهریزی شد که هرگزATZرا ارسال نکند. - تایماوتهای بیکاری: اگر دانگل حدود ۰.۷ ثانیه بدون فعالیت میماند، لینک را قطع میکرد. برای جلوگیری از این اتفاق، پل ارتباطی در هر زمان که کلاینتی متصل نبود، یک کاراکتر Carriage-Return (بازگشت به خط) میفرستاد تا اتصال زنده بماند.
- هنگ سختافزاری: پس از چرخههای سریع اتصال/قطع در طول دیباگ، دانگل گاهی هنگ میکرد و اتصالها را در چند میلیثانیه میپذیرفت و سپس قطع میکرد. تنها راه حل، جدا کردن و وصل کردن مجدد دانگل از پورت OBD-II به مدت ۱۰ ثانیه بود.
برای پنهان کردن این ناپایداریها از دید کاربر، توسعهدهنده یک شنونده TCP (TCP Listener) پیاده کرد که باز میماند و بهصورت بیصدا در پسزمینه، بازاتصالات بلوتوث را با تایماوت ۲.۵ ثانیه انجام میداد.
ساخت خط لوله داده (Data Pipeline)
با پایدار شدن پل ارتباطی، توسعهدهنده یک پشته چندلایه برای تجسم دادهها ساخت:
۱. پل جاوا: یک برنامه بازتابی ۵۷۰ خطی که بایتهای بلوتوث را به یک سوکت TCP محلی در 127.0.0.1:35000 تبدیل میکند.
۲. سرور Node.js: یک سرور ۷۵۰ خطی (بدون استفاده از فریمورک) که از transport.js برای اتصال به پل استفاده میکند. این سرور شامل یک شبیهساز داخلی بود که دادههای واقعگرایانه برای حالتهای بیکاری، شتاب و کروز تولید میکرد تا داشبورد بدون نیاز به حضور خودرو طراحی شود. فایل obd.js پروتکل ELM327 و تشخیص PIDها را مدیریت میکرد و server.js از Server-Sent Events (SSE) برای ارسال بهروزرسانیهای لحظهای به مرورگر استفاده میکرد.
۳. داشبورد وب: یک سایت استاتیک ۶۰۰ خطی که برای استفاده آفلاین بهصورت محلی باندل شده بود. این سایت از GridStack.js برای چیدمانهای قابل جابجایی و تغییر اندازه، از canvas-gauges برای نشانگرهای بصری و از uPlot برای نمودارهای زنده استفاده میکرد. سایت بهصورت PWA (وباپلیکیشن پیشرونده) پیکربندی شد تا تجربه تمامصفحه "Add to Home screen" را فراهم کند.

تست در دنیای واقعی و عملکرد
توسعهدهنده خوانایی را بر زیبایی ترجیح داد و بهجای سرعتسنجهای سنتی، کارتهای عددی بزرگ با مینیبارهای رنگی، مقادیر کمینه/بیشینه و مناطق رنگی را انتخاب کرد. در تست روی یک خودروی واقعی، سیستم بهروزرسانیهای بدون تأخیر و لحظهای RPM و دمای مایع خنککننده را ارائه داد. توسعهدهنده مشاهده کرد که با فشار دادن پدال گاز، RPM بهصورت آنی روی صفحه میپرد و هیچ قطعی در اتصال رخ نمیدهد.
بر اساس این موفقیت، دو قابلیت پیشرفته اضافه شد:
- تایمر شتاب (Drag Timer): یک اسکریپت سمت سرور (
drag.js) که پس از دستور "ARM" و حرکت خودرو بهطور خودکار شروع به کار میکند. این ابزار فاصله را از روی سرعت محاسبه کرده و زمانهای میانی (Splits) را برای ۶۰ فوت، ۰-۱۰۰ کیلومتر بر ساعت، ۲۰۱ متر (۱/۸ مایل) و ۴۰۲ متر (۱/۴ مایل) به همراه سرعت نهایی ثبت میکند. یک اجرای نمونه، حداکثر سرعت ۱۰۷ کیلومتر بر ساعت و زمان ۱۸.۷۱ ثانیه برای ۴۰۲ متر را ثبت کرد. ساختار دادههای یک اجرا به این شکل است:peakSpeed: 107distance: 403.5duration: 18.77splits: 60ft (2.19s, 36km/h), 0-100kmh (10.8s, 100km/h), 201m (11.03s, 101km/h), 402m (18.71s, 74km/h).
- دینامومتر مجازی (Virtual Dyno): یک ابزار سمت کلاینت که از روش اینرسی برای محاسبه اسببخار و گشتاور استفاده میکند. با وارد کردن جرم خودرو و تلفات سیستم انتقال قدرت در تنظیمات، سیستم از فرمول (نیرو = جرم × شتاب) به اضافهی مقاومت هوا و غلتشی برای تولید منحنی دینامومتر از یک لانچ واحد استفاده میکند.

نتیجهگیری
این پروژه ثابت میکند که محدودیت سختافزار موبایل اغلب ناشی از سیلیکون نیست، بلکه به دلیل مجوزهای نرمافزاری است. با تبدیل تبلت به یک محیط لینوکسی خام و استفاده از AI برای پیمایش در اعماق مستندنشده پروژه متنباز اندروید (AOSP)، یک تبلت لوکس به یک ابزار خودرویی در سطح حرفهای تبدیل شد.
سه درس کلیدی از این تجربه حاصل شد: اول، معماری بلوتوث اندروید کاملاً با BlueZ لینوکس متفاوت است و برای دسترسی شل به app_process نیاز دارد. دوم، مدیریت UID حیاتی است؛ کاربر شل (uid 2000) اغلب برای مجوزهای سختافزاری خاص، مفیدتر از روت (uid 0) است. سوم، اتصالی که با نظم کامل قطع میشود، معمولاً نشانه مشکل در جمعکننده زباله (GC) است، نه نقص سختافزاری.
برای کسانی که به دنبال تجربه هستند، اولین قدم درک تفاوت بین مجوزهای روت و شل در اندروید ۱۴ و ۱۶ است. میتوانید با بررسی قابلیتهای app_process در Termux شروع کنید تا ببینید کدام APIهای سیستمی از طریق شل شما قابل دسترسی هستند.
گام بعدی شما
- بررسی تفاوتهای دسترسی Root و Shell در اندروید ۱۴ و ۱۶ برای درک محدودیتهای سختافزاری.
- آزمایش قابلیتهای
app_processدر Termux برای دسترسی به APIهای سیستمی بدون نیاز به ساخت APK. - مطالعه سورسکد AOSP در بخش بلوتوث برای شناسایی متدهایی که در SDKهای استاندارد پنهان شدهاند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell و پردازش لبه مراجعه کنید.




گفتگو