Leggere da iPhone la batteria ibrida di una Lexus CT200h

Un'app iOS che legge la batteria ibrida di una Lexus CT200h tramite un adattatore diagnostico Bluetooth da pochi euro: come si interroga la centralina della batteria, come si decodificano tensioni, correnti e temperature, e come si comanda la ventola in sicurezza.

iOSSwiftOBD-IIBLECAN
Indice
  1. Perché guardare la batteria
  2. La catena di comunicazione
  3. Preparare l'adattatore
  4. Risposte lunghe: più frame per un messaggio
  5. Tenere aperta la sessione
  6. Un comando alla volta
  7. Da byte a numeri
  8. Il segno della corrente
  9. Le temperature e i 10 gradi di scarto
  10. La ventola
  11. Altre auto, una sola verificata
  12. Limiti

In breve

  • La centralina della batteria ibrida conosce tensioni, corrente, carica e temperature, ma l'auto non le mostra. Con un adattatore da pochi euro nella presa diagnostica si possono leggere.
  • L'app chiede i dati alla centralina con comandi diagnostici e riceve pacchetti di byte. Il lavoro vero è capire come trasformare quei byte in numeri corretti.
  • Un numero sbagliato ma plausibile è l'errore peggiore: le temperature mi risultavano 10 °C più alte del reale, e niente lo segnalava.
  • L'app può anche comandare la ventola di raffreddamento della batteria, ma nelle modalità normali non può mai raffreddare meno di quanto farebbe l'auto da sola.
  • Se l'app smette di comunicare, entro un paio di secondi la ventola torna sotto il controllo dell'auto.

La Lexus CT200h è una compatta ibrida, parente stretta della Toyota Prius. La sua batteria di trazione è un pacco al nichel-metallo idruro (NiMH) sotto il pianale del bagagliaio, raffreddato da una ventola che aspira aria dall'abitacolo. La centralina della batteria misura continuamente tensioni, corrente, stato di carica e temperature, ma nessuna schermata di bordo le mostra. Su Android esistono app che le leggono; io volevo un'app iOS che mostrasse i dati della mia auto e mi permettesse anche di regolare la ventola.

L'hardware è minimo: un adattatore Bluetooth Low Energy basato su ELM327 (il chip su cui si basano quasi tutti gli adattatori diagnostici economici) inserito nella presa OBD-II, quella che usano le officine per la diagnosi. L'app è scritta in Swift e SwiftUI.

Perché guardare la batteria

Il pacco è composto da 28 moduli in serie, che la centralina misura a coppie: 14 blocchi da circa 14,4 V nominali, per un totale di circa 200 V. Una batteria NiMH invecchia soprattutto con il calore, e quando invecchia i blocchi smettono di comportarsi allo stesso modo: uno cala di tensione sotto carico più degli altri, oppure la sua resistenza interna cresce. Guardare i 14 blocchi uno accanto all'altro, e la temperatura del pacco mentre si guida, dice molto più della spia sul cruscotto, che si accende quando il problema è già serio.

Per chi non è del mestiere. Immaginate 14 pile collegate in fila. Finché sono tutte uguali la batteria lavora bene. Quando una comincia a cedere, tutto il pacco lavora male, e l'auto se ne accorge tardi. L'app serve a vedere le 14 pile una per una.

La catena di comunicazione

Tra il telefono e la batteria ci sono due tratti di comunicazione molto diversi.

iPhone app in Swift Bluetooth Low Energy comandi di testo, come su una seriale ELM327 nella presa OBD-II bus CAN, 500 kbit/s 7E2 → richiesta 7EA ← risposta Centralina batteria gestisce il pacco ad alta tensione
Il telefono parla con l'adattatore in testo; l'adattatore traduce in frame CAN verso la centralina. 7E2 e 7EA sono gli indirizzi CAN di richiesta e risposta.

Il primo tratto è Bluetooth Low Energy. L'ELM327 nasce come interprete di comandi su una porta seriale; in BLE la seriale diventa una coppia di caratteristiche GATT, una su cui scrivere e una da cui ricevere notifiche. Adattatori diversi usano identificativi di servizio diversi, quindi l'app cerca tra alcune coppie note quella che ha una caratteristica scrivibile e una che invia notifiche.

Il secondo tratto è il bus CAN, la rete con cui comunicano le centraline dell'auto. L'adattatore riceve dal telefono comandi di testo, li trasforma in frame CAN, li invia alla centralina e restituisce la risposta come testo esadecimale.

Preparare l'adattatore

All'avvio l'app configura l'adattatore con una sequenza fissa di comandi AT, che sono documentati nel datasheet dell'ELM327:

ComandoA cosa serve
ATWSriavvio dell'adattatore
ATE0niente eco dei comandi nella risposta
ATSP6protocollo ISO 15765-4: CAN con identificativi a 11 bit, 500 kbit/s
ATAT1tempi di attesa adattivi
ATH1mostra l'indirizzo di chi risponde
ATL0, ATS0niente a capo e niente spazi: risposte più corte da interpretare
ATCAF1l'adattatore gestisce da solo la formattazione dei frame CAN

Poi, prima di ogni richiesta alla centralina della batteria, l'app imposta l'indirizzo di destinazione (7E2) e lo stesso indirizzo per i messaggi di controllo del flusso, che servono quando la risposta è lunga.

Risposte lunghe: più frame per un messaggio

Un frame CAN trasporta al massimo 8 byte di dati. Una richiesta come «dammi le tensioni dei blocchi» sta comodamente in un frame, ma la risposta, con 14 tensioni da 2 byte ciascuna, no. Lo standard ISO 15765-2 (ISO-TP) spezza i messaggi lunghi in più frame: il primo dice quanto è lungo il messaggio, chi riceve risponde «manda pure il resto», e i frame successivi sono numerati.

Ogni richiesta è formata da un modo e da un PID, l'identificativo del dato richiesto. Lo schema è questo. I byte indicati con xx sono dati; lunghezze e contenuti sono illustrativi:

→ 7E2  02 21 81                    richiesta: modo 21, PID 81
← 7EA  10 LL 61 81 xx xx xx xx     primo frame: il messaggio è lungo LL byte
→ 7E2  30 00 00                    controllo di flusso: invia il resto
← 7EA  21 xx xx xx xx xx xx xx     frame successivo n. 1
← 7EA  22 xx xx xx ...             frame successivo n. 2

Il 61 81 all'inizio della risposta è la conferma: la risposta positiva a una richiesta di modo 21 ha codice 61, seguito dal PID richiesto. L'ELM327 manda da solo il controllo di flusso e restituisce i frame uno per riga; l'app toglie i byte di servizio, controlla che la risposta inizi con la conferma giusta e ricompone il contenuto.

Tenere aperta la sessione

La centralina chiude la sessione diagnostica se per qualche secondo non riceve nulla. Per questo l'app invia ogni 2,5 secondi un messaggio tester present (3E 00), che nello standard diagnostico UDS significa semplicemente «sono ancora qui».

Un comando alla volta

L'adattatore gestisce un solo comando alla volta, ma nell'app le richieste arrivano da tre parti indipendenti: il ciclo di lettura dei dati, il tester present e il controllo della ventola.

L'accesso all'adattatore passa da un actor Swift, un costrutto che garantisce che il suo codice non venga eseguito da due flussi contemporaneamente. Non basta, però. Un actor si sospende a ogni punto di attesa, per esempio mentre aspetta la risposta dell'adattatore, e in quel momento può iniziare a eseguire un'altra richiesta. Una sequenza completa è fatta di più passi: imposto l'indirizzo, invio la richiesta, aspetto la risposta. Se un'altra sequenza si infila a metà, l'adattatore riceve comandi mescolati. Il sintomo era un errore di scrittura intermittente, «busy», difficile da riprodurre.

La soluzione è una coda esplicita: ogni sequenza completa prende un «turno» prima di iniziare e lo rilascia solo alla fine; chi arriva nel frattempo aspetta in fila. È lo stesso principio del numerino al banco dei salumi: il banco è uno solo, e chi prende il numero viene servito per intero prima del successivo.

Da byte a numeri

Questi sono i dati che l'app chiede alla centralina. Il modo 21 è un modo diagnostico specifico di Toyota, non standard OBD-II:

Modo + PIDContenuto
2181tensione dei 14 blocchi, batteria ausiliaria a 12 V, tensione del pacco
2187temperature: aria in ingresso e sonde nel pacco
2195resistenza interna dei blocchi
2198corrente, limiti di potenza in carica e scarica, stato di carica minimo e massimo
2101stato di carica
219Bstato della ventola

Per le formule sono partito dalle tabelle di PID che la community usa per la Prius di terza generazione, che ha lo stesso sistema ibrido della CT200h. Le ho confrontate tra loro e, soprattutto, con le letture sulla mia auto.

La maggior parte dei valori sono parole a 16 bit big-endian: due byte, il primo è la parte alta. La tensione di un blocco, per esempio:

byte ricevuti        2E 14
valore grezzo        0x2E14 = 46 × 256 + 20 = 11.796
tensione             11.796 / 819,2 ≈ 14,40 V

Nelle fonti la stessa formula compare in forme diverse: grezzo × 79,99 / 65.535, oppure grezzo / 819,2, oppure grezzo × 0,122 / 100 arrotondato. Sono equivalenti a meno dello 0,1%: 65.535 / 80 ≈ 819,2, cioè il fondo scala è di circa 80 V. Tre fonti diverse che danno lo stesso risultato sono un buon segno.

Lo stato di carica è un solo byte, con fondo scala 255 = 100%. I limiti di potenza in 2198 sono byte con un passo di 0,5 kW e uno scostamento di −64 kW.

Ogni decodifica controlla la lunghezza della risposta e scarta i valori fuori da un intervallo plausibile invece di mostrarli: un blocco NiMH deve stare tra 5 e 25 V, la batteria ausiliaria a 12 V tra 0 e 20 V, il pacco tra 100 e 400 V. Un valore fuori intervallo indica quasi sempre un errore di decodifica, non una batteria anomala. L'app conserva anche i byte grezzi di ogni risposta, così la decodifica si può rifare in seguito.

Il segno della corrente

La corrente arriva come valore a 16 bit centrato su 0x8000: sottraendo 32.768 e dividendo per 100 si ottengono ampere, con risoluzione di un centesimo. Nella convenzione Toyota il valore è positivo quando la batteria si scarica.

L'app inverte il segno, perché per chi guarda è più naturale leggere «+» quando la batteria si ricarica. Una scelta del genere va verificata, non assunta. L'ho controllata su una registrazione reale: mentre il motore termico ricaricava il pacco e lo stato di carica saliva dal 38 al 50%, il valore grezzo restava sotto il centro, cioè corrente in ingresso. E il picco di corrente in ingresso coincideva con una frenata rigenerativa, come doveva essere.

Le temperature e i 10 gradi di scarto

Le temperature sono il punto in cui è più facile sbagliare. La lettura più ovvia prende un byte per sonda e sottrae 40, la convenzione più comune nell'OBD-II standard. Con questa lettura i numeri sono plausibili, 30, 35, 38 gradi, ma circa 10 °C più alti del reale. Su questo punto le fonti pubbliche non sono d'accordo tra loro, ed è per questo che l'ho verificato a fondo.

In realtà ogni temperatura è una parola a 16 bit con un'altra scala:

temperatura = grezzo × 255,9 / 65.535 − 50

Il motivo dei 10 gradi esatti si vede facendo i conti. Moltiplicare per 255,9 / 65.535 equivale quasi esattamente a dividere per 256, cioè a prendere solo il byte alto. La formula giusta è quindi, in pratica, «byte alto − 50». La convenzione standard legge lo stesso byte e sottrae 40. Un esempio:

byte ricevuti        4E 07
formula corretta     19.975 × 255,9 / 65.535 − 50 ≈ 28,0 °C
convenzione OBD-II   0x4E = 78,  78 − 40 = 38 °C

È il tipo di errore più insidioso: niente crash, niente valori assurdi, solo un numero sbagliato che sembra giusto. Per questo, per ogni grandezza, controllo due cose: che le fonti siano coerenti tra loro e che l'andamento abbia senso rispetto a quello che fa l'auto. La corrente deve cambiare segno tra carica e scarica, la temperatura deve salire sotto carico e scendere quando la ventola gira forte.

La ventola

Oltre a leggere, l'app può comandare la ventola di raffreddamento della batteria, con un comando diagnostico che chiede alla centralina di forzare una delle 6 velocità disponibili. È l'unica parte in cui un errore ha conseguenze sull'auto, e l'ho progettata partendo da quello che può andare storto.

La centralina riprende il controllo da sola

Dalle registrazioni sull'auto si vede che la centralina mantiene la velocità comandata solo per circa 2 secondi dopo l'ultimo comando, poi torna alla sua gestione automatica: nei log la velocità scendeva da 6 a 3 a 1. Per mantenere una velocità l'app ripete quindi il comando ogni 1,5 secondi.

Questo comportamento è anche la protezione più importante. Se il telefono si blocca, se il Bluetooth cade o se l'app viene chiusa, i comandi smettono di arrivare e dopo un paio di secondi la ventola torna sotto il controllo dell'auto. Non esiste uno stato in cui l'app lascia la ventola bloccata.

Un comando viene considerato applicato solo se la centralina risponde con una conferma positiva. Se risponde con un rifiuto, o non risponde, l'app non considera la velocità applicata.

Le modalità

ModalitàCosa faPuò raffreddare meno dell'auto?
Automaticala ventola la gestisce l'auto; l'app non invia comandino
Velocità minimauna velocità fissa scelta dall'utente, alzata dalla curva di sicurezza se serveno
Curva protettauna curva temperatura → velocità scelta dall'utente, con la stessa soglia minimano
Curva liberala curva dell'utente, senza soglia minimasì, modalità esperta
Manualeuna velocità fissa, senza protezionisì, modalità esperta

Le due modalità esperte si sbloccano solo dopo un avviso e una doppia conferma. Se le si blocca di nuovo mentre una di esse è attiva, l'app torna alla curva protetta.

La curva di sicurezza

Nelle modalità protette la velocità comandata non scende mai sotto quella di una curva di sicurezza, calcolata sulla temperatura più alta tra le sonde del pacco. La curva parte dalle soglie documentate dal costruttore ed è un po' più prudente nella parte alta: velocità 1 da 36 °C, 2 da 39, 3 da 42, 4 da 44, 5 da 46 e 6 da 48 °C.

La regola è una sola: la velocità effettiva è la più alta tra quella chiesta dall'utente e quella della curva di sicurezza. L'utente può quindi solo aggiungere raffreddamento, mai toglierne.

curva di sicurezza curva utente (esempio) 0 1 2 3 4 5 6 30 35 40 45 50 temperatura massima del pacco (°C) velocità ventola
La curva utente dell'esempio fa partire la ventola prima, ma sopra i 42 °C è più pigra della curva di sicurezza. L'app usa in ogni punto la più alta delle due.

L'isteresi

Se la temperatura oscilla attorno a una soglia, per esempio tra 41,9 e 42,1 °C, una regola ingenua cambierebbe velocità a ogni lettura. Per evitarlo c'è un'isteresi di 2 °C: la velocità sale appena la temperatura supera la soglia, ma scende solo quando la temperatura è di nuovo 2 gradi sotto. Nell'esempio, la velocità 3 si attiva a 42 °C e resta attiva finché il pacco non scende sotto i 40 °C.

È lo stesso principio del termostato di casa, che non accende e spegne la caldaia ogni volta che la temperatura passa di un decimo sopra o sotto quella impostata.

Altre auto, una sola verificata

Visto che il protocollo della CT200h è lo stesso della Prius di terza generazione, ho aggiunto dei profili per altri ibridi Toyota e Lexus. L'app riconosce l'auto dal numero di telaio (VIN). Se non basta, interroga la centralina batteria di ogni profilo con un PID di prova e controlla la risposta: una risposta corretta conferma che si sta parlando davvero con quella centralina, anche se non sempre distingue due modelli della stessa famiglia. In quel caso l'interfaccia mostra un «?» e lascia scegliere a mano.

L'unico profilo provato su un'auto reale è la CT200h. Gli altri sono verificati sulle fonti, non sulle auto. Per questi l'app mostra un avviso e privilegia il monitoraggio; per le famiglie più recenti (piattaforma TNGA, come il SUV Lexus UX250h) le fonti non concordano nemmeno sul comando della ventola, che quindi resta disattivato. Per completare queste verifiche l'app può esportare tutti i frame OBD grezzi: basta un giro con l'auto giusta per confermare o correggere un profilo.

Limiti

  • È un'app personale, non uno strumento diagnostico certificato.
  • Alcune costanti di scala possono cambiare tra una calibrazione della centralina e l'altra: per questo conservo i byte grezzi.
  • La conferma dei profili diversi dalla CT200h richiede prove sulle rispettive auto.
  • Il codice non è pubblico.

← Tutti gli articoli