Das ist die App, die über die Headunit sämtliche Einstellungen an der TBox vornimmt, damit die LTE-Bridge eine verbindung zur TBox und zum Backend aufbauen kann
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Timo Erdbrügger 30c3fe0a3d CI: Release-APK zusaetzlich ins Backend hochladen (Download-Quelle der Landingpage)
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
2026-09-24 10:36:21 +02:00
.forgejo/workflows CI: Release-APK zusaetzlich ins Backend hochladen (Download-Quelle der Landingpage) 2026-09-24 10:36:21 +02:00
app release: Vorhandene TBox-Config erkennen (QR direkt zeigen) + Beenden-Knopf 2026-09-11 09:04:51 +02:00
gradle/wrapper HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00
.gitignore HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00
build.gradle HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00
claude_initial_setup.txt claude_initial_setup.txt hinzugefügt 2026-08-31 16:21:12 +02:00
gradle.properties HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00
gradlew HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00
gradlew.bat HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00
README.md release: Spur-Datei tbox_daemon.start wieder raus (fota.sh sonst unveraendert) 2026-09-10 14:05:28 +02:00
settings.gradle HeadUnit-Provisioning-App v0.1: Wizard + TBox-Deploy + QR 2026-08-31 16:28:33 +02:00

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

  1. 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.
  2. „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 via SITE 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.sh und /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: current ist auf vielen Boxen nur ein Symlink auf den aktiven Slot (/usrapp/1 bzw. /usrapp/2) — teils ist auch die Wurzel selbst oder die fota.sh ein 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-LIST Komponente für Komponente auf (busybox-ls -l zeigt name -> 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) und fota.sh.bak (Stand vor diesem Lauf) — beide Backups werden zurückgelesen und verglichen; lässt sich kein Backup belegen, bleibt diese fota.sh unangetastet. Existieren weitere Slots mit eigener fota.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 einzige fota.sh den Autostart bekommen hat. Das Log nennt am Ende die Zahl der gepatchten Dateien.

    Wo der Block landet: die Werks-fota.sh startet den admDaemon mit & und beendet sich sofort mit exit 0. Ein angehängter Autostart steht damit hinter dem exit und läuft nie — genau das ist beim ersten Community-Test passiert. Die App setzt den Block deshalb vor das erste Top-Level-exit (verschachtelte exit in if/case/Funktionen zählen nicht mit); gibt es gar kein exit, wird angehängt. Vor dem Schreiben prüft die App die fertige Datei noch einmal selbst — steht der Block hinter einem exit oder doppelt drin, wird gar nicht geschrieben. Ein erneuter Provisioning-Lauf repariert eine bereits falsch gepatchte fota.sh (der alte Block wird beim Neuaufbau entfernt).

    Und das abschließende exit 0 fällt weg. Auf einer Box, deren fota.sh am Dateiende kein exit 0 hat, startet der Daemon; auf einer sonst zeichengleichen Box mit exit 0 nicht — obwohl der Block davor steht. Das ist der einzige belegte Unterschied zwischen beiden Fällen, also kommentiert die App die terminierende exit-Zeile aus (# [AICHI-Provisioning deaktiviert] exit 0); das Skript endet dann am Dateiende. Angefasst wird nur ein exit, 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 eingecheckte app/src/main/assets/tbox_daemon (v1.0.49) ist nur Fallback für lokale Builds und für den Fall, dass secrets.AIWAYS_CI_TOKEN fehlt.

    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
    
  3. 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/current real 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.