- C 63.1%
- Python 34.9%
- Shell 1.4%
- Makefile 0.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| config | ||
| scripts | ||
| src | ||
| test | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
| SPEC.md | ||
| tbox_daemon_new_arm | ||
tbox_daemon v1 — TBox-Daemon (Read + Keep-Awake + WiFi-Watchdog)
Ein kleiner nativer C-Daemon, der auf der TBox läuft. Er liest die
Fahrzeug-Telemetrie lokal aus zwei Quellen — per dbcvpage (0x301-Shell auf
127.0.0.1:50000) und über den lokalen MQTT-IPC-Bus (admups auf
127.0.0.1:7090) —, hält die Box per setmode 0 wach, richtet den WiFi-AP mit
eigener SSID/Key aus seiner Config ein und überwacht ihn: fällt er weg, fährt
der Daemon ihn per 0x2xx-Opcode wieder hoch — abgesichert durch einen
12V-SoC-Cutoff, damit der Pufferakku nie leergezogen wird.
Kein Cloud-Umweg: der Daemon redet direkt mit den TBox-Kanälen auf :50000
(0x301-Shell für dbcvpage/setmode, 0x2xx-Opcodes für WiFi) und mit dem lokalen
MQTT-Broker. Sämtliche Signale beider Quellen liegen im RAM; :9000
serialisiert sie pro Anfrage, mit eigenem age_s je Quelle.
WiFi läuft über 0x2xx, NICHT über
setwifi(0x301). Am Auto bestätigt:setwifi/setssidper Shell wirken nicht (auch nicht nach Reboot). Die dedizierten Opcodes wirken: 0x204 setzt SSID, 0x205 Key (beide schreiben tbox_apps persistenten Store → überleben Reboot), 0x203 schaltet AN/AUS. 0x20c wird bewusst gemieden — es überschreibt SSID/Key mit den Werks-DefaultsArcfox-WIFI/00000000. Auth (WPA2) bleibt unangetastet persistiert.
Warum ein Watchdog? Getestet am Auto:
setmode 0hält die CPU wach, aber der WiFi-AP (ql_wifi) fällt nach Stunden trotzdem weg und kommt nicht von allein zurück. Der Daemon läuft lokal auf der Box (braucht selbst kein WiFi), erkennt den Ausfall und startet den AP neu (0x203 on, bei Bedarf SSID+Key neu).Grenze (ehrlich): Der Watchdog rettet nur den Fall „Box wach, AP-Radio tot" (laut Halter der Regelfall, da
tbox_appgeparkt weiterläuft). Sollte doch mal die ganze Box schlafen, ist auch der Daemon eingefroren — dann bräuchte es einen externen Weck-Trigger (timerwake/BLE), ein eigener Ausbau.
Was er tut
Beim Start: lädt die Config (/usrdata/tbox_daemon.conf) und richtet den AP
mit eigener SSID/Key ein — 0x204 (SSID) → 0x205 (Key) → 0x203 (an). Auth
bleibt persistiert (WPA2). Abschaltbar mit --no-ap-setup.
Jeden Zyklus (default 20 s):
- Liest die dbcvpage-Seiten
{13,30,33,47,59,61,64}und extrahiert die relevanten Signale. Danach stellt er dieselbe Runde Read-Anfragen über den MQTT-IPC-Bus (getCarState,getBatteryPower,getPowerStatus,getNetInfo, einmaliggetCarInfo— siehe MQTT-Quelle). - Entscheidet über Keep-Awake:
- erlaubt, wenn
12V-SoC > Cutoffoder das Auto lädt →setmode 0(running, Box bleibt wach). - sonst (12V unter Cutoff und nicht am Laden) →
setmode 3(auto, Box darf schlafen). So wird die 12V nie unter die Schwelle gezogen.
- erlaubt, wenn
- Legt den Snapshot im Speicher ab. Anfragen auf TCP :9000 werden pro
Anfrage frisch aus diesem Snapshot serialisiert (mit aktuellem
age_s) — nicht aus einer Datei gelesen. Eine Zustandsdatei schreibt der Daemon standardmäßig gar nicht; wer sie zum Debuggen will, schaltet sie mit--state PATHbzw.state = …ein. Siehe Warum ein Cache?.
Zusätzlich jede Sekunde (im Wartefenster zwischen den Telemetrie-Zyklen):
- WiFi-Watchdog: prüft lokal per
getifaddrs()zwei Dinge — ob die AP-Gateway-IP (ap_ip, default 192.168.1.1) an ein Interface gebunden ist und ob ein hochgezogenes AP-Radio-Interface (wlan_prefix, defaultwlan) existiert. Fehlt eines von beiden, gilt der AP als weg:- erst sanft
0x203 on(bringt den AP mit den persistierten Creds zurück), - nach 3 aufeinanderfolgenden Fehl-Ticks zusätzlich SSID+Key neu (
0x204+0x205) +0x203 on. - Frühestens alle
--wifi-retrySekunden (default 10), damit nicht dauergehämmert wird. - Bleibt der AP trotzdem
wifi_warmup_afterSekunden (default 60) weg, ist das Radio vermutlich kalt — das bringt0x203nicht hoch. Dann eskaliert der Watchdog einmalig0x20c(Radio-Neustart) und fährt sofortap_setuphinterher, weil0x20cauf die Werks-SSID zurücksetzt. Streng begrenzt: frühestens allewifi_warmup_retrys (default 300) und höchstenswifi_warmup_maxmal (default 3) am Stück ohne Erfolg. Nur mitwifi_warmup = 1. - Greift nur, wenn Wachbleiben gerade erlaubt ist (nicht im Cutoff) — im Cutoff soll die Box ja schlafen, da wird der AP bewusst nicht wiederbelebt.
- erst sanft
Warum die IP allein nicht reicht: auf der TBox hängt 192.168.1.1 an
bridge0— und die bleibt auch bei kaltem Radio bestehen, während gar keinwlan-Interface existiert. Die alte Prüfung sah deshalb „AP oben", obwohl kein WLAN in der Luft war (Issue #4). Heißt die Schnittstelle auf einer Box anders,wlan_prefixanpassen oder mitwifi_strict = 0auf die reine IP-Prüfung zurückschalten.
Ablauf: ein Thread, kein Parallelbetrieb
Der Daemon hat genau einen Thread — es läuft nie etwas „nebenher". Der
:9000-Listener ist ein nicht-blockierender Socket, der im Sekundentakt
abgefragt wird:
while (läuft) {
Telemetrie-Zyklus # 7x dbcvpage, danach setmode, dann Snapshot in den RAM
MQTT-Runde # 4-5 Read-Anfragen abschicken (Antworten kommen asynchron)
for (s = 0; s < interval; s++) { # Wartefenster, default 20x
accept() auf :9000 -> wartende Clients aus dem RAM beantworten
MQTT-Socket leeren -> eingetroffene Antworten in den RAM
WiFi-Watchdog-Tick
sleep(1)
}
}
Daraus folgt:
- Der WiFi-Check läuft jede Sekunde, nicht alle
intervalSekunden. Der teure dbcvpage-Zyklus läuft alleintervalSekunden (default 20). :9000antwortet aus dem RAM, ohne die Box zu befragen — deshalb sofort.- Trifft eine Anfrage während des Lesezyklus ein, wird sie nicht abgewiesen:
sie liegt im Listen-Backlog und wird beantwortet, sobald der Zyklus fertig ist
(also spätestens ein paar Sekunden später — bei hängender Box im Worst Case
~42 s, siehe unten). Das ist der einzige Fall, in dem
:9000„langsam" ist. - MQTT blockiert nie. Der Broker-Socket ist nicht-blockierend und wird im selben Sekundentakt geleert; Anfragen werden abgeschickt und die Antworten landen ein paar Millisekunden später im RAM. Fällt der Broker aus, versucht der Daemon alle 30 s neu zu verbinden — der dbcvpage-Zweig läuft davon unberührt weiter (und umgekehrt).
JSON-Felder
{"ts":1788040000,"ok":1,"age_s":7,"soc":29,"soh":100,"range_km":99.0,
"energy_kwh":18.2,"power_kw":-2.62,"pack_v":349.5,"pack_a":-7.5,
"charge_connected":0,"odo_km":43038.0,"batt12_v":11.780,"batt12_a":-0.178,
"batt12_soc":82,"keepawake":1,"cutoff_soc":70,"wifi_up":1,
"wifi_recover_count":0,"est_hours_left":30.3,
"wifi":{"wlan_iface":"wlan0","ap_ip_bound":1,"strict":1,"warmup_rc":0,
"warmup_tries":1,"setup_rc":{"ssid":0,"key":0,"enable":0}},
"mqtt":{"connected":1,"msgs":42,
"carstate":{"age_s":3,"speed":0,"batt_level":29,"parking_brake":1,"gear":15,
"engine_rate":0,"key":0,"ignition":0,"charge_state":3},
"batt12_pct":{"age_s":3,"value":82},
"power_state":{"age_s":3,"value":2},
"net":{"age_s":3,"cid":12345,"lac":678,"mcc":262,"mnc":1,"net_type":7,
"rx_lev":-71,"lon":8.123456,"lat":52.987654},
"car":{"vin":"LJ1…","model":"U5"}}}
Die dbcvpage-Felder stehen unverändert auf oberster Ebene (bestehende Clients
brauchen nichts zu ändern), die Bus-Signale hängen unter mqtt.
| Feld | Bedeutung | Skalierung/Quelle |
|---|---|---|
ts |
Unix-Zeit des Snapshots (TBox-Uhr!) | time() beim Lesezyklus |
ok |
1 = letzter Lesezyklus hat geklappt, 0 = TBox gerade nicht erreichbar (Werte sind dann der letzte bekannte Stand) | Laufzeit |
age_s |
Alter des Snapshots in Sekunden zum Zeitpunkt der Anfrage, −1 = noch nie gelesen | pro Anfrage berechnet |
soc |
HV-Ladestand % | BMSStateOfCharge |
soh |
HV-Gesundheit % | BMSStateOfHealth |
range_km |
Restreichweite km | VCUDrivingrange ×0.1 |
energy_kwh |
Restenergie kWh | BMSStateOfEnergy ×0.1 |
power_kw |
HV-Leistung kW (− = laden) | PackV × PackA / 1000 |
pack_v / pack_a |
HV-Pack Spannung/Strom | BMSPackVoltage / BMSPackCurrent (− = laden) |
charge_connected |
Ladestecker verbunden | BMSACChgConnectStt |
odo_km |
Kilometerstand | IPTotalOdometer |
batt12_v / batt12_a |
12V Spannung/Strom | BCMIBSUBATT ×0.001, (BCMIBSBatCur−32768)/1000 (+ = laden) |
batt12_soc |
12V-SoC % | BCMIBSSOC |
keepawake |
1 = Box wird gerade wachgehalten | Laufzeit-Entscheidung |
wifi_up |
1 = AP belegt oben (AP-IP und WLAN-Iface) | getifaddrs, siehe wifi |
wifi_recover_count |
wie oft der AP seit Start wiederbelebt wurde | Laufzeit-Zähler |
est_hours_left |
grobe Rest-Wachzeit bei aktueller 12V-Last bis Cutoff | aus --batt-ah |
Der Block wifi belegt, was der Daemon bei den AP-Operationen wirklich
erreicht hat — ohne telnet auf der Box:
| Feld | Bedeutung |
|---|---|
wlan_iface |
Name des gefundenen AP-Radio-Ifaces; leer = Radio kalt |
ap_ip_bound |
AP-Gateway-IP an irgendein Iface gebunden (auf der TBox: bridge0) |
strict |
1 = wifi_up verlangt beide Belege, 0 = nur die IP (alte Logik) |
warmup_rc |
letztes 0x20c: 0 quittiert, -1 keine Antwort, -2 nie versucht |
warmup_tries |
wie oft 0x20c abgesetzt wurde (Start + Eskalationen) |
setup_rc.ssid / .key / .enable |
letztes 0x204/0x205/0x203 on, gleiche rc-Konvention |
-1heißt: die Box hat auf den Opcode nicht geantwortet — die Einstellung ist also nicht belegt angekommen. Genau diese Fälle liefen früher still ins Leere, während das Log Erfolg meldete.
est_hours_leftist eine grobe Schätzung (nur bei 12V-Entladung gefüllt) und hängt von der angenommenen 12V-Kapazität (--batt-ah, default 45 Ah) ab.
MQTT-Quelle: der lokale IPC-Bus
Auf der TBox läuft ein anonymer MQTT-Broker (admups, 127.0.0.1:7090,
Klartext). Darüber reden tbox_app/sysi und die OTA-App miteinander. Der Daemon
hängt sich als weiterer Client dran, stellt dieselben Read-Anfragen und liest
zusätzlich alles mit, was die echte OTA-App abfragt.
Das Protokoll ist aus der Firmware belegt (libuccore.so / libsysi4tbox.so):
| Baustein | Wert | Fundstelle |
|---|---|---|
| Topic-Schema | ota/<produkt>/<empfänger>/<msgName> |
ups_notify, ups_add_method |
<produkt> |
mas861 |
adm_ups_getUpProducutName |
| Endpunkte | 0X701 = tbox_app/sysi, upclientOtaApp = OTA-App |
adm_sysi4tbox_initUP("0X701"), adm_ups_getOtaAppInstallType |
| Envelope | {"sender":…,"msgName":…,"msgInd":…} (msgBody fehlt bei Gettern) |
adm_ups_sendMsg |
| Anfrage / Antwort | get<X>Ind an 0X701 → get<X>Rsp an upclientOtaApp |
msg-id-Tabelle in .data |
Die Kennung
0X7B4, die im IPC-Mitschnitt auftaucht, ist die UE/HMI-Kennung (adm_ups_getUeInstallType) — die OTA-App wird alsupclientOtaAppadressiert.
Der Daemon abonniert ota/mas861/upclientOtaApp/+ und wertet jede passende …Rsp aus
— auch die auf Anfragen der echten OTA-App (msgInd wird bewusst ignoriert, das
macht das passive Mitlesen gratis). Ein result/retVal != 0 im msgBody wird
verworfen (Fehlercode der Firmware, Nutzdaten dann ungültig); ebenso bleibt der
alte Stand samt seinem age_s stehen, wenn eine Antwort kein einziges bekanntes
Feld enthält — Altes wird nie als frisch markiert.
| Feld im JSON | Quelle (msgName) |
msgBody-Feld |
|---|---|---|
carstate.speed |
getCarStateRsp |
carSpeed |
carstate.batt_level |
" | batteryLevel |
carstate.parking_brake |
" | parkingBrake |
carstate.gear |
" | gear (roh; 0xF=P, 0xE=R, D=D, 0=N) |
carstate.engine_rate |
" | engineRate |
carstate.key / .ignition |
" | key / ignitesState |
carstate.charge_state |
" | chargeState |
batt12_pct.value |
getBatteryPowerRsp |
littleBatteryPower (12V in %) |
power_state.value |
getPowerStatusRsp |
type (PowerState 0–5, 4 = IN_OTA) |
net.* |
getNetInfoRsp |
cid lac mcc mnc netType rxLev longitude latitude |
car.vin / .model |
getCarInfoRsp |
vin / model (einmalig abgefragt) |
Die Werte sind roh wie auf dem Bus — bewusst nicht umgerechnet, solange die
Skalierung nicht am Auto gegengeprüft ist. age_s je Block sagt, wie alt die
jeweilige Antwort ist (gear/chargeState etc. gibt es auch aus dbcvpage nicht —
deshalb der Merge).
Sicherheitsleitplanken:
- Nur
get*-Anfragen.setPowerStatus(der „IN_OTA"-Park-Hebel ausSPEC.md) wird nie gesendet — das ist ein Eingriff ins Fahrzeug und gehört nicht in einen Read-Daemon. - Eigene Client-ID (
mqtt_client_id, defaulttboxd). Nicht aufupclientOtaAppsetzen: die Client-ID der Endpunkte ist ihr installType, und MQTT wirft bei doppelter Client-ID den bestehenden Client raus — das wäre die echte OTA-App. Relevant ist das nur in einem Fall:sysiverschickt eine Antwort nur, wenn es den Empfänger für „online" hält (isDstOnlineWhenOnline). Läuft die OTA-App gar nicht, bleiben eigene Anfragen also womöglich unbeantwortet — dann wäre das Verdrängen der letzte Hebel, bewusst und wissend, dass die OTA-App solange weg ist. - Abschaltbar mit
--no-mqtt; nur lauschen, nichts fragen mit--mqtt-no-poll.
Testen ohne Auto: test/mockmqtt.py ist ein Minimal-Broker, der zugleich
tbox_app spielt (antwortet mit dem echten msgBody-Schema):
python3 test/mock301.py 50055 & # 0x301-Kanal
python3 test/mockmqtt.py 7091 & # IPC-Bus
./tbox_daemon --no-conf --no-ap-setup --tbox-port 50055 --mqtt-port 7091 --once
Warum ein Cache (und nicht pro Anfrage frisch lesen)?
Die JSON-Antwort wird sehr wohl pro Anfrage frisch gebaut — gecacht sind nur die Rohwerte aus dem Fahrzeug, weil genau die teuer sind:
- Ein Lesezyklus = 7
dbcvpage-Kommandos, jedes mit eigener TCP-Verbindung zur 0x301-Shell und 6 s Timeout. Hängt die Box, dauert eine Anfrage im Worst Case ~42 s. - Der Daemon ist single-threaded: er würde währenddessen weder
setmode(Keep-Awake) senden noch den WiFi-Watchdog ticken lassen. Ein Client, der alle 2 s pollt, könnte den Watchdog faktisch stilllegen. - Die 0x301-Shell ist eine geteilte Ressource (die Head-Unit nutzt sie auch) — sie pro Client-Anfrage zu hämmern ist keine gute Idee.
Der Client sieht über age_s immer, wie alt die Werte sind (uhrunabhängig, die
TBox-Uhr darf schief stehen), und über ok, ob die Box beim letzten Versuch
erreichbar war. Wer es trotzdem on-demand will: --refresh-max-age N lässt den
Daemon vor dem Antworten frisch lesen, wenn die Daten älter als N Sekunden sind
(default 0 = aus). N begrenzt gleichzeitig die Lese-Rate.
Und die Race-Frage — Anfrage kommt rein, während die Datei geschrieben wird?
Kann nicht passieren: Die Antwort auf :9000 kommt aus dem Speicher, die Datei
wird dafür nie gelesen. Und da alles in einem Thread läuft (Lesezyklus und
:9000 wechseln sich ab), gibt es keinen halb aktualisierten Snapshot — eine
Anfrage sieht immer den kompletten alten oder den kompletten neuen Stand.
Auch ein fremder Leser der Datei kann nichts Halbes erwischen: geschrieben wird
nach *.tmp und dann per rename() atomar umgehängt.
Die Zustandsdatei (default aus)
Weil :9000 sie nicht braucht und sie sonst niemand liest, ist sie seit v1.0.13
abgeschaltet. Eingeschaltet (--state /usrdata/tbox_state.json) gilt:
- Es entsteht eine Datei, kein File pro Zyklus oder Lauf — dieselbe Datei
wird jeden erfolgreichen Zyklus überschrieben (kurzzeitig existiert daneben
…json.tmp, das perrename()drübergezogen wird). - Das sind bei
interval = 20rund 4.300 Flash-Schreibvorgänge pro Tag auf/usrdata. Genau deshalb ist der Default aus. - Sinnvoll ist sie nur zum Debuggen, wenn man nicht im TBox-WLAN hängt und den Stand z.B. per FTP abholen will.
Bauen
Nativ (nur zum Testen auf dem Laptop)
make
Cross für die TBox (armv7, glibc 2.22)
Die TBox ist ein Quectel AG35 (MDM9607, armv7l, glibc 2.22). Cross-Compilen
musst du lokal mit dem Quectel-Toolchain ql-ol-crosstool machen — in der
Cloud-Umgebung liegt kein armv7-Toolchain.
# Toolchain-Umgebung setzen (Pfad je nach deiner Installation):
source /opt/ql-ol-crosstool/ql-ol-crosstool-env-init
# oder direkt den Prefix übergeben:
make cross CROSS=/opt/ql-ol-crosstool/sysroots/x86_64-oesdk-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi-
Ergebnis: tbox_daemon_arm. Dynamisch gegen glibc 2.22 gelinkt — passt zur Box.
Falls es beim Start GLIBC_x.xx not found gibt, mit -static neu bauen:
make cross CROSS=<prefix> CFLAGS="-O2 -Wall -Wextra -std=gnu99 -static"
Deployen
-
Binary auf die Box kopieren (FTP
root:oelinux123@192.168.1.1:21):# z.B. per curl/lftp — Zielordner /usrdata (überlebt Reboots) curl -T tbox_daemon_arm ftp://root:oelinux123@192.168.1.1/usrdata/tbox_daemon -
Auf der Box ausführbar machen und testen (telnet :2323, via fota.sh-Block):
chmod +x /usrdata/tbox_daemon /usrdata/tbox_daemon --once # ein Zyklus, JSON auf stdout -
Dauerbetrieb + auslesen:
/usrdata/tbox_daemon & # im Hintergrund # vom Laptop im TBox-WLAN: curl http://192.168.1.1:9000 # aktueller JSON-Snapshot (aus dem Speicher) # nur falls man die Zustandsdatei ausdrücklich einschaltet (default: keine): /usrdata/tbox_daemon --state /usrdata/tbox_state.json & cat /usrdata/tbox_state.json
Optionen
--interval N Zyklus in s (default 20)
--cutoff N 12V-SoC-Cutoff % (default 70)
--no-keepawake Keep-Awake aus (nur lesen, kein setmode)
--api-port N JSON-Port (default 9000)
--state PATH zusätzlich Zustandsdatei schreiben (default: keine — :9000
antwortet aus dem RAM, das schont den Flash)
--no-state Zustandsdatei aus (Default; überstimmt `state =` aus der Config)
--refresh-max-age N :9000 liest bei älteren Daten frisch nach (0 = aus, default;
blockiert währenddessen Keep-Awake/Watchdog — siehe „Warum ein Cache?")
--batt-ah N 12V-Kapazität Ah für Restlaufzeit (default 45)
--no-wifi-watch WiFi-Watchdog aus
--ap-ip IP AP-Gateway-IP für Liveness-Check (default 192.168.1.1)
--wifi-retry N min. Sekunden zwischen AP-Recover-Versuchen (default 10)
--wlan-prefix S Iface-Präfix des AP-Radios (default wlan) — dessen Existenz ist
der Beleg für ein WARMES Radio
--no-wifi-strict AP-Liveness nur über die AP-IP prüfen (alte Logik; Achtung:
bridge0 trägt die IP auch bei kaltem Radio)
--warmup Radio-Warmlauf (0x20c) nach dem ersten erfolgreichen Read
--warmup-after N s AP-weg, bis 0x20c eskaliert wird (default 60)
--warmup-retry N min. Sekunden zwischen zwei 0x20c-Eskalationen (default 300)
--conf PATH Config-Datei (default /usrdata/tbox_daemon.conf)
--no-conf Config-Datei nicht laden
--no-ap-setup AP beim Start nicht (neu) konfigurieren
--once 1 Zyklus + JSON auf stdout, dann Ende (Test)
--no-mqtt MQTT-Quelle aus (default an: liest den lokalen IPC-Bus)
--mqtt-host IP Broker-IP (default 127.0.0.1)
--mqtt-port N Broker-Port (default 7090)
--mqtt-client-id S eigene MQTT-Client-ID (default tboxd — NICHT
upclientOtaApp: das würde die echte OTA-App rauswerfen)
--mqtt-product S Topic-Präfix ota/<S>/... (default mas861)
--mqtt-self S Endpunkt, dessen Antworten wir lesen (default upclientOtaApp)
--mqtt-peer S Endpunkt, den wir fragen (default 0X701 = tbox_app)
--mqtt-sender S `sender` im Envelope (default upclientOtaApp)
--mqtt-no-poll nichts selbst anfragen, nur passiv mitlesen
--mqtt-debug jede empfangene MQTT-Antwort loggen
CLI-Flags haben Vorrang vor der Config-Datei.
Config-Datei (/usrdata/tbox_daemon.conf, chmod 600)
key = value, #-Kommentare. Hier stehen v.a. deine eigenen AP-Creds:
wifi_ssid = MeinGeheimerAP
wifi_key = SuperSicher2026!
ap_ip = 192.168.1.1
cutoff_soc = 70
interval = 20
keepawake = 1
wifi_watch = 1
wifi_retry = 10
wlan_prefix = wlan # Iface-Präfix des AP-Radios (Beleg für warmes Radio)
wifi_strict = 1 # 0 = nur AP-IP prüfen (alte, blinde Logik)
wifi_warmup = 1 # 0x20c nach dem ersten erfolgreichen Read (fota-Autostart)
wifi_warmup_wait = 15 # s nach 0x20c warten, bevor SSID/Key gesetzt werden
wifi_warmup_after = 60 # s AP-weg, bis 0x20c eskaliert wird
wifi_warmup_retry = 300 # min. s zwischen zwei 0x20c-Eskalationen
wifi_warmup_max = 3 # max. erfolglose Eskalationen am Stück
batt12_ah = 45
api_port = 9000
# state = /usrdata/tbox_state.json # default: keine Datei; nur zum Debuggen setzen
# refresh_max_age = 0 # >0: :9000 liest bei älteren Daten nach
# MQTT-IPC-Bus (default an, Werte = die der echten Firmware)
mqtt = 1
mqtt_host = 127.0.0.1
mqtt_port = 7090
mqtt_client_id = tboxd # NICHT upclientOtaApp — sonst fliegt die echte OTA-App raus
# mqtt_poll = 1 # 0 = nur mitlesen, nichts selbst anfragen
Ist
wifi_ssid/wifi_keyleer, lässt der Daemon die SSID/Key unangetastet und schaltet nur0x203 on. Auth (WPA2) wird nie angefasst — bleibt persistiert.
Beispiel — konservativer Cutoff bei 75 %, längerer Zyklus:
/usrdata/tbox_daemon --cutoff 75 --interval 30 &
Nur lesen, ohne die Box wachzuhalten:
/usrdata/tbox_daemon --no-keepawake &
Autostart via fota.sh
fota.sh ist der Block, über den wir bereits telnet/Shell aktivieren. Dort ans
Ende einen idempotenten Starter hängen (verhindert Doppelstart):
# --- tbox_daemon autostart ---
if [ -x /usrdata/tbox_daemon ] && ! pgrep -f /usrdata/tbox_daemon >/dev/null 2>&1; then
/usrdata/tbox_daemon --cutoff 70 --interval 20 >/usrdata/tbox_daemon.log 2>&1 &
fi
# --- ende ---
Log liegt dann in /usrdata/tbox_daemon.log.
Sicherheit / Grenzen (v1 bewusst schlank)
- Nur Read + Keep-Awake. Keine Fahrzeug-Steuerung (Klima/Fenster/Blinker), kein HMAC — kommt in einer späteren Version.
- Der
setmode-Hebel hält die Box über den 12V-Puffer wach (kein HV-Nachschub im geparkten Zustand). Der Cutoff ist das einzige Sicherheitsnetz — bei zu niedrigem--batt-ahoder falsch skaliertemBCMIBSSOCwürde er zu spät greifen. Konservativ testen, 12V-SoC im JSON im Auge behalten. - Kein Auto-Reset beim Beenden:
setmode 0bleibt gesetzt, wenn der Daemon hart stirbt, bis der nächste Zyklus (oder ein manuellessetmode 3) greift. Das ist so gewollt. :9000ist ungesichert und offen im TBox-WLAN — dort hängt nur dein Laptop dran, aber es ist kein Auth-Layer.