ELAI S.r.l.

Il sensore invia due byte: perché l’AI legge la temperatura sbagliata?

Il sensore invia due byte: perché l’AI legge la temperatura sbagliata? Un esempio svolto chiarisce metodo, ipotesi e limiti.

Il sensore invia due byte: perché l’AI legge la temperatura sbagliata?

Prima dell’AI c’è un contratto sui byte

Un sensore invia due byte e il software li trasforma in una temperatura da dare a un modello AI. Il pacchetto arriva intero, il numero di byte è giusto, ma il modello vede una misura impossibile. Oppure, caso più insidioso, una misura sbagliata ma plausibile. Vogliamo capire quali scelte separano il dato trasmesso dal valore fisico. Costruiremo un protocollo sintetico: non attribuiamo formato, risoluzione o prestazioni a un sensore commerciale o a un dispositivo EL-AI.

Due byte non specificano da soli un intero

Ogni byte contiene otto bit e può rappresentare un codice da 0 a 255. Scegliamo la sequenza esadecimale 9c ff: i byte valgono 156 e 255. In little-endian il primo pesa uno e il secondo 256: u=156+256·255=65436. In big-endian pesa prima il byte alto: u=256·156+255=40191. Nessun bit è cambiato. È cambiato il peso assegnato a ciascuna posizione, come quando leggiamo cifre con un ordine diverso; l’analogia riguarda i pesi, non una regola per indovinare il protocollo.

u_little = b_0 + 256 b_1; u_big = 256 b_0 + b_1

L’ordine dei byte non è l’ordine dei bit dentro ogni byte e non indica ancora se il numero possa essere negativo. Per un intero con segno a sedici bit in complemento a due, il codice u inferiore a 32768 rappresenta u; altrimenti rappresenta u−65536. La scelta è parte del formato, non una correzione da applicare soltanto quando una lettura sembra strana. Con i nostri byte otteniamo −100 in little-endian con segno e −25345 in big-endian con segno.

v = u if u<2^15, otherwise u−2^16

Dal codice al valore fisico c’è ancora una scala

Nel protocollo ipotetico, un conteggio vale 0,01 °C e l’offset è zero: T=s·v+o. s ha unità gradi Celsius per conteggio; v è il codice intero interpretato; o è un offset in gradi. I byte 9c ff letti correttamente danno −1 °C. Cambiando ordine e segno otteniamo invece 654,36, −253,45 o 401,91 °C. Non sono quattro misure di un ambiente: sono quattro interpretazioni della stessa sequenza. Il modello AI a valle non può sapere automaticamente quale contratto era previsto.

FormatoInteroTemperatura (°C)
<h-100-1.0
<H65436654.36
>h-25345-253.45
>H40191401.91
Gli stessi byte 9c ff producono quattro numeri secondo ordine e segno. Le barre non sono quattro temperature misurate: sono quattro interpretazioni di un protocollo sintetico.
Gli stessi byte 9c ff producono quattro numeri secondo ordine e segno. Le barre non sono quattro temperature misurate: sono quattro interpretazioni di un protocollo sintetico.

La notazione Python rende esplicite queste scelte: < h è un intero standard con segno a due byte little-endian, > h lo stesso big-endian, H la variante senza segno. Il prefisso nativo @ dipende dalla piattaforma e può includere regole di allineamento nei formati composti. Per uno scambio esterno è preferibile dichiarare il contratto invece di presumere che la macchina del produttore e quella del consumatore condividano convenzioni. La documentazione consultata e la versione Python eseguita sono distinte nell’archivio.

La plausibilità può lasciare passare un errore

Supponiamo un intervallo operativo ipotetico da −40 a 125 °C: tre interpretazioni del primo esempio vengono respinte. Ma con i byte 01 02, due letture con segno producono 5,13 °C in little-endian e 2,58 °C in big-endian. Entrambe rientrano nell’intervallo. Uno scambio di byte può quindi non generare un allarme e alterare comunque la decisione AI. Il controllo di range è utile ma non dimostra che il formato sia quello giusto; deve affiancare esempi noti dal produttore e verifiche del protocollo.

Anche l’integrità dei byte è una proprietà diversa. Un checksum può confermare che la sequenza ricevuta coincide con quella inviata senza stabilire se interpretiamo correttamente segno, scala o unità. Non abbiamo eseguito una prova di trasmissione o un calcolo di checksum in questo esperimento. La distinzione è logica: preservare il messaggio non equivale a preservarne il significato quando il contratto del destinatario è diverso.

Stato del sensore e temperatura non sono lo stesso tipo di dato

Un protocollo può riservare un codice per “misura non disponibile”. Nel nostro esempio scegliamo 0x7fff, cioè byte ff 7f nell’ordine previsto, come sentinella ipotetica: non va passato all’AI come 327,67 °C. La funzione decode verifica la lunghezza di due byte, controlla la sentinella e soltanto dopo interpreta segno e scala. Restituisce None per lo stato mancante. Non sostituiamo automaticamente quel dato con zero: zero gradi sarebbe una misura valida e diversa dall’assenza.

Lo script confronta inoltre la formula manuale, int.from_bytes e struct per tutti i 65536 codici a sedici bit con segno. Questa enumerazione verifica la conversione nel dominio scelto; non prova che un sensore reale usi quel protocollo, che il cavo sia affidabile o che le letture siano calibrate. L’operazione su due byte ha costo costante nel modello, ma non riportiamo latenza, memoria, energia o risultati su microcontrollori. Il test è stato eseguito su host con Python 3.14.0.

Il primo modello da verificare è quello del messaggio

La temperatura può essere sbagliata prima che la rete inizi il calcolo. Ordine dei byte, segno, scala, offset e codici di stato devono essere espliciti e versionati. Per collegare un sensore a un modello embedded serve confrontare byte noti e valori fisici attesi lungo tutto il percorso, poi misurare la piattaforma reale. Un’AI più accurata non ripara da sola un contratto di acquisizione interpretato al contrario.

Python struct — Byte order, standard size and alignment.

import struct,json,sys
from pathlib import Path
raw=bytes.fromhex('9c ff');rows=[]
for order in ['little','big']:
 for signed in [True,False]:
  value=int.from_bytes(raw,byteorder=order,signed=signed);rows.append(dict(order=order,signed=signed,count=value,temperatureC=.01*value))
assert int.from_bytes(raw,'little',signed=True)==-100
def decode(data):
 if len(data)!=2:raise ValueError('Expected exactly two bytes')
 u=int.from_bytes(data,'little',signed=False)
 if u==0x7fff:return None
 signed=u if u<32768 else u-65536
 return .01*signed
assert decode(raw)==-1 and decode(bytes.fromhex('ff 7f')) is None
try:decode(b'')
except ValueError:pass
else:raise AssertionError('Invalid length accepted')
for value in range(-32768,32768):
 data=struct.pack('<h',value);u=data[0]+256*data[1];manual=u if u<32768 else u-65536
 assert struct.unpack('<h',data)[0]==value==manual==int.from_bytes(data,'little',signed=True)
ambiguous=bytes.fromhex('01 02');both=[.01*int.from_bytes(ambiguous,x,signed=True) for x in ['little','big']]
assert both==[5.13,2.58]
r=dict(python=sys.version.split()[0],seed=None,scope='Hypothetical signed 16-bit sensor protocol, not a real sensor datasheet or device measurement',wireHex=raw.hex(' '),scaleCPerCount=.01,rows=rows,ambiguousHex=ambiguous.hex(' '),bothPlausibleC=both,exhaustiveCodesChecked=65536,reservedExample='0x7fff hypothetically reserved: must decode status rather than a numeric temperature')
Path(__file__).with_name('results.json').write_text(json.dumps(r,indent=2),encoding='utf8')

Codice, dati e istruzioni · JSON. Calcoli didattici eseguiti con Python 3.14.0; figure con Matplotlib 3.11.2. Analisi con assistenza AI, senza dichiarare peer review o revisione umana. Copertina originale ImageGen, illustrativa: non documenta persone, sedi o installazioni EL-AI. Fonti consultate il 25 ottobre 2026.