App als Schlüsselersatz #10
Labels
No labels
blocked
claude
in-progress
needs-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
aiways/AiwaysApp#10
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ein Community-Mitglied hat mich darauf aufmerksam gemacht, dass in der originalen Aiways-App eine Funktion vorhanden war, mit der man sein Smartphone als Schlüssel registrieren konnte. Können wir aus dem Decompilat rauslesen, wie das implementiert wurde?
Ja — vollständig. Und mehr als das: die Funktion ist nicht nur aus dem Dekompilat lesbar, wir haben sie schon reverse-engineered, gegen die Firmware verifiziert, am echten Auto erfolgreich getestet (14.08.,
resCode 0) und in unsere App portiert (Issue #8). Hier die ganze Kette.Was „Handy als Schlüssel" bei Aiways technisch war
Ein RSA-Schlüsselpaar pro Handy, das das Auto per Bluetooth per Challenge-Response prüft. Kein NFC, kein UWB — reines BLE mit App-seitiger Signatur.
Die Bausteine im Dekompilat (
com.aiways.near.communication.ble+…car.control):BtKey(ble/client/BtKey.java) — das Schlüssel-Objekt:id(btkeyId),userType(Owner vs. geteilter Nutzer),mac(die BLE-MAC der TBox),randomKey(AES-Key),pubK,priK(RSA-Paar),hWType.btkey-Retrofit-Interface):POST btkey/getBtkey,btkey/manualCreateBtkey,btkey/resetBtkey. Die Antwort trägtbtkeyId, keyType, tboxBtMac, vin, beginTime/expireTimeund einkey-Objekt mitpublicKey/privateKey/randomKey. Heisst: das RSA-Paar wurde vom Aiways-Server erzeugt, ans Handy geliefert und dort in SharedPreferenceseu_aiways_share_datauntercache_bt_key_keygecacht. Owner-Keys sind permanent, geteilte Keys habenexpireTime— das war die „Schlüssel teilen"-Funktion./usrdata/pem/key_<btkeyId>(+key_<btkeyId>_expiretime). Asymmetrisch: App signiert, TBox verifiziert — in der Key-Datei steckt kein Geheimnis.Der BLE-Handshake (firmware-verifiziert):
0x2B70, Notify0x2B71. Längen-Präfix 4 Byte little-endian, Write mit Response, Chunks sequentiell.AT A001 1.0 <tid> <date> <body> <sig>, mit festem Login-Key AES-CBC verschlüsselt;sig = RSA_privEncrypt( MD5( body+" "+aesKey+" "+tid ) ). Die TBox verifiziert gegenkey_<btkeyId>.{"rk":<hex>,"encryptionMethod":"AES-128","btkeyId":..,"resCode":"0"}.rk= der mit unserem Public-Key RSA-verschlüsselte Session-AES-Key (16 Byte, UTC-basiert). Mit dem Private-Key entschlüsseln.0123456789ABCDEF.AES/CBC/PKCS5Padding+RSA/NONE/PKCS1Padding(BouncyCastle), MD5-Prüfsumme. Fehlerpfade:errCode 1= keinkey_<btkeyId>gefunden,errCode 2= Signatur ungültig.Was das für uns bedeutet — die ehrliche Einordnung
lib/ble/protocol.dart(A001/B001),crypto.dart(AES/RSA/MD5),rsa_key.dart,community_key.dart. Das ist Issue #8./usrdata/pem/key_<id>(+ac_public_key.pem). Damit fällt der komplette OEM-Server aus dem Pfad. Die Community nutzt dafür einen festen, permanenten SchlüsselOPENAIWAYS— der liegt auf Timos TBox bereits korrekt (key_OPENAIWAYS== unser Public-Key ✓).resCode 0, Session-Key empfangen. Der einzige Stolperstein war ein Vorbesitzer-ac_public_key.pem, den wir überschrieben haben.Kurz
Ja, aus dem Dekompilat ist exakt ablesbar, wie es implementiert war — RSA-Keypair pro Handy, Cloud-Provisionierung, BLE-A001/B001-Handshake, alles bis auf die Byte-Ebene dokumentiert (siehe Research
04-protokolle/aichi-app-key-fund.md,aichi-a001-handshake.md,aichi-auth-erfolg.md). Und wir haben es nicht nur gelesen, sondern nachgebaut und am Auto zum Laufen gebracht. Was in der App-UI noch fehlt, ist der Feinschliff um diesen BLE-Kern (Onboarding „Handy als Schlüssel", Status/Fehlermeldungen, geteilte Keys) — das ist reine App-Arbeit, kein Krypto-Risiko mehr.Sag, ob Du daraus eine sichtbare „Schlüssel"-Funktion in der App gebaut haben willst (Auf/Zu direkt vom Home-Screen, Verbindungsstatus, Key-Verwaltung) — dann lege ich ein Issue an und setze auf dem #8-Stack auf.
Okay, bevor wir da loslegen. Wie muss ich das verstehen? Wenn ich ohne Schlüssel ins Auto steige, unsere App aufmache (die sich dann verbindet) kann ich losfahren?
Gute, genau die richtige Frage — kurz und ehrlich, getrennt nach Einsteigen und Losfahren:
Einsteigen — ja, bewiesen
App auf → BLE-Scan findet die TBox (
AICHI_…) → A001-Auth (unserOPENAIWAYS-Key) → B001DoorUnlock(type 1) → Türen auf. Der Auth-Handshake lief am echten Auto mitresCode 0, Steuerbefehle (Blinker/Hupe) sind am Fahrzeug getestet. Das Auf/Zu ist damit im Prinzip vorhanden — genau der Kern aus #8.Losfahren — konzeptionell ja, aber der Startteil ist noch NICHT am Auto verifiziert
Der Original-Handyschlüssel konnte mehr als nur Türen: im Befehlssatz steckt
ReleaseStartAuth(B001 type 13) — das ist die Startfreigabe / das Lösen der Wegfahrsperre. Genau darüber meldet das Handy der TBox „Fahrer autorisiert", danach Startknopf drücken → fahren. So war „Handy als Schlüssel" beim OEM gedacht: kein physischer Schlüssel und keine Keycard nötig.Was fehlt, ehrlich:
type 13(Startfreigabe) haben wir noch nicht ausgelöst.type 13ist im Dekompilat bekannt, aber noch nicht verdrahtet.Also, dein Szenario konkret
Einsteigen (Tür per App auf): ja, das können wir schon. Danach wortwörtlich „losfahren" hängt an genau einem noch offenen Baustein — der Startfreigabe
type 13. Der Befehl liegt vor, ist aber am Auto noch nicht bestätigt und in der App noch nicht drin.Vorschlag für den nächsten Schritt (auf dem #8-Stack):
type 13in den BLE-Client aufnehmen und am Auto testen (resCode prüfen, dann Startknopf) — erst wenn das grün ist, wissen wir sicher, dass „losfahren" real geht.Sag einfach „ja, bau die Startfreigabe + Test" — dann lege ich das als Issue an und setze es um. Testen am Auto brauchst du physisch dabei (BLE-Reichweite), das koordinieren wir dann.
Ja lass uns das ausprobieren, baue den Start Knopf erstmal in die Bluetooth Einstellungen der App (Startfreigabe-Test)
Gebaut und deployt. ✅ CI grün (analyze/gate/release/deploy-web), Web live unter http://192.168.1.85/.
Was drin ist
BleCommandType.releaseStartAuth = '13'(ReleaseStartAuth — Wegfahrsperre lösen) inlib/ble/protocol.dart.lib/screens/ble_screen.dart): Erklärtext, Bestätigungsdialog, ein Knopf „Startfreigabe senden“, und darunter das Ergebnis (resCode 0 = grün mit Hinweis „Startknopf drücken“, sonst Fehlermeldung + Detail).Wie es sendet
Bewusst am
CommandQueue/Backend vorbei — direkt über den rohen BLE-Pfad (BleLink().send('13')). Die Startfreigabe hat keine Backend-Entsprechung, und der Test soll ausschließlich den Bluetooth-Weg prüfen (Auth → B001 type 13). Statusmeldungen (Suche/Verbinde/Authentifiziere/Sende) laufen auf dem Knopf mit, alles landet zusätzlich im Bluetooth-Protokoll zum Weiterschicken.Zum Testen am Auto (APK aus dem Release installieren, BLE gibt's nur auf Android/iOS):
Ehrliche Einordnung bleibt:
type 13ist im Dekompilat bekannt, aber am Fahrzeug noch nicht verifiziert — genau das ist der Zweck dieses Knopfes. Sag Bescheid, wann Du am Auto testen kannst; falls es geht, baue ich als Nächstes die sichtbare „Losfahren“-Aktion (Home-Screen) auf diesem Stack.Also es hat funktioniert! Aber bitte noch nicht mit der Losfahren Taste auf dem Homescreen starten.
Das Verhalten ist denke ich noch nicht ganz verstanden. Was habe ich getan?
Auto mit unserer App entriegelt, dann reingesetzt und Bremse betätigt (Start). Meldung im Auto: Kein Schlüssel. Dann in der App die Start-Freigabe Test gedrückt. Wieder Bremse betätigt. Auto fährt ! So weit so gut.
Allerdings hat der BLE dann keine weiteren Befehle verarbeitet. Wollte aussteigen, abschließen > nichts. Fenster runter > nichts.
Ins Haus gegangen, Schlüssel geholt, verriegeln auf dem Schlüssel gedrückt > nichts.
Wir sollten nochmal nachschauen, wie dieses BLE Interface in der originalen App genau bedient wird und unsere Fehler identifizieren.
Nachgeschaut, Fehler gefunden und behoben. ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.38 im Release.
Wie die Original-App das BLE-Interface bedient
Ich habe den Ablauf im Dekompilat der Original-App durchgezogen (
com.aiways.near.communication.ble,client/rc/*). Der Kern, den wir übersehen hatten: auf jeden Steuerbefehl kommen ZWEI Nachrichten zurück, nicht eine.B001/FunctionResponseMessagemitresCode. Das heißt nur „Frame angenommen", nicht „ausgeführt".resCode != 0= sofortige Ablehnung (u. a.-5= „es ist bereits eine App mit der TBox verbunden",-4= Dienst abgelaufen).B002/AsyncMessagemitresult, das etwas später asynchron eintrifft.result 0= wirklich ausgeführt. Sonst trägt einreason-Code den Grund.Die Original-App (
client/rc/h.java) wertet einen Befehl erst als erfolgreich, wenn das B002 mitresult 0da ist — die Quittung prüft sie nur auf sofortige Ablehnung. Und sie ordnet Antworten pertiddem Befehl zu.Die
reason-Tabelle (protocol/d.java) ist Gold für die Diagnose. Auszug:2= Auto nicht ausgeschaltet,4/7= Tür offen,6= Fahrzeug fährt,8= Schlüssel im Fahrzeug,11= Tür nicht verriegelt,12= Fenster offen,13= Bremse getreten,33= schon verriegelt,19= Auth fehlgeschlagen.Unser Fehler
Wir lasen nur den ersten Frame (die Quittung) und werteten
resCode 0schon als „ausgeführt" — das B002 mit dem echten Ergebnis haben wir nie gelesen. Folge: ein Befehl, den das Auto annimmt aber blockiert, sah bei uns stumm tot aus, weil der Grund im ungelesenen B002 steckte. Genau dein Bild nach dem Losfahren.Was am ehesten passiert ist (dein Ablauf)
Dein Verriegeln/Fenster nach der Fahrt lief mit hoher Wahrscheinlichkeit auf
reason 2— „Auto ist nicht ausgeschaltet". Nach der Startfreigabe war das Auto fahrbereit/an; ein laufendes Auto verriegelt weder per BLE noch per Funkschlüssel — deshalb ging auch der physische Schlüssel nicht. Kein Krypto-/Verbindungsdefekt, sondern eine Fahrzeugsperre, deren Grund wir dir bloß nicht angezeigt haben. Zum Abschließen muss das Auto erst aus (Startknopf → aus), dann verriegeln.Sicher wissen wir das erst mit dem nächsten Test — genau dafür zeigt die App jetzt den Grund.
Behoben (v1.0.38)
result 0als ausgeführt; sonst zeigen wir den Klartext-Grund („Auto ist nicht ausgeschaltet", „Bremspedal getreten", …) statt „nichts".-5„schon eine App verbunden")._FrameReaderverschluckt nach einem Timeout keinen nachrückenden Frame mehr.APK zum Testen (arm64): https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.38/aiways-app-v1.0.38-arm64-v8a.apk
Nächster Test am Auto (wenn du Zeit hast)
19:25:23.548 Adapter an — Verbindungsversuch
19:25:23.550 Adapter: an
19:25:24.709 Scan startet (8 s) …
19:25:24.771 Adapter an — Verbindungsversuch
19:25:26.779 Fahrzeug gefunden: AICHI_563978 (-79 dBm)
19:25:26.786 Verbinde mit A4:04:50:CE:2C:62 …
19:25:31.950 Kanäle gefunden (2b70/2b71)
19:25:32.018 A001 Auth …
19:25:35.094 Fahrzeug gemerkt: AICHI_563978 A4:04:50:CE:2C:62 — der nächste Aufbau spart den Scan
19:25:35.094 Verbindung steht — Befehle gehen jetzt ohne Vorlauf raus
19:25:35.094 Verbindung getrennt (App im Hintergrund)
19:25:35.108 Adapter: an
19:25:35.108 Verbinde mit A4:04:50:CE:2C:62 …
19:25:36.378 Kanäle gefunden (2b70/2b71)
19:25:36.410 A001 Auth …
19:25:47.989 Direktverbindung zu A4:04:50:CE:2C:62 gescheitert: Zeitüberschreitung am Fahrzeug.
19:25:47.997 Scan startet (8 s) …
19:25:49.936 Fahrzeug gefunden: AICHI_563978 (-79 dBm)
19:25:49.942 Verbinde mit A4:04:50:CE:2C:62 …
19:25:53.953 Adapter: an
19:25:53.953 Verbinde mit A4:04:50:CE:2C:62 …
19:25:56.828 Kanäle gefunden (2b70/2b71)
19:25:56.863 A001 Auth …
19:25:59.695 Verbindung steht — Befehle gehen jetzt ohne Vorlauf raus
19:26:09.829 ── Befehl 2 ──
19:26:09.829 Verbindung steht — B001 direkt
19:26:09.830 B001 2 … (tid 02405336fe9840c4bdd81e3ceecb9f2d)
19:26:12.049 Fremde Antwort (tid 02405336fe9840c4bdd81e3ceecb9f2d����) übersprungen
19:26:17.086 Fremde Antwort (tid 02405336fe9840c4bdd81e3ceecb9f2d����) übersprungen
19:26:20.187 Keine Quittung auf B001
19:26:20.187 Bestehende Verbindung antwortet nicht — neu aufbauen
19:26:20.188 Verbindung getrennt (Verbindung antwortet nicht)
19:26:20.207 Adapter: an
19:26:20.207 Verbinde mit A4:04:50:CE:2C:62 …
19:26:21.272 Kanäle gefunden (2b70/2b71)
19:26:21.314 A001 Auth …
19:26:32.614 Direktverbindung zu A4:04:50:CE:2C:62 gescheitert: Zeitüberschreitung am Fahrzeug.
19:26:32.627 Scan startet (8 s) …
19:26:34.300 Fahrzeug gefunden: AICHI_563978 (-80 dBm)
19:26:34.308 Verbinde mit A4:04:50:CE:2C:62 …
19:26:36.782 Kanäle gefunden (2b70/2b71)
19:26:36.829 A001 Auth …
19:26:39.503 Verbindung steht — Befehle gehen jetzt ohne Vorlauf raus
19:26:39.504 B001 2 … (tid 17997490ea984bdea467f32d35972618)
19:26:41.846 Fremde Antwort (tid 17997490ea984bdea467f32d35972618����) übersprungen
19:26:46.880 Fremde Antwort (tid 17997490ea984bdea467f32d35972618����) übersprungen
19:26:49.950 Keine Quittung auf B001
19:26:49.950 Verbindung getrennt (Fahrzeug antwortet nicht auf den Befehl)
19:26:51.970 Adapter: an
19:26:51.970 Verbinde mit A4:04:50:CE:2C:62 …
19:26:53.073 Kanäle gefunden (2b70/2b71)
19:26:53.104 A001 Auth …
19:27:04.267 Direktverbindung zu A4:04:50:CE:2C:62 gescheitert: Zeitüberschreitung am Fahrzeug.
19:27:04.278 Scan startet (8 s) …
19:27:05.341 Fahrzeug gefunden: AICHI_563978 (-82 dBm)
19:27:05.348 Verbinde mit A4:04:50:CE:2C:62 …
19:27:08.930 Kanäle gefunden (2b70/2b71)
19:27:08.971 A001 Auth …
19:27:11.736 Verbindung steht — Befehle gehen jetzt ohne Vorlauf raus
19:27:30.497 Verbindung getrennt (App im Hintergrund)
19:27:44.605 Adapter an — Verbindungsversuch
19:27:44.911 Adapter: an
19:27:44.911 Verbinde mit A4:04:50:CE:2C:62 …
19:27:50.562 Direktverbindung zu A4:04:50:CE:2C:62 gescheitert: connect: ANDROID_SPECIFIC_ERROR
19:27:50.569 Scan startet (8 s) …
19:27:51.578 Fahrzeug gefunden: AICHI_563978 (-75 dBm)
19:27:51.584 Verbinde mit A4:04:50:CE:2C:62 …
In der Schleife scheint er jetzt zu hängen. Irgendwas übersehen wir. Btw das Auto hat keinen Start/Stopp Knopf. Start mit Schlüssel im Auto über Bremspedal. Aus durch abschließen.
Fehler gefunden und behoben — genau der aus deinem Log. ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.39 im Release.
Was im Log passiert ist
Beide Frames tragen exakt unsere gesendete tid — nur mit einem unsichtbaren Schwanz aus Füllbytes (die Leerzeichen im Log). Wir haben sie trotzdem als „fremd" verworfen → keine Quittung → Verbindung neu aufbauen → genau die Endlosschleife, in der es hängen blieb.
Die Ursache (unser Fehler, seit v1.0.38)
Mit v1.0.38 haben wir Antworten per tid dem Befehl zugeordnet, um verspätete Frames des vorigen Befehls zu überspringen. Die TBox echot unsere 32-Zeichen-tid aber in ein breiteres Feld und füllt hinten mit Füllbytes auf (NUL/Steuerzeichen — kein Leerzeichen
0x20). Weil unser Feld-Splitter nur an0x20trennt, blieb dieser Schwanz am tid-Token kleben:unsere_tid≠unsere_tid␀␀␀␀→ jede echte Antwort galt als fremd. Der am Prototyp verifizierte Java-Pfad filterte gar nicht nach tid — deshalb fiel das erst am Auto auf.Das erklärt auch dein Bild vom Vortag vollständig: nach der Startfreigabe kam auf Verriegeln/Fenster sehr wohl eine Antwort der TBox — wir haben sie nur weggeworfen, statt den Grund (z. B. „Auto nicht ausgeschaltet") zu zeigen. Es war kein Krypto-/Verbindungsdefekt.
Behoben (v1.0.39)
readReply→_cleanTid): das tid-Feld wird auf seinen reinen Hex/UUID-Kern reduziert, Füllbytes fallen weg. Damit erkennt die App ihre eigene Antwort wieder.Nächster Test am Auto
APK v1.0.39 (arm64): https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.39/aiways-app-v1.0.39-arm64-v8a.apk
Zu deiner Notiz „Aus durch Abschließen": das passt zum PEPS-Verhalten. Wenn Verriegeln jetzt am laufenden Auto den Grund
2(„Auto nicht ausgeschaltet") liefert statt still zu hängen, wissen wir, dass die TBox das Verriegeln als Aus-Kommando erst bei stehendem Antrieb akzeptiert — das prüfen wir im nächsten Durchgang.19:52:36.262 Adapter an — Verbindungsversuch
19:52:36.263 Adapter: an
19:52:37.463 Scan startet (8 s) …
19:52:37.534 Adapter an — Verbindungsversuch
19:52:37.560 Adapter an — Verbindungsversuch
19:52:39.433 Fahrzeug gefunden: AICHI_563978 (-77 dBm)
19:52:39.436 Verbinde mit A4:04:50:CE:2C:62 …
19:52:41.974 Kanäle gefunden (2b70/2b71)
19:52:42.019 A001 Auth …
19:52:44.966 Fahrzeug gemerkt: AICHI_563978 A4:04:50:CE:2C:62 — der nächste Aufbau spart den Scan
19:52:44.966 Verbindung steht — Befehle gehen jetzt ohne Vorlauf raus
19:52:44.966 Verbindung getrennt (App im Hintergrund)
19:52:44.981 Adapter: an
19:52:44.981 Verbinde mit A4:04:50:CE:2C:62 …
19:52:46.113 Kanäle gefunden (2b70/2b71)
19:52:46.159 A001 Auth …
19:52:57.502 Direktverbindung zu A4:04:50:CE:2C:62 gescheitert: Zeitüberschreitung am Fahrzeug.
19:52:57.510 Scan startet (8 s) …
19:53:00.771 Fahrzeug gefunden: AICHI_563978 (-77 dBm)
19:53:00.777 Verbinde mit A4:04:50:CE:2C:62 …
19:53:04.787 Adapter: an
19:53:04.787 Verbinde mit A4:04:50:CE:2C:62 …
19:53:06.787 Kanäle gefunden (2b70/2b71)
19:53:06.827 A001 Auth …
19:53:09.478 Verbindung steht — Befehle gehen jetzt ohne Vorlauf raus
19:53:18.652 ── Befehl 2 ──
19:53:18.652 Verbindung steht — B001 direkt
19:53:18.654 B001 2 … (tid 5efae7bcdd644e24ad364b394dda4c37)
19:53:20.933 Quittung ok (resCode 0) — warte auf Ausführung
19:53:25.972 Fahrzeug hat NICHT ausgeführt: result=1 reason=0xF1 (Ausführbedingungen nicht erfüllt)
Der Grund aus deinem Log ist gefunden — und er ist keine Verbindungs- oder Krypto-Störung, sondern genau der Nebeneffekt der Startfreigabe. ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.40 im Release.
Erst das Gute: der Hänger ist weg
Keine Endlosschleife mehr, keine „fremde Antwort übersprungen". Der tid-Fix aus v1.0.39 sitzt: die App liest jetzt sauber Quittung und das echte Ergebnis (B002). Was du siehst, ist zum ersten Mal die ehrliche Antwort der TBox auf „Verriegeln" —
reason 0xF1.Was 0xF1 bedeutet — und warum es logisch ist
0xF1(241) = „Ausführbedingungen nicht erfüllt". Ich habe im TBox-Firmware-Dekompilat nachgezogen, was die Startfreigabe auf der Fahrzeugseite auslöst (remote_data_computing, case0xd):set_BLE_4G_key(1)und schickt per CAN0x28eden virtuellen Schlüssel als „steckend". Genau das macht das Auto fahrbereit — dein „Bremse treten → fährt" ✅.0xF1. Das ist dasselbe Bild wie am Vortag (Verriegeln/Fenster gingen nicht), nur dass wir dir den Grund damals verschwiegen haben, weil wir das B002 nicht lasen.set_BLE_4G_key(0), inacota_do_remoteReport), sobald das Fahrzeug ausgeschaltet ist. Aus wird dieses Auto laut dir übers Verriegeln — also beißt sich die Katze in den Schwanz, wenn wir zu früh verriegeln: das Auto ist noch „an", also nimmt es Verriegeln nicht an.0x28d, Start/HV-artig). Also nicht mehrfach „Startfreigabe" drücken und gleiche Wirkung erwarten.Was v1.0.40 ändert (App-Seite, kein Krypto-Risiko)
reason 0xF1/241. Suite grün.Die eigentliche offene Frage (Test am Auto)
Damit „losfahren und wieder sauber abschließen" rund wird, müssen wir wissen, wann genau die TBox den virtuellen Schlüssel zurückzieht. Vorschlag für den nächsten Durchgang mit v1.0.40 (arm64):
https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.40/aiways-app-v1.0.40-arm64-v8a.apk
2= nicht ausgeschaltet,8= Schlüssel im Fahrzeug). Bitte die Meldung + Protokoll kopieren.Home-Screen/„Losfahren"-Taste bleibt wie gewünscht bewusst draußen, bis das Verhalten vollständig verstanden ist.
Ah, na dann ergibt das Sinn. Es gibt in der Head-Unit den unter "Fahrzeug" den Button "Fahrzeug ausschalten". Wahrscheinlich muss man den drücken, damit es funktioniert. Kannst du die Fehlermeldung von „…wahrscheinlich hält das Auto noch die Startfreigabe (virtueller Schlüssel steckt, es ist fahrbereit). Über Bluetooth lässt sich die Startfreigabe nicht widerrufen; erst wenn das Auto ausgeschaltet ist, nimmt es Verriegeln wieder an." ändern in "Bitte das Fahrzeug erst ausschalten"?
Erledigt. ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.41 im Release.
Geändert
— Bitte das Fahrzeug erst ausschalten.Der lange Erklärtext (virtueller Schlüssel / kein Widerruf über BLE) ist raus.flutter analyzesauber, alle 65 Tests grün. Home-Screen/„Losfahren“ weiterhin bewusst unangetastet.APK v1.0.41 (arm64): https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.41/aiways-app-v1.0.41-arm64-v8a.apk
Nächster Test am Auto
Wenn Du wieder Zeit hast: Startfreigabe → Bremse → fahren → Head-Unit „Fahrzeug ausschalten“ → dann in der App verriegeln. Geht das durch, ist die Kette komplett verstanden und ich baue die sichtbare „Losfahren“-Aktion. Falls die TBox weiter blockt, steht der reason-Code in der Meldung + im Protokoll — bitte wieder kopieren.
Ja, betätigt des "Fahrzeug ausschalten" Buttons im Headunit Menü hat geklappt. Danach liefen die BLE Commands soweit durch.
Ich habe jetzt Mal Steuerung über BLE etwas getestet:
Ergebnis:
Verriegeln, Entriegeln, Licht, Hupe, Fenster hoch, Fenster runter funktioniert.
Heckklappe, Ladeanschluss, Sonnendach auf, Sonnendach zu funktioniert NICHT.
Klimasteuerung teste ich als nächstes. Ich hätte das BLE Thema gerne richtig Bulletproof.
Was mir zufällig aufgefallen ist. Das Befehlspopup (Notification?) hat ne ziemlich lange runtime. Wenn man viele Befehle nacheinander abfeuert, dann ist das sehr befremdlich, durch den Zeitversatz
Ich habe die BLE-Befehle im Dekompilat der Original-App Byte für Byte durchgezogen und die Ausfälle sauber getrennt in „unser Fehler" vs. „Protokoll-/Fahrzeuggrenze". ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.42.
Was die Original-App über BLE wirklich kann (V1,
client/rc/j.java)Der V1-Client (den deine TBox spricht) implementiert genau diese Befehle:
doorUnlock=1, doorLock=2, windowRaise=3, windowFall=4, liftUpSkylight=5, parallelSkylight=6, dropSkylight=7, trunkUnlock=8, whistle=11, flash=12, releaseStartAuth=13.Zwei Dinge, die die Original-App über V1-BLE selbst ablehnt:
chargingCover(...)(Ladeanschluss) →onError("请断开蓝牙后重试")= „erst Bluetooth trennen". Lief beim OEM nur über Cloud/4G, nicht BLE.trunkOpen(degree)(Heckklappe elektrisch auffahren) → dieselbe Ablehnung. Über BLE gibt's nurtrunkUnlock(type 8), das nur die Klappe entriegelt.Unsere Fehler → behoben (v1.0.42)
percentwird bei V1 nicht mit serialisiert, also{"type":"6"}).resCode -2 „Typ unbekannt". Jetzt gehen Klima und Ladeanschluss bewusst ans Backend statt am Auto ins Leere.Fahrzeugseite — was zu erwarten ist
Nächster Test am Auto (v1.0.42 arm64)
https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.42/aiways-app-v1.0.42-arm64-v8a.apk
Bitte Heckklappe und Sonnendach auf/zu je einmal auslösen und die Meldung/den reason-Code (steht jetzt im Klartext + im Bluetooth-Protokoll) kopieren. Damit sehen wir schwarz auf weiß, ob das Auto die Aktoren gar nicht hat oder ob wir noch was drehen müssen. Klima kannst du wie geplant testen — es geht jetzt über das Backend, nicht BLE.
Ja, das kann sein. In dem Fall sollten wir in der original App schauen, wie das über Cloud implementiert war, bspw Klima. Es stimmt, dass die original App das per BLE gar nicht implementiert. Trotzdem ist der Befehl über Bluetooth vorhanden. Das habe ich im dekompilat der TBox gefunden und auch schon am Auto getestet (nur mit dem payload habe ich noch nicht so viel rumgespielt, das sollten wir tun.
Beides umgesetzt — genau deine zwei Punkte. ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.43.
1) Klima geht jetzt per Bluetooth (nicht mehr ans Backend)
Du hattest recht: nur weil die Original-App Klima nur über Cloud/4G anbot, heißt das nicht, dass die TBox es nicht per BLE kann. Im TBox-Dekompilat dispatcht
remote_data_computingdentypein derselben Kommandotabelle wie die übrigen BLE-Steuerbefehle:Der BLE-B001-Pfad (
FUN_0003686c→ ParserFUN_00027350→ dieselben Firmware-Globals wie Cloud) liest genau die Klima-FelderacState, windVolume, autoMode, cycleMode, windDirection, delay, isSchedule, temp. Also: Klima ist über BLE ansprechbar — Cloud/BLE teilen sich Parser und Dispatch.In der App:
climate_on→ BLE type 9,climate_off→ BLE type 10 (statt vorhernull→ Backend).{"type":"9","param":{"temp":"<Zehntel>"}}, z. B. 21,5 °C →215) — das ist das am besten verstandene Feld. Gebläse/Luftverteilung/Sitzheizung sind noch nicht in der BLE-Payload — genau der Teil, mit dem du „noch nicht viel rumgespielt" hast. Der Rahmen steht jetzt, wir können die param-Felder am Auto Schritt für Schritt dazunehmen.2) Schreiben ist jetzt BLE-only — Rest ins Backlog, nicht ins Backend
Zieldesign umgesetzt: Fahrzeug-Aktoren gehen ausschließlich über Bluetooth. Kein Backend-Ausweich mehr.
charge_port_open(Ladeanschluss) hat in der V1-BLE-Tabelle der TBox keinen Eintrag → geht bis zur Verifikation ins Backlog, nicht mehr ans Backend. (Falls du den Ladeanschluss-Befehl im TBox-Dekompilat auch per BLE gefunden hast, sag mir dentype— dann verdrahte ich ihn.)set_charge_limit(Ladelimit) ist kein Aktor und geht weiter ans Backend.Eine Konsequenz, ehrlich geflaggt: Die Web-App kann Steuerbefehle jetzt nicht mehr absetzen (kein BLE im Browser) — sie landen dort im Backlog. Für echte Steuerung ist damit die Android-APK mit BLE der Weg; Web bleibt Vorschau/Telemetrie. Passt das so, oder soll Web für Steuerung eine eigene Behandlung bekommen?
flutter analyzesauber, 67 Tests grün (Routing-Tests aufs neue Design umgestellt).Test am Auto (v1.0.43, arm64)
https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.43/aiways-app-v1.0.43-arm64-v8a.apk
tempwill oder mehr.windVolume, LuftverteilungwindDirection, UmluftcycleMode) und optional Sitzheizung als eigenen BLE-Befehl (type 14/15) dazu.Okay, das hast du jetzt falsch verstanden. Mit Backlog meinte ich nicht den Backlog in der App, sondern unseren eigenen Backlog der offenen ToDos. Damit war nicht gemeint, dass wir nicht mehr in unser Backend schicken sollen. Die Logik sollte nach wie vor sein:
BLE verbunden? Execute over BLE : Kann BLE verbunden werden? Execute over BLE : Execute over LTE (Backend)
Für die App bedeutet das, dass Steuerungen, die wir bis jetzt noch nicht am Fahrzeug als funktional verifiziert haben in einen Testbereich innerhalb der App verschoben werden. Von da aus machen wir Tests und wann das funktioniert, packen wir sie wieder in den Steuerungsbereich der App
Verstanden — beides umgesetzt, und die falsche Auslegung von „Backlog" ist wieder raus. ✅ CI grün (analyze/gate/release/deploy-web), Web live, APK v1.0.44.
1) Der Weg zum Auto ist wieder genau deine Regel
BLE verbunden? → BLE : BLE verbindbar? → BLE : LTE (Backend)So läuft die
CommandQueuejetzt wieder: erst Bluetooth, und erst wenn das nicht geht (kein Auto in Reichweite, Auth/Timeout, oder für den Aktor kein BLE-Code), geht der Befehl per LTE ans Backend. Nur wenn auch das Backend nicht erreichbar ist (offline), bleibt er lokal liegen und wird beim nächsten Anlauf erneut versucht. Der BLE-Grund reist als Detail in der Meldung mit — sonst sieht „ans Backend übergeben" so aus, als hätte die App Bluetooth nie probiert.Zwei Konsequenzen, die damit auch wieder gelten:
set_*-Befehle (Ladelimit) sparen sich wie bisher den BLE-Umweg — die steuern keinen Aktor.2) Testbereich in der App
Neu: Mehr → Testbereich (auch von der Steuerungsseite aus verlinkt). Grundlage ist ein Katalog (
lib/controls_catalog.dart), in dem jeder Steuerbefehl seinen Stand am Fahrzeug trägt —verified/ohne Wirkung/ungetestetplus Notiz. Ein Befehl wandert zwischen Steuerung und Testbereich durch Ändern genau einer Zeile; damit ist dein Ablauf („testen, und wenn's läuft, zurück in die Steuerung") eine Minutensache.In der Steuerung geblieben (von dir am 30.08. am U5 bestätigt): Türen ver-/entriegeln, Fenster hoch/runter, Blinken, Hupe.
In den Testbereich gewandert:
Jede Karte hat einen „Testen"-Knopf und zeigt danach die echte Antwort der TBox (result/reason im Klartext) — das ist genau das Material, das wir zum Weiterkommen brauchen. Die Startfreigabe ist ebenfalls vom Testbereich aus verlinkt, bleibt aber auf der Bluetooth-Seite (sie hängt am rohen BLE-Pfad samt Protokoll).
Zwei Entscheidungen, die ich getroffen habe — sag, wenn du es anders willst, beides ist schnell zurückgedreht:
Unser Backlog (offene ToDos, nicht App-Zustand)
type 9/10überhaupt durch? Danach die param-Felder (windVolume,windDirection,cycleMode, Sitzheizung 14/15) Stück für Stück dazu.typesuchen (du meintest, du hast dort Befehle gefunden, die die OEM-App nicht nutzt — wenn du den Code hast, verdrahte ich ihn sofort).Test am Auto (v1.0.44, arm64)
https://forgejo.timo-erdbruegger.de/aiways/AiwaysApp/releases/download/v1.0.44/aiways-app-v1.0.44-arm64-v8a.apk
Bitte im Testbereich je einmal Heckklappe, Sonnendach neigen/öffnen/schließen und die Klima-Seite auslösen und die Meldung bzw. den reason-Code kopieren. Damit sortiere ich die Tabelle oben nach — und was durchläuft, ist beim nächsten Release wieder in der Steuerung.
flutter analyzesauber, 75 Tests grün. Web: http://192.168.1.85/ (Strg+F5 wegen Cache).Da scheint wieder etwas untergegangen zu sein. Den BLE Klima-Befehl hatten wir bereits getestet. Der funktionierte. Wir haben nur noch nicht die einzelne Payload-Tiefe durchgetestet. Also Klima-Befehl ohne Payload lief (nimmt dann einfach die zuletzt eingestellten settings).
Außerdem: Könnte es sein, dass das was du Ladeanschluss / set_charge_limit nennst eigentlich das Max-Lade-Limit ist? Also bspw. Akku nur bis 80% laden?
Beides erledigt. ✅ CI grün (analyze/gate/release/deploy-web), Web live unter http://192.168.1.85/, APK v1.0.45 im Release.
1) Klima ist wieder im normalen Menü
Du hast recht — der BLE-Klima-Befehl (type 9/10) ist am U5 bestätigt; nur die feine Payload (Gebläse/Luftverteilung) ist noch nicht einzeln durchgetestet. Der Befehl ohne Detail-Payload läuft und das Auto nimmt die zuletzt eingestellten Werte. Also gehört Klima nicht in den Testbereich:
flutter analyzesauber, 75 Tests grün.2) „Ladeanschluss" vs. „Ladelimit" — das sind zwei verschiedene Dinge
Kurz: nein, der Ladeanschluss ist nicht das Max-Lade-Limit. Ich hatte die zwei in meinen Kommentaren unglücklich in einen Topf geworfen — hier sauber getrennt:
a) Ladeanschluss =
charge_port_open= ChargingCover (充电口盖) = die physische Ladeklappe.Das ist die kleine Klappe, hinter der der Ladestecker sitzt — reines Auf/Zu eines Aktors, kein Prozentwert. Belege aus dem Reverse-Engineering:
BCMChargingCoverSts_AC10Aauf0x2D0(Klappe offen/zu).0x28C, data0x00100000(»Ladeklappe öffnen«) — verifiziertes Frame-Format über den Port-50000/HU-Root-Kanal (tbox50000.py).b) Ladelimit =
set_charge_limit= »lade nur bis X %« (z. B. 80 %).Das ist der Ziel-Ladezustand — eine Konfiguration, kein Aktor. Genau das, was du mit „Akku nur bis 80 %" meinst.
Ehrliche Einordnung, zwei Punkte:
set_charge_limithabe ich im reversten OEM-Protokoll bisher KEINEN Befehl gefunden — weder im BLE-Satz (V1/V2) noch in der Cloud/4G-Case-Tabelle (remote_data_computing, Cases 1–15 + 0x5b) noch in der Port-50000-Opcode-Liste. Der OEM hat das Ladelimit (Ziel-SoC / „保电") sehr wahrscheinlich nur cloud-seitig gesetzt. Heißt: unserset_charge_limitist aktuell ein eigener Backend-Wert, am Fahrzeug noch nicht verifiziert. Der Schieber in der Lade-Seite funktioniert lokal + Backend, aber ob der U5 ihn tatsächlich übernimmt, ist offen. Das gehört sauber auf unseren Backlog: den echten Ziel-SoC-Pfad (CAN/BLE) finden.[1,15]. Aber deine TBox spricht V1, und dort sind9/10— wie du am Auto bestätigt hast — Klima, nicht Kofferraum/Ladeklappe (die alte „tbc"-Vermutung in meinen Notizen war für V1 also falsch). Für den V1-Ladeklappen-Befehl haben wir schlicht noch keinen bestätigtentype. Deshalb bleibt der Ladeanschluss zu Recht im Testbereich.Vorschlag nächster Schritt (kein Auto-Zwang jetzt): Ich kann einen Ladeklappen-Test in den Testbereich bauen, der am Auto den V1-
typeeinkreist (Kandidaten durchprobieren und dabeiBCMChargingCoverStsauf0x2D0beobachten) — bzw. alternativ den verifizierten CAN-Weg0x28C/0x00100000über den Port-50000-Kanal. Und separat den echten Ziel-SoC-Pfad für das Ladelimit suchen. Sag, ob ich das als eigene Issues anlegen soll.Wenn du das mit der Ladeklappe im V2 gefunden hast, dann können wir das ignorieren. Soweit ich das bisher verstanden habe, war die V2 Implementation die Variante für den Aiways U6, das hat mit unserem U5 nichts zu tun.
Den set_charge_limit wird über BLE nicht existieren, das wäre dann für uns auch ein reiner Backend-Command. Ich würde das aber in der App nicht sichtbar unterscheiden. Der Enduser soll sich da am Ende gar keine Gedanken drüber machen, welches Command jetzt über welchen Kanal ans Auto geht. Der soll einfach nur ein schönes UX Erlebnis haben.
Beides aus Deinem letzten Kommentar ist umgesetzt — und ich muss dazusagen, dass der vorige Lauf mittendrin abgebrochen ist: der Code war committet und gepusht, der Release-Lauf dazu fehlte. Deshalb hier der Stand sauber zusammen.
1) V2-Ladeklappe ist raus aus der Verfolgung
Deine Einordnung übernommen: V2 = U6, für unseren U5 irrelevant. Der
[1,15]-Opcode taucht in der App nirgends mehr als Hoffnungsträger auf. Im Testbereich steht beim Ladeanschluss jetzt nur noch, was stimmt: am Auto ohne Wirkung, für den U5 kennen wir noch keinen bestätigten Befehl. Kein V2-Hinweis, keine falsche Fährte.2) Der Kanal ist aus der Oberfläche verschwunden
Genau Deine Vorgabe — der Nutzer soll sich keine Gedanken machen, über welchen Weg ein Befehl ans Auto geht:
lock, „ausgeführt / unterwegs / wartet" stattacked/sent/pending.set_charge_limitbleibt technisch ein reiner Backend-Wert (über BLE gibt es ihn nicht) — in der App ist davon nichts zu sehen. Der Schieber auf der Lade-Seite ist einfach ein Schieber.Die Regel dahinter läuft unverändert wie von Dir vorgegeben: verbunden → BLE, sonst verbindbar → BLE, sonst LTE.
3) Nebenbei: der Web-Build hing an der BLE-Krypto
Beim Nachziehen ist aufgefallen, dass der Web-Build im CI ins Swappen lief (~30 Min, sah aus wie „hängt"). Ursache: der pointycastle-Import (AES/RSA/MD5 für BLE) wurde über
command_queuebis in den Web-Build gezogen — dort ist er sinnlos, BLE gibt es im Browser nicht.crypto.dartist jetzt eine bedingte Weiche: nativ die echte Krypto, im Web nur die Hex-/Wire-Helfer. Lokal ist der Web-Build damit wieder bei 49 s statt 94 s.Stand der Verifikation — ehrlich
flutter analyzesauber, 94 Tests grün, Web-Build lokal gebaut. Ein Release/Deploy gibt es dafür noch nicht: Forgejo legt seit ca. 12:24 für neue Pushes keine Actions-Runs mehr an — drei Pushes sind aufmainangekommen, es startet schlicht kein Lauf (der letzte Release-Lauf wurde nur „cancelled"). Alsclaudecodekomme ich nicht an die Runner-Verwaltung. Sobald der Runner wieder läuft, baut der nächste Push Release + Web-Deploy und ich melde die APK-Version nach. Der Code liegt fertig aufmain(951023c).Alles klar, auf dem Homescreen hast du jetzt den Klima-Short-Cut durch Fenster ersetzt. Das sollten wir wieder rückgängig machen. Ich habe auch eben Klima getestet mit 22°C, sie ist angegangen, also der Befehl an sich hat funktioniert, aber im Display der Headunit stand weiterhin 21°C (was zuletzt eingestellt gewesen ist). Hast du eine Idee?
Beides erledigt — und ja, ich habe eine Idee zu den 21 °C, die sich im TBox-Dekompilat eindeutig belegen ließ. ✅ CI grün (analyze/gate/release/deploy-web), Web live unter http://192.168.1.85/, APK v1.0.51.
Vorweg zur Transparenz: der Lauf, der das gebaut hat, ist nach dem Release abgebrochen — Code, CI und Deploy waren durch, nur dieser Kommentar fehlte. Ich habe den Stand deshalb gerade komplett nachverifiziert (Dekompilat neu nachgelesen,
flutter analyzesauber, 98 Tests grün, Releasev1.0.51auf Commit81890a7, Web antwortet).1) Klima-Shortcut ist wieder auf dem Homescreen
Rückgängig gemacht: die dritte Kachel auf der Startseite ist wieder Klima, nicht Fenster. Das war ein Kollateralschaden der Testbereich-Umstellung — die Kachel war getauscht worden, als Klima kurzzeitig als „unbestätigt" galt. Fenster auf/zu bleibt in der Steuerung, da ändert sich nichts.
2) Warum die Head-Unit auf 21 °C stehen blieb — wir haben die falsche Einheit geschickt
Kein Verbindungs-, Krypto- oder Timing-Problem. Wir haben 22 °C als „220" auf den Draht gelegt, die TBox hat daraus 220 °C gelesen und den Wert als unsinnig verworfen. Der Ein-Befehl selbst blieb gültig — deshalb ging die Klimaanlage an und behielt einfach die alte Temperatur.
Die Kette, beide Enden im Dekompilat:
a) Der Parser (
FUN_00027350, den BLE und Cloud benutzen) liest 18 param-Felder. 17 davon perstrtol. Genau eines geht perstrtod— und wird danach mit0x40240000multipliziert, also der Double-Konstanten10.0, und per Soft-Float-d2izaufintabgeschnitten:Dass genau dieses eine Feld die Temperatur ist, ist die einzige Lesart, die passt: alle übrigen Klima-Felder (
acState,windVolume,autoMode,cycleMode,windDirection,delay,isSchedule) sind ganzzahlig — nur die Zieltemperatur hat Nachkommastellen. Heißt: intern rechnet die Firmware in Zehnteln, auf dem Draht erwartet sie Grad.b) Der Dispatcher (
remote_data_computing, case 9) nimmt diesen Wert alsparam_6entgegen:Das erklärt deine Beobachtung Byte für Byte:
"220")"22.0")strtod0xFD(keine Änderung)(220−160)/5 = 12→12*4|1=0x31Der
0x28d-Frame wird in beiden Fällen gesendet — nur das Temperaturbyte kippt auf den Sentinel. Genau deshalb sah es so aus, als hätte der Befehl „funktioniert, aber halb".Was in der App geändert ist
climateParamschickt jetzt Grad:{"temp":"22.0"}statt{"temp":"220"}.0xFDgehen, das fangen wir jetzt vorher ab.× 10imd2iznicht mehr danebenschneiden ("22.3"wäre als 222.9999… zu 222 geworden).Bitte einmal am Auto gegenprüfen
Klima mit 22,5 °C starten — wenn die Head-Unit jetzt 22,5 anzeigt statt des alten Werts, ist die Kette durch und wir können uns an die Payload-Tiefe machen (Gebläse, Luftverteilung,
autoMode/cycleMode). Die Felder liegen im selben Parser direkt daneben, die kann ich einzeln freischalten, sobald die Temperatur bestätigt ist.