DEV Community

Cover image for Potensi Asli Tablet Mahal Dengan Artificial Intelligence (Lenovo Legion Tab Gen3)
M Lukman
M Lukman

Posted on

Potensi Asli Tablet Mahal Dengan Artificial Intelligence (Lenovo Legion Tab Gen3)

Ini cerita santai waktu saya bikin proyek iseng namanya OBD Dash. Intinya, tablet saya akhirnya bisa baca data ECU mobil seperti RPM, kecepatan, dan suhu, lewat dongle Bluetooth ELM327. Yang bikin seru, semuanya jalan tanpa install satu aplikasi pun, murni dari dalam shell, dan saya garap bareng Claude Code CLI yang jalan langsung di Termux sebagai asisten codernya. Di jalan banyak buntunya, ada satu misteri yang bikin saya hampir nyalahin hardware, dan ada satu momen pas angka RPM beneran muncul di layar yang bikin semua usaha kerasa worth it.

Foto Awal

Awalnya cuma kasihan sama tablet sendiri

Jujur, saya punya satu tablet yang kalau dipikir-pikir kemahalan buat dipakai cuma nonton dan scroll-scroll. Lenovo Legion Tab TB321FU. Tablet kelas atas, speknya garang, CPU-nya kenceng. Tapi selama ini potensinya kepakai mungkin nggak sampai sepersepuluh. Sayang banget rasanya.

Yang bikin gemes, benda ini sebenernya komputer betulan. Sudah saya root pakai Magisk, dan saya hidup di Termux, terminal Linux di dalamnya. Jadi di kepala saya selalu ada yang ngeganjel. Masa tablet sekuat ini cuma buat gitu-gitu doang?

Suatu hari kepikiran satu tantangan yang kayaknya seru sekaligus berguna. Bisa nggak tablet ini ngomong langsung sama mobil saya? Maksudnya baca data dari ECU, mulai dari RPM, kecepatan, suhu coolant, sampai throttle, lewat dongle ELM327 Bluetooth murah yang nyolok di port OBD-II. Habis itu tampilin angkanya gede-gede di browser.

Tapi saya kasih satu aturan main ke diri sendiri. Nggak boleh install aplikasi apa pun. Nggak Torque, nggak APK sideload, nggak ada. Tablet udah rooted, Termux udah ada, mestinya bisa langsung dari shell. Baru belakangan saya sadar, aturan satu kalimat itulah yang nyita waktu saya berhari-hari sekaligus bikin proyeknya jadi petualangan.

Asisten codernya: Claude Code CLI di tablet yang udah dirooted

Sebelum masuk ke teknis, ada satu hal yang perlu saya ceritain. Saya nggak ngerjain ini sendirian. Claude Code CLI yang tadi saya sebut itu nemenin sepanjang proyek. Dia yang bantu nyusun program Java reflection-nya, ngejar error-error aneh, sampai nemu trik yang nggak ada di tutorial mana pun.

Yang menarik, Claude Code CLI ini sebenernya bisa diinstal di Android biasa lewat Termux, tanpa root sekalipun. Tapi potensinya baru kebuka penuh kalau tabletnya udah dirooted. Soalnya banyak hal di proyek ini yang cuma jalan dengan su. Mulai dari jalanin app_process sebagai uid 2000, baca file sistem yang ketutup buat app biasa, sampai ngotak-ngatik hal level OS. Tanpa root, asistennya cuma bisa nyaranin, tapi nggak bisa ikut ngeksekusi sampai ke dalam.

Justru itu alasan utama saya nge-root Android 16 di tablet ini. Bukan cuma biar speknya kepakai maksimal, tapi biar asisten codernya punya akses penuh juga. Jadi dia bisa beneran bantu dari ujung ke ujung, bukan setengah-setengah cuma jadi tukang saran.

Naluri anak Linux yang langsung kejedot tembok

Karena udah biasa di Linux, langkah pertama saya otomatis. Buka socket Bluetooth RFCOMM dari Python. Di Linux biasa, ini cuma lima baris.

import socket
s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)
Enter fullscreen mode Exit fullscreen mode

Hasilnya AttributeError. Python di Termux ternyata nggak punya AF_BLUETOOTH.

Ya udah, coba tool command-line aja. rfcomm? hcitool? sdptool? Nggak ada satu pun. Saya intip /dev, dan /dev/rfcomm juga nggak ada. Pelan-pelan saya nyadar kenyataan pahitnya. Android itu nggak pakai BlueZ. Stack Bluetooth Linux yang saya kenal seumur hidup, di sini sama sekali nggak ada.

Android punya stack Bluetooth sendiri namanya Fluoride. Dan satu-satunya pintu masuk cuma lewat API Bluetooth Java Android. Masalahnya, API itu normalnya cuma dipanggil dari dalam sebuah aplikasi, dengan permission BLUETOOTH_CONNECT.

Lah, padahal saya tadi keukeuh nggak mau bikin aplikasi. Di titik ini, orang waras biasanya nyerah dan install Torque aja. Saya malah makin penasaran. Bisa nggak sih manggil API Java Android itu dari shell biasa, tanpa app sama sekali?

Nemu app_process, pintu belakang ke runtime Android

Ternyata ada celahnya. Android nge-start tiap aplikasi pakai runtime namanya app_process. Dan app_process ini bisa dipanggil langsung dari shell. Artinya saya bisa nulis program Java kecil, compile jadi DEX pakai javac terus d8, lalu jalanin lewat app_process. Programnya jalan di luar aplikasi mana pun, tapi tetep di dalam runtime Android yang punya akses ke Bluetooth.

Idenya begini. Program Java ini buka koneksi Bluetooth ke ELM327, terus nyalin byte-nya bolak-balik ke TCP socket lokal di 127.0.0.1:35000. Jadi dari sisi luar, port itu kelakuannya persis kayak adapter ELM327 WiFi. Semua kerumitan Bluetooth Android disembunyiin di balik socket TCP biasa yang gampang diajak ngobrol.

ELM327  <- BT SPP / RFCOMM ->  bridge (uid 2000, app_process + su)  <- TCP 127.0.0.1:35000 ->  server Node + dashboard web
Enter fullscreen mode Exit fullscreen mode

Di kepala kelihatan rapi banget. Eksekusinya ternyata ada enam jebakan beruntun.

Enam jebakan yang ketemunya satu per satu

Saya nulis ObdBridge.java, terus nyoba hal paling simpel. Ambil adapter Bluetooth, connect. Hasilnya null. Mulai deh drama penemuannya.

Jebakan pertama, jangan jalan sebagai root. Naluri "pakai root buat semua yang susah" malah salah total di sini. Saya jalanin sebagai root, alias uid 0, dan getAdapter() balik null. Ternyata root nggak punya identitas package, jadi sistem ogah ngasih adapter. Yang megang permission BLUETOOTH_CONNECT itu uid 2000, alias shell. Dan cara manggilnya pun ada jebakannya sendiri.

su 2000 -c '...'      # BENAR, jalan sebagai uid 2000
su -c '...' 2000      # SALAH, Magisk malah jalan sebagai root, '2000' jadi argumen
Enter fullscreen mode Exit fullscreen mode

Selain itu, jar-nya harus ditaruh di tempat yang bisa dibaca uid 2000, misalnya /data/local/tmp, dan bukan di home Termux yang ketutup.

Jebakan kedua, hidden API diblokir. Panggilan ke API internal langsung ditolak. Harus dibuka dulu.

dalvik.system.VMRuntime.getRuntime().setHiddenApiExemptions("L");
Enter fullscreen mode Exit fullscreen mode

Jebakan ketiga, error "no Looper". Panggilan context dan adapter ngelempar error soal Looper. Proses shell nggak punya main thread Android kayak app, jadi harus dibikin manual.

android.os.Looper.prepareMainLooper();
Enter fullscreen mode Exit fullscreen mode

Jebakan keempat, ini yang paling nyebelin dan paling ngumpet. Abis semua di atas, getAdapter() masih aja null. Malah getState() ngelempar NPE aneh, bunyinya getBluetoothManagerServiceRegisterer() on null. Nggak ada di Stack Overflow, nggak ada di tutorial mana pun. Saya sampai harus ngubek source code AOSP, baru nemu kalau di Android 14 ke atas, di proses non-app, ada satu static yang null dan harus saya isi sendiri.

android.bluetooth.BluetoothFrameworkInitializer
    .setBluetoothServiceManager(new android.os.BluetoothServiceManager());
Enter fullscreen mode Exit fullscreen mode

Begitu baris ini ditambahin, jalan kebuka.

Jebakan kelima, akhirnya bisa ambil adapternya lewat jalur yang bener.

ActivityThread.systemMain().getSystemContext()
    .getSystemService("bluetooth").getAdapter();
Enter fullscreen mode Exit fullscreen mode

Jebakan keenam, soal socket buat dongle clone. Connect biasa kadang ditolak sama clone. Yang paling mulus ternyata versi insecure.

device.createInsecureRfcommSocketToServiceRecord(
    UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect();
Enter fullscreen mode Exit fullscreen mode

Dan akhirnya, tulisan RFCOMM CONNECTED nongol di log. Bluetooth nyambung ke dongle. Saya kira ini garis finis. Eh ternyata ini malah awal dari bagian yang paling bikin pusing.

Misteri putus tepat satu detik

Koneksi berhasil. Terus putus pas sekitar 0,9 detik kemudian. Reconnect otomatis, putus lagi 0,9 detik. Terus-terusan kayak metronom.

Tersangka pertama saya jelas. Dongle clone murah ini pasti rusak. Gejalanya persis kayak hardware jelek. Connect bentar, mati, ulang. Saya sempet nyalahin dongle berjam-jam. Ganti timeout, ganti cara connect, kirim perintah penjaga. Nggak ada yang ngubah pola 0,9 detik itu sedikit pun.

Justru keteraturannya yang akhirnya bikin saya curiga. Hardware rusak biasanya gagalnya nggak rapi, kok ini konsisten banget. Berarti sesuatu yang berbasis timer atau lifecycle, bukan elektronik. Terus kepikiran satu kemungkinan. Ini Garbage Collector.

Di aplikasi Android normal, objek-objek framework Bluetooth idup selama app-nya idup. Tapi di proses shell saya, begitu fungsi setup kelar dan connect() balik, JVM ngeliat ActivityThread, Context, BluetoothManager, dan BluetoothAdapter itu kayak udah nggak kepake siapa-siapa. Terus di-GC. Nah, begitu BluetoothManager dan Context yang ndaftarin adapter itu ilang, proses Bluetooth Android nge-reap link RFCOMM-nya kira-kira sedetik kemudian. Nggak peduli ada data ngalir atau nggak.

Solusinya ternyata sesimpel nahan objeknya biar nggak di-GC. Caranya pegang strong-reference di field statik.

// JANGAN biarin ini di-GC. Kalau ilang, RFCOMM di-reap sekitar 1 detik kemudian.
static ActivityThread   sActivityThread;
static Context          sContext;
static BluetoothManager sBtManager;
static BluetoothAdapter  sAdapter;
Enter fullscreen mode Exit fullscreen mode

Saya jalanin lagi. Connect, satu detik, masih nyambung. Lima detik, tiga puluh detik, tetep nyambung. Abis sekian lama nyalahin dongle yang sebenernya sehat-sehat aja, pelakunya ternyata garbage collector.

Dongle clone yang ternyata punya kepribadian

Abis GC beres, dongle-nya sendiri masih nyimpen beberapa keanehan yang harus saya pelajarin satu-satu.

Pertama, jangan pernah kirim ATZ. Perintah reset standar ELM327 ini malah nge-reset chip dan njatuhin link Bluetooth dengan error "Broken pipe" di clone. Jadi server saya sengaja nggak pernah ngirim ATZ.

Kedua, clone njatuhin link yang nganggur dalam waktu sekitar 0,7 detik. Kalau nggak ada yang ngajak ngobrol, dia langsung ngambek. Jadi bridge ngirim keepalive, cuma satu carriage-return, selama belum ada client yang nempel.

Ketiga, abis sederet connect dan drop yang cepet, dan ini banyak kejadian pas saya lagi debugging, clone bisa nyangkut total. Dia terima connect terus putus dalam milidetik. Toggle Bluetooth tablet nggak ngaruh. Satu-satunya obat ya cabut lalu colok lagi dongle dari port OBD sekitar 10 detik.

Saya juga bikin TCP listener-nya tetep kebuka dan nge-reconnect Bluetooth diem-diem di bawah dashboard, dengan timeout connect dibatesin 2,5 detik. Tujuannya biar dari sisi saya semua kerasa mulus, walau di belakang layar ada drama reconnect.

Bikin sisanya, dari byte mentah jadi angka yang berarti

Begitu jembatannya stabil, bagian paling ngeselin udah lewat. Sisanya web stack biasa yang malah nyenengin.

Di atas socket TCP 35000 itu saya nulis server Node polosan, tanpa framework. transport.js nyambung ke port bridge, dan punya simulator bawaan. Simulatornya ngeluarin data palsu tapi realistis, dengan siklus idle, akselerasi, lalu cruise. Ini ternyata penyelamat, karena saya bisa nata seluruh layout dashboard di rumah, tanpa mobil sama sekali. Lalu obd.js yang ngomong protokol ELM327 dan ndeteksi otomatis PID mana aja yang didukung mobil saya. Dan server.js yang nyajiin HTTP serta nge-push nilai ke browser lewat SSE, alias Server-Sent Events, jadi angka di layar update realtime tanpa polling.

Dashboard-nya murni web statis. Semua library saya bundle lokal biar tetep jalan tanpa internet di dalem mobil. Buat drag dan resize saya pakai GridStack.js. Buat gauge pakai canvas-gauges. Buat grafik live pakai uPlot. Saya jadiin PWA juga, biar bisa "Add to Home screen" dan tampil fullscreen kayak app beneran.

Soal tampilan, saya punya satu prinsip. Angka gede yang jelas, bukan dial speedometer yang cantik tapi susah dibaca sekilas pas nyetir. Jadi widget default-nya kartu numerik gede, isinya nilai, mini-bar, min/max, dan zona warna. Semuanya disusun jadi grid padat yang bisa saya seret sesuka hati pakai GridStack. Mode edit dikunci default biar aman.

Saya jalanin mode simulasi.

cd ~/obd-dash
./start.sh --sim
Enter fullscreen mode Exit fullscreen mode

Buka http://127.0.0.1:8080 di Chrome, dan angka-angka mulai ngalir. Idle, naik pas akselerasi, turun pas cruise. Saya seret-seret widget, atur ukurannya, susun tab. Dashboard-nya idup. Tapi ini masih data palsu. Ujian beneran ada di mobil.

Momen kebenaran, nyobain di mobil sungguhan

Saya bawa tablet ke mobil, colok dongle ELM327 ke port OBD-II, puter kontak ke ON biar adapter ada setrumnya. Terus dari Termux saya jalanin.

cd ~/obd-dash
./start.sh        # target default dongle "OBDII"
Enter fullscreen mode Exit fullscreen mode

Di terminal, log bergulir. Bridge start sebagai uid 2000, setBluetoothServiceManager OK, adapter dapet, connect ke OBDII, terus muncul RFCOMM CONNECTED. Dan kali ini, berkat strong-ref tadi, nggak putus.

Saya buka dashboard-nya. Beberapa detik server negosiasi PID sama ECU. Terus badge di pojok berubah dari "menunggu" jadi "LIVE", dan angka-angka di kartu gede itu bukan simulasi lagi. RPM idle asli mobil saya. Suhu coolant naik pelan pas mesin manas. Saya injek gas dikit, dan RPM-nya melonjak di layar, realtime, tanpa lag.

Sebuah tablet, tanpa satu aplikasi pun keinstal, baca ECU mobil lewat Bluetooth dari dalem shell. Sekali connect, nol drop, datanya ngalir penuh. Di momen ini tablet itu akhirnya kerasa kayak komputer beneran.

Foto Lain 1
Foto Lain 2
Foto Lain 3

Keterusan, drag timer dan dyno virtual

Begitu data RPM sama kecepatan udah realtime dan presisi, sayang kalau berhenti di situ. Jadi saya nambahin dua fitur yang sebenernya di luar rencana awal.

Yang pertama drag timer, jalan di sisi server lewat drag.js. Cara pakainya pencet ARM, terus launch dari diem. Timer mulai otomatis begitu mobil gerak. Dia ngitung jarak dari kecepatan, dan nyatet split 60ft, 0 sampai 100 km/h, 201 meter alias 1/8 mil, dan 402 meter alias 1/4 mil, lengkap sama trap speed. Kelar di 402 meter, run-nya otomatis kesimpen. Pas run jalan, server fokus polling RPM dan speed doang demi resolusi maksimum. Ini contoh run nyata pertama yang kesimpen.

{ "peakSpeed":107, "distance":403.5, "duration":18.77,
  "splits": { "60ft":{"t":2.19,"v":36}, "0-100kmh":{"t":10.8,"v":100},
              "201m":{"t":11.03,"v":101}, "402m":{"t":18.71,"v":74} } }
Enter fullscreen mode Exit fullscreen mode

Yang kedua dyno virtual, jalan di sisi client. Dari trace sebuah run, dia ngitung HP dan torsi terhadap RPM pakai metode inertial. Rumusnya gaya sama dengan massa kali percepatan, ditambah hambatan angin dan gelinding. Saya tinggal set massa kendaraan dan rugi drivetrain di menu setting. Jadi dari satu launch saya dapet slip dragstrip sekaligus kurva dyno, tanpa alat tambahan apa pun.

Penutup

Sekarang tablet itu nempel di mobil dan nampilin data mesin realtime dengan jelas, semuanya jalan dari shell dan offline. Kalau dihitung, kodenya nggak banyak. Bridge sekitar 570 baris Java reflection, server Node sekitar 750 baris, dashboard web sekitar 600 baris.

Ada tiga hal yang paling nempel buat saya dari proyek ini.

Pertama, Android itu beda dunia dari Linux soal Bluetooth. Lupain BlueZ, jalannya lewat API Java Android, dan app_process yang jadi kunci buat manggilnya dari shell.

Kedua, uid itu penting. Insting buat ngejalanin semua sebagai root malah salah, karena BLUETOOTH_CONNECT justru nempel di uid 2000, bukan di root. Kadang punya hak lebih sedikit malah yang bener.

Ketiga, dan ini yang paling berkesan, putus yang terlalu rapi itu tanda buat curigain garbage collector, bukan hardware. Satu strong-reference di tempat yang tepat itu pembeda antara "dongle ini rusak, buang aja" dan koneksi yang stabil seharian.

Pertanyaan iseng di awal, masa tablet sekuat ini cuma buat gitu-gitu, sekarang udah kejawab. Cuma buat ngejawabnya saya harus lewat enam jebakan reflection, satu garbage collector yang nyamar jadi dongle rusak, dan satu clone yang sesekali minta dicabut-colok. Tapi pas RPM asli melonjak realtime di layar, semuanya kerasa sepadan.

Dan kalau dipikir lagi, semua ini berdiri di atas satu keputusan kecil di awal: nge-root tabletnya. Dari situ tabletnya bebas dipakai sampai mentok, dan asisten codernya pun ikut bebas bantuin sampai ke akar sistem. Buat saya, ya begitu cara ngeluarin potensi asli sebuah tablet mahal.

Top comments (0)