- Java 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Nach dem Forgejo-Release laedt die CI die APK per POST /firmware ins Backend (Header X-Upload-Token aus secrets.BACKEND_UPLOAD_TOKEN, Ziel vars.BACKEND_URL, Default https://aiways.it-perspective.de). Damit haengen die Landing-Downloads nicht mehr an Forgejo. Fehlt das Secret oder scheitert der Upload: nur Warnung, Release bleibt gueltig. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011shUomooZ2agMQJjKmjHpi |
||
| .forgejo/workflows | ||
| app | ||
| gradle/wrapper | ||
| .gitignore | ||
| build.gradle | ||
| claude_initial_setup.txt | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle | ||
HeadUnit TBox Provisioning
Provisioning-App für die Aiways Head-Unit (Android 4.4.2 / API 19). Wird per USB-Stick installiert und richtet in einem Erststart-Wizard die TBox komplett ein.
Nachfolger/Erweiterung von BLEAppProvisioning
(die nur die BLE-key_*-Files schrieb) — diese App macht das gesamte Setup.
Ablauf
-
Wizard (Erststart): erfasst
- WLAN-SSID der TBox — vorbelegt
MyAiways-XXXXX(5 Zufallszeichen), editierbar, - WLAN-Passwort — vorgeneriert (12 Zeichen), editierbar (8–31 Zeichen),
- Backend-URL — Default
https://aiways.it-perspective.de.
- WLAN-SSID der TBox — vorbelegt
-
„Fertig – TBox einrichten" → schreibt per FTP (
root:oelinux123@<TBox-IP>:21) auf die Box:- BLE-Schlüssel
key_AIWAYSCOMMUNITY,key_AIWAYSCOMMUNITY_expiretime,ac_public_key.pem→/usrdata/pem, - unseren Telemetrie-Daemon
tbox_daemon(statisches armv7-Binary, als Asset eingebettet) →/usrdata/tbox_daemon(+x viaSITE CHMOD), - dessen Config aus den Wizard-Werten →
/usrdata/tbox_daemon.conf, - den Daemon-Autostart in jede echte
fota.sh(Boot-Script der TBox).
Zur echten
fota.sh: die Box hat sie zweimal —/usrapp/current/data/fota/fota.shund/usrdata/current/data/fota/fota.sh. Welche davon beim Boot wirklich ausgeführt wird, hängt an Firmware/Slot, also patcht die App beide (der Autostart-Block ist idempotent, ein zweiter Lauf erzeugt keine Duplikate). Dazu kommt:currentist auf vielen Boxen nur ein Symlink auf den aktiven Slot (/usrapp/1bzw./usrapp/2) — teils ist auch die Wurzel selbst oder diefota.shein Link. Wer blind auf den Link-Pfad schreibt, erwischt je nach ftpd/Firmware nicht die Datei, die beim Boot wirklich läuft (und legt das Backup neben den Link statt neben das Original). Die App löst jeden Pfad deshalb zur Laufzeit per FTP-LISTKomponente für Komponente auf (busybox-ls -lzeigtname -> ziel) und arbeitet nur auf dem physischen Pfad; zeigen beide Wurzeln auf dieselbe Datei, wird sie nur einmal angefasst. Die Auflösungskette steht im Log. Gesichert wird zweifach:fota.sh.orig(Werkszustand, nur beim ersten Lauf) undfota.sh.bak(Stand vor diesem Lauf) — beide Backups werden zurückgelesen und verglichen; lässt sich kein Backup belegen, bleibt diesefota.shunangetastet. Existieren weitere Slots mit eigenerfota.sh, bekommen sie denselben Autostart-Block — sonst wäre er nach einem FOTA-Slot-Wechsel wieder weg. Angelegt wird dabei nichts, nur vorhandene Dateien werden ergänzt. Einzelne Pfade dürfen scheitern (read-only Slot o. ä.); abgebrochen wird der Lauf nur, wenn keine einzigefota.shden Autostart bekommen hat. Das Log nennt am Ende die Zahl der gepatchten Dateien.Wo der Block landet: die Werks-
fota.shstartet denadmDaemonmit&und beendet sich sofort mitexit 0. Ein angehängter Autostart steht damit hinter demexitund läuft nie — genau das ist beim ersten Community-Test passiert. Die App setzt den Block deshalb vor das erste Top-Level-exit(verschachtelteexitinif/case/Funktionen zählen nicht mit); gibt es gar keinexit, wird angehängt. Vor dem Schreiben prüft die App die fertige Datei noch einmal selbst — steht der Block hinter einemexitoder doppelt drin, wird gar nicht geschrieben. Ein erneuter Provisioning-Lauf repariert eine bereits falsch gepatchtefota.sh(der alte Block wird beim Neuaufbau entfernt).Und das abschließende
exit 0fällt weg. Auf einer Box, derenfota.sham Dateiende keinexit 0hat, startet der Daemon; auf einer sonst zeichengleichen Box mitexit 0nicht — obwohl der Block davor steht. Das ist der einzige belegte Unterschied zwischen beiden Fällen, also kommentiert die App die terminierendeexit-Zeile aus (# [AICHI-Provisioning deaktiviert] exit 0); das Skript endet dann am Dateiende. Angefasst wird nur einexit, das allein auf seiner Zeile steht und hinter dem nichts Ausführbares mehr folgt — sonst würde bisher toter Code der Box scharf geschaltet. Auskommentiert statt gelöscht heißt: sichtbar, und der nächste Lauf rechnet wieder vom Original aus (kein Aufschaukeln über mehrere Läufe).Der Block selbst besteht aus einer Zeile (
chmod +x+ Daemon-Start mit--warmup); die Diagnose steht in/usrdata/tbox_daemon.log. Eine zusätzliche Spur-Datei (tbox_daemon.start, kurzzeitig in v1.0.31) war beim Nachsehen auf der Box eher verwirrend als hilfreich und ist wieder draußen — ein Provisioning-Lauf löscht eine liegengebliebene Datei mit.Welcher Daemon steckt in der APK? Die CI zieht bei jedem Build das neueste
tbox_daemon-Release und überschreibt damit das eingecheckte Asset — die ausgelieferte APK ist also nie älter als der Daemon. Das eingecheckteapp/src/main/assets/tbox_daemon(v1.0.49) ist nur Fallback für lokale Builds und für den Fall, dasssecrets.AIWAYS_CI_TOKENfehlt.Der tatsächlich eingebettete Tag steht an drei Stellen, damit man ihn nicht raten muss: im Asset
tbox_daemon.version, in den Release-Notes und im App-Log beim Provisionieren (Daemon-Asset: v1.0.49 (642440 B)).Eingechecktes Asset von Hand aktualisieren (Version in beiden Dateien anpassen):
TAG=v1.0.49 curl -fL -o app/src/main/assets/tbox_daemon \ "https://forgejo.timo-erdbruegger.de/aiways/tbox_daemon/releases/download/$TAG/tbox_daemon-$TAG-armv7" printf '%s\n' "$TAG" > app/src/main/assets/tbox_daemon.version - BLE-Schlüssel
-
Zeigt einen QR-Code, den die AiwaysApp scannt. Payload (JSON):
{"v":1,"type":"aiways-provision","backend":"…","ap_ssid":"…","ap_key":"…","tbox_ip":"…"}Damit weiß die App, welches Backend sie nutzt und wie sie in den TBox-AP kommt.
Der QR erscheint erst ~95 s nach dem Reboot (
REBOOT_WAIT_SEC) — so lange läuft der Ladeindikator mit Countdown weiter. Grund: Boot +fota.sh-Warmlauf + Umbenennen der AP-SSID dauern real ~90 s; wer sofort scannt, findet das WLAN noch nicht. Der große Status-Text zeigt bewusst nur generische Phasen („Wir aktualisieren jetzt deine TBox …") — die FTP-/Protokoll-Details stehen im aufklappbaren Log darunter.
Die TBox-IP ist per Default 192.168.1.1; „TBox suchen" scannt das lokale Netz nach offenem FTP (:21).
Bauen
Reines Java, kein AndroidX, einzige Dependency com.google.zxing:core (QR). Gebaut wie die
anderen Repos über die Forgejo-CI (.forgejo/workflows/build-apk.yml): AGP 8.5.2, Gradle 8.7,
JDK 17, assembleDebug. Ein Commit mit dem Wort release erzeugt automatisch ein Release
mit der APK (Tag vX.Y.Z, Patch zählt hoch).
Lokal:
./gradlew assembleDebug
# -> app/build/outputs/apk/debug/app-debug.apk
Deployen (auf die Head-Unit)
APK per USB-Stick auf die Head-Unit kopieren und installieren (Gerät ist gerootet, im selben Netz wie die TBox). App starten → Wizard.
Noch offen (Folge-Issues)
- Hardware-Gegenprobe der Pfad-Auflösung: auf einer Community-Box im Log prüfen, wohin
/usrapp/currentreal zeigt (die App protokolliert die Link-Kette) — der Autostart selbst ist implementiert. - QR-Scan in der AiwaysApp: Gegenstück implementieren (Payload-Vertrag oben), inkl. des optionalen Konto-Anlege-Pfads (in der App bisher deaktiviert).
- ESP32-S3-LTE-Provisionierung über dieselben Daten — Hardware liegt noch nicht vor, der QR-Payload ist aber schon so gestaltet, dass er später erweitert werden kann.
- Hardware-End-to-End-Test am echten Fahrzeug.
Sicherheit
Die App läuft nur im lokalen TBox-Netz. FTP-Zugang (oelinux123) ist der Werks-Default der Box.
Die erzeugten AP-Credentials stehen im QR-Code (bewusst — der Installateur scannt ihn direkt) und
in /usrdata/tbox_daemon.conf (chmod 600). Keine echten Secrets im Repo.