Arabama taktığım Android teyp, Peugeot 208'in kapı uyarısını, yakıt tüketimini ve menzilini biliyordu ama kimseye söylemiyordu. PSA grubu CAN verisini standart yollardan vermediği için, kendi yazdığım MyWay uygulamasına bu bilgileri taşımak tam iki haftamı aldı. Root yok, adb yok, belge yok. Bu yazı o iki haftada öğrendiklerimin hikâyesi.
tersine mühendislik
veri kaynağı
uyarı kodu
ya da adb
1. Önce karşımdakini tanımak
İlk iş, teybin gerçekte ne olduğunu bulmaktı. İnternetteki Çin malı teyplerin çoğu FYT ya da Microntek platformunda çalışıyor ve onlar için yazılmış bir sürü rehber var. Benimki ikisi de değildi: bir TopWay (TW) çıktı. Yani hazır bilgi neredeyse hiç yoktu.
| Özellik | Değer |
|---|---|
| Platform | TopWay (TW), FYT ya da Microntek değil |
| İşlemci | Allwinner T3 (dört çekirdek), sun8iw11p1, ro.board.platform=t3 |
| Android | 10 (çekirdek imajı t3_p1-eng 8.1.0 ... test-keys) |
| RAM | 2 GB |
| Üretici uygulamaları | com.tw.* |
| CAN kutusu | Raise, PSA protokolü (com.tw.car içinde PsaRaiseSettingsActivity) |
Üreticinin uygulamaları
Teypteki her şeyi com.tw.* paketleri yönetiyor. Hangisinin ne yaptığını tek tek açıp bakarak çıkardım:
| Paket | Görevi | Konumu |
|---|---|---|
com.tw.service | Ana sistem servisi, MCU mesajları, teybin kendi kapı göstergesi | /system/priv-app |
com.tw.car | CAN kutusu uygulaması; PSA uyarıları ve yol bilgisayarı ekranı | /data/app |
com.tw.carinfoservice | CAN verisini diğer uygulamalara açan servis (AIDL) | /data/app |
com.tw.carchoose | Araç / CAN protokolü seçimi | /data/app |
com.tw.bt | Bluetooth telefon ve müzik | /system/priv-app |
com.tw.music, radio, video, eq, auxin | Medya uygulamaları | /system/priv-app |
com.tw.core | Bulut / hesap (cardoor.cn, dofuncar.com) | /data/app |
com.zjinnova.zlink | CarPlay / Android Auto (ZLink) | /data/app |
com.dofun.variety | Tema uygulaması | /data/app |
Fark edilmesi gereken detay
com.tw.* uygulamalarının hepsi android.uid.system kimliğiyle çalışıyor, yani üreticinin imza anahtarıyla imzalı. /data/app altında duranlar da öyle. Benim uygulamam bu yetkiye hiçbir zaman sahip olamayacaktı. Bu yüzden asıl soru, sistem kimliği gerektirmeyen bir kapı bulmaktı.
2. Root, adb ve erişim: kapalı kapılar
Bu tür işlerde ilk akla gelen araçlar root ve adb'dir. Üçü de kapalı çıktı:
- Root uygulamalara kapalı. Uygulamadan
suçağrılıncasu: not alloweddönüyor. Magisk ya da SuperSU yok; root sadece adb kabuğuna açık. MyWay'deki "Root ile tüm izinleri ver" seçeneği bu teypte işe yaramıyor. - Kablosuz hata ayıklama yok. Android 10'da bu menü bulunmuyor, 5555 portu da kapalı.
- USB ile adb olmuyor. Teybin USB girişi host modunda çalışıyor; Mac'e bağlayınca Mac teybi hiç görmüyor.
Kural belli oldu
Ne yapacaksam, root ve adb olmadan, sıradan bir uygulamanın erişebildiği yollarla yapacaktım.
APK'yı teybe taşımak
adb olmayınca her yeni sürümü teybe kurmak bile ayrı bir dert. En basit çözüm şu oldu: Mac ile teybi telefonun hotspot'una bağlayıp Mac'te küçük bir HTTP sunucusu açmak.
# Mac: 172.20.10.12, teyp: 172.20.10.3 (aynı hotspot)
cd <apk klasörü> && python3 -m http.server 8000 --bind 172.20.10.12
Ardından teypteki tarayıcıdan http://172.20.10.12:8000/<dosya>.apk adresini açıp kurmak yetiyor. İş bitince sunucuyu Ctrl+C ile kapatıyorum.
Ters yön: teypteki uygulamaları Mac'e almak
Üreticinin kodunu okuyabilmek için APK'lara ihtiyacım vardı. Bunun için MyWay'e küçük bir sunucu ekledim: Ayarlar → "Üretici uygulamalarını bilgisayara aç (Wi-Fi)". Pencere açık kaldığı sürece:
http://<teyp-ip>:8080/tüm paketleri ve APK yollarını listeliyor.http://<teyp-ip>:8080/apk/<paket>o paketin APK'sını indiriyor.
Gelen APK'ları Mac'te jadx ile açtım. Sunucu sadece pencere açıkken çalışıyor ve sadece APK veriyor. Teypteki okunabilir bütün dosyaları ağa açan bir sürümünü de denedim ama güvenlik riski yüzünden vazgeçtim. Kodu: util/ApkServer.kt
3. Veri tek bir yerden gelmiyor
İki haftanın en önemli bulgusu bu oldu. Teyp araç verisini tek bir kanaldan dağıtmıyor; her biri farklı şeyler taşıyan üç ayrı yol var:
AIDL servisi
Genel CAN verisi: devir, far, akü. Peugeot'da kapı bilgisi yok.
android.tw.john.TWUtilMCU kanalı
Ham CAN kutusu mesajları: yol bilgisayarı ve uyarı listesi.
com.tw.bt (TXZ)Bluetooth müzik
Şarkı adı ve sanatçı, sesli asistan yayınları üzerinden.
3a. com.tw.carinfoservice: AIDL servisi
İlk bulduğum ve en "resmî" görünen yol bu. Teyp, CAN verisini diğer uygulamalarla paylaşmak için bir AIDL servisi sunuyor. Güzel tarafı şu: gerekli izin com.tw.carinfoservice.permission.USE_AIDL, koruma seviyesi normal. Yani her uygulama bu izni alabiliyor.
val intent = Intent("com.tw.carinfoservice.CarService.Bind")
.setPackage("com.tw.carinfoservice")
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
Daha da iyisi, arayüzün orijinal .aidl dosyaları APK'nın içinde duruyordu:
com/tw/carinfoservice/CarServiceAidl.aidl(64 metot)com/tw/carinfoservice/CarServiceCallBack.aidl(66 callback)
Bu dosyaları olduğu gibi app/src/main/aidl/ altına kopyaladım. Burada dikkat edilmesi gereken tek şey metot sırasının değişmemesi; AIDL metotları sıra numarasıyla çağrılıyor. Teybin uygulaması güncellenirse sayıların yeniden kontrol edilmesi gerekiyor.
Kâğıt üzerinde servis çok şey sunuyor: kapılar, yakıt, menzil, anlık ve ortalama tüketim, devir, vites, sıcaklıklar, akü, lastik basıncı, kemerler, el freni, far. Ama bu araçta gerçekten gelenler çok az:
- Devir (ör. 745 rpm)
- Far açık / kapalı
- Akü (hep 0 V, yani kullanışsız)
getDoor()ise -1 döndürüyor. Peugeot'da kapı bilgisi bu servise hiç gelmiyor.
Kritik tuzak: unRegister'ı asla çağırmayın
unRegisterCarServiceCallBack çağrıldığında servis, araç işlemcisine write(1291, 0, 0) yazıyor. Bu da hız ve devir akışını herkes için kapatıyor; teybin kendi göstergeleri bile etkilenebiliyor. Bu yüzden MyWay bağlantıyı süreç boyunca açık tutuyor ve bu metodu hiç çağırmıyor. pauseData ise sunucu tarafında hiçbir şey yapmıyor.
3b. android.tw.john.TWUtil: araç işlemcisinin kanalı
Kapı bilgisi AIDL'de yoksa, teybin kendi CAN uygulaması (com.tw.car) onu nereden alıyordu? Kodunu izleyince TWUtil adında bir sınıfa ulaştım. Bu sınıf APK'larda değil, doğrudan framework'te duruyor. Reflection ile açmayı denedim ve open() 0 döndü: kanal uygulamalara da açıktı. Üretici uygulamaları sistem kimliğiyle çalışsa da bu kanal kısıtlanmamış.
val cls = Class.forName("android.tw.john.TWUtil")
val tw = cls.getConstructor(Int::class.javaPrimitiveType).newInstance(16)
cls.getMethod("open", ShortArray::class.java)
.invoke(tw, shortArrayOf(1281)) // 0 = başarılı
cls.getMethod("start").invoke(tw)
cls.getMethod("addHandler", String::class.java, Handler::class.java)
.invoke(tw, "MyWay", handler)
Handler'a düşen her mesajda what kanal kimliğini, arg1 komutu, arg2 uzunluğu, obj ise ham byte[] veriyi taşıyor. 1281 (0x0501) CAN kutusu verisinin kanalı; saniyede yaklaşık 6 mesaj geliyor.
Sadece dinle
MyWay bu kanala asla write yapmıyor. Yanlış bir yazma, araç işlemcisine komut göndermek anlamına geliyor.
PSA (Raise) 1281 komutları
Baytların anlamını tahmin etmedim; com.tw.car'ın kendi kodundan çıkardım:
| arg1 | Anlamı | Bayt düzeni |
|---|---|---|
| 51 | Anlık yol bilgisayarı | [0-1] anlık tüketim ×10 L/100 (FFFF = araç duruyor) · [2-3] menzil km · [4-5] mesafe · [6-8] saat:dk:sn · [9-10] hız ×100 |
| 52 | Yolculuk 1 | [0-1] ortalama ×10 L/100 · [2-3] ortalama hız km/s · [4-5] mesafe km |
| 53 | Yolculuk 2 | 52 ile aynı düzen |
| 58 | Uyarı listesi | [0] uyarı sayısı · [1..n] uyarı kodları |
| 59 | Metin mesajı (GBK) | [0]==1 ise gösterilecek metin |
| 125, 39, 54, 56, 61, 65, 67, 78, 79 | Görülüyor, henüz çözülmedi | — |
51, 52 ve 53 yaklaşık 6,5 saniyede bir geliyor. Gerçek log'dan alınmış mesajların nasıl çözüldüğünü aşağıda görebilirsiniz:
PSA uyarı kodları
58 numaralı mesajdaki kodlar, com.tw.car içindeki raise_check_list dizisindeki sıra numaraları (toplam 136 madde). Türkçe hallerini res/values/psa_warnings.xml dosyasına kopyaladım. Kapıyla ilgili olanlar şunlar:
| Kod | Metin |
|---|---|
| 17 | Kapı, bagaj, kaput, arka cam veya yakıt deposu kapalı değil |
| 83 | Bagaj kapağı kapalı değil |
| 85 | Sağ arka kapı kapalı değil |
| 86 | Sol arka kapı kapalı değil |
| 87 | Sağ ön kapı kapalı değil |
| 88 | Sol ön kapı kapalı değil |
| 109 | Çarpışma riski algılama hatası (kaput kapalı değil) |
| 119 | Motor arızası: Lütfen aracı onarın |
Aşağıdaki simülasyon, gerçek log'daki sırayı oynatıyor: sağ ön kapı açılınca uyarı listesine 87 ekleniyor, kapanınca listede yalnızca 119 kalıyor.
Teybin kendi kapı göstergesi neden çalışmıyordu?
com.tw.service'teki kapı göstergesi kapı bitlerini farklı bir CAN komutundan bekliyor. Bu CAN kutusu ise kapı bilgisini sadece uyarı listesinin içinde gönderiyor. com.tw.car da uyarı metnini sadece kendi belleğinde tutuyor; ne yayınlıyor ne log'a yazıyor. Yani logcat'e erişimim olsaydı bile bu bilgiyi orada bulamayacaktım.
Kodu: car/PsaCan.kt, util/Probe.kt (TwUtilProbe)
3c. Bluetooth müzik: com.tw.bt ve TXZ yayınları
Telefondan Bluetooth ile müzik çalarken şarkı adını göstermek basit bir iş gibi duruyordu. Değilmiş. Teybin Bluetooth uygulaması MediaSession açmıyor, o yüzden bildirim erişimiyle bu bilgiye ulaşılamıyor.
Çözüm, teypte gömülü TXZ sesli asistan protokolünde çıktı. Asistan, şarkı bilgisini yayınlarla soruyor ve gönderen için hiçbir izin kontrolü yapılmıyor:
| Yön | Yayın | Ek alanlar |
|---|---|---|
| İstek | com.txznet.launcher.btmusic.action.send | action: 8 = bilgi iste, 4/5 = çal/duraklat, 6/7 = sonraki/önceki (tahmin) |
| Cevap | com.txznet.launcher.btmusic.action.recv | title, artist, picUrl (hep boş) |
// Şarkı bilgisini iste; cevap ...btmusic.action.recv ile gelir
context.sendBroadcast(
Intent("com.txznet.launcher.btmusic.action.send")
.putExtra("action", 8)
)
MyWay, uygulama açıkken ve başka bir şey çalmıyorken 3 saniyede bir bu soruyu soruyor. Birkaç ayrıntı da var:
- Telefon başlık göndermezse
com.tw.btalanlara kendi "Bilinmeyen" metnini yazıyor; MyWay bunu boş sayıyor. - Teyp çalma durumunu (çalıyor / duraklatıldı) bildirmiyor.
- Kapak resmi bu kanaldan hiç gelmiyor. MyWay onu iTunes aramasıyla buluyor: önce TR, sonra US mağazası.
Diğer TXZ yayınları: com.txznet.launcher.bt.action.recv (bağlantı durumu) ve com.txznet.launcher.call.action.recv (arama). Kodu: media/TwBtMusic.kt, media/MediaHub.kt, media/ArtworkFetcher.kt
4. Denenen ama işe yaramayan yollar
Bu bölüm yazının en kısa kısmı ama iki haftanın çoğu burada geçti:
com.android.music.metachanged, A2DP sink ve benzerlerinin hiçbiri gelmedi.su: not allowedgetDoor() = -1; Peugeot'da kapı bu servise gelmiyor.com.tw.carchoose.broadcast.UART); kapı bilgisi yayınla gelmiyor.5. Bütün bunlar uygulamada neye dönüştü?
| Özellik | Veri kaynağı | Dosya |
|---|---|---|
| Kapı açık bandı (harita altı) | PSA 58 + carinfoservice | MainActivity.renderCarAlert |
| Her ekranda uyarı pop-up'ı (Ayarlar'dan) | PSA 58 | ui/WarningOverlay.kt |
| Gerçek tüketimle HARCANAN / TUTAR | PSA 51 + devir | eco/FuelMeter.kt, DrivingTrackerService |
| ARAÇ ORTALAMASI, anlık L/100, menzil | PSA 51/52 | MainActivity.renderPsaTrip |
| Araç bilgileri penceresi | carinfoservice + PSA uyarıları | CockpitDialogs.showVehicleInfo |
| BT şarkı adı ve kontrol | TXZ yayınları | media/TwBtMusic.kt |
| BT / kapı teşhisi, LİNK AL + QR | Hepsi | CockpitDialogs.runProbe, util/Probe.kt, util/Diagnostics.kt |
| Motorin fiyatı | Opet API | net/FuelPriceFetcher.kt |
Yakıt hesabı: FuelMeter
Önceden harcanan yakıtı sadece mesafe × baz tüketim ile tahmin ediyordum. Gerçek anlık tüketim elime geçince hesap üç kademeli hale geldi. Her GPS adımında sırayla şunlara bakılıyor:
Hareket halinde ve veri taze
Son 20 saniye içinde anlık tüketim geldiyse: anlık L/100 × mesafe.
Araç duruyor, motor çalışıyor
Devir 300'ün üstündeyse: 0,5 L/sa × (devir / 800) × süre. Bu bir tahmin; 1.4 HDi rölanti tüketimine göre varsayıldı.
Hiçbiri yok
Eski davranışa dönülüyor: mesafe × baz tüketim.
"Yakıt aldım" sayacı artık litre olarak (fuel_used_l) saklanıyor. Eski kayıtlar ilk açılışta mesafe × baz tüketim ile başlatılıyor.
Opet fiyatı
Tutarı hesaplamak için güncel motorin fiyatını Opet'in API'sinden alıyorum:
GET https://api.opet.com.tr/api/fuelprices/prices?ProvinceCode=1 // 1 = Adana
// MERKEZ ilçesi, A128 = Motorin EcoForce
Uç noktayı sinanerdinc/opet projesinden öğrendim.
6. 2 GB RAM'de yaşamak
Teyp aynı anda harita, müzik ve CarPlay çalıştırırken MyWay'in sistemi yavaşlatmaması gerekiyordu. Saniyede 6 MCU mesajı ve sürekli gelen AIDL callback'leri için şu kuralları koydum:
- Araç işlemcisinden gelen ilgisiz komutlar ilk satırda atlanıyor; aynı içerik tekrar gelirse yeni nesne üretilmiyor.
CarInfocallback'leri binder thread'inde sadece alanlara yazıyor. Ekran için görüntü saniyede en fazla bir kez ve sadece dinleyen varsa üretiliyor.- Uyarı pop-up'ının görünümü sadece ekrandayken var; gizlenince bırakılıyor.
- Teşhis araçları (tarama, sunucu, TWUtil dinleme) yalnızca butona basınca çalışıyor.
- Kapak resimleri 192 px'e küçültülüp önbellekte tutuluyor (en fazla 40 tane).
7. Geliştirme notları
- Arabada minified release APK, emülatörde debug çalışıyor. R8, yalnızca reflection ile kullanılan sınıfları silebiliyor. AIDL sınıfları için
proguard-rules.proiçinde-keep class com.tw.carinfoservice.** { *; }kuralı var. - Derleme: Sistemde Java yok, Android Studio'nun JDK'sı kullanılıyor.
# Release derleme
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
./gradlew :app:assembleRelease
# APK açma (bazı metotlar --show-bad-code olmadan boş çıkıyor)
jadx --no-res --show-bad-code -d <çıktı> <apk>
# String ve dizi kaynaklarının dökümü
aapt2 dump resources <apk>
8. Açık kalanlar
Her şey çözülmedi. Doğrulanması gereken varsayımlar şunlar:
- Bluetooth'ta 6 = sonraki, 7 = önceki varsayımı.
- Emniyet kemeri bayrağının anlamı (
true= takılı değil kabul edildi). Bu araçta hiç gelmiyor da olabilir. - Rölanti tüketim katsayısı (800 rpm'de 0,5 L/sa). Depo dolumlarıyla karşılaştırılmalı.
- 1281 kanalındaki 125, 39, 54, 56, 61, 65, 67, 78, 79 komutları. Karşılıkları
com.tw.carkodunda (HandlerC0051d,HandlerC0057k) bulunabilir. - Uyarı kodu 119 (motor arızası) sürekli geliyor. Araçta gerçekten bir arıza olup olmadığına bakılmalı.
Sonuç
Bu iki haftadan çıkardığım en büyük ders şu: kapalı görünen bir sistemde bile kapılar genelde vardır, sadece beklediğiniz yerde değildir. Ne root ne adb işe yaradı. Sonunda işi çözen şeyler, APK'nın içinde unutulmuş .aidl dosyaları, reflection ile açılan bir framework sınıfı ve izin kontrolü olmayan bir sesli asistan yayını oldu.
Bir başka ders de üreticinin kodunu okumanın tahmin yürütmekten her zaman hızlı olduğu. PSA uyarı listesindeki 136 kodun anlamı da, 51 numaralı mesajın bayt düzeni de zaten com.tw.car'ın içinde yazıyordu.
Aynı teybe ya da başka bir PSA aracına sahipseniz ve benzer bir şey deniyorsanız, yukarıdaki tuzaklar size birkaç gün kazandırabilir. Özellikle unRegisterCarServiceCallBack'e dikkat edin.