Reverse engineering del protocollo IR (HITACHI_AC 28 byte) del telecomando RAR-6NE1 e custom component ESPHome bidirezionale (TX + RX) per Home Assistant. - esphome/: custom component hitachi_rar6ne1 (climate_ir::ClimateIR) + config - src/: firmware Arduino di cattura IR con web UI (strumento di diagnostica) - README.md: documentazione completa (protocollo, decode, checksum, gotcha) - Segreti esclusi dal versionamento (vedi *.example e .gitignore) - Licenza GPL-3.0 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
233 lines
17 KiB
Markdown
233 lines
17 KiB
Markdown
# Hitachi RAR-6NE1 — Reverse Engineering protocollo IR per ESPHome
|
||
|
||
## Obiettivo
|
||
Decodificare il protocollo IR del telecomando Hitachi **RAR-6NE1** (compatibile con unità interne RAK-15QPE/18RPE/25RPE/35RPE/42RPE/50RPE) per realizzare un climate control via ESP32 + ESPHome.
|
||
|
||
## Stato attuale
|
||
|
||
> **2026-06-30 — Implementazione ESPHome realizzata e funzionante in bidirezionale.**
|
||
> Il condizionatore si comanda da Home Assistant (TX) e lo stato si sincronizza
|
||
> quando si usa il telecomando fisico (RX). Vedi la sezione **[Implementazione
|
||
> ESPHome (fase finale)](#implementazione-esphome-fase-finale)** in fondo per
|
||
> architettura, file e i "gotcha" risolti.
|
||
|
||
Reverse engineering **sostanzialmente completo** sul lato protocollo. Mappatura solida di tutti i campi principali (power, mode, temp, fan, swing, silent, powerful) e **algoritmo di checksum determinato e verificato al 100%** — coincide esattamente con `IRHitachiAc::calcChecksum()` della libreria IRremoteESP8266: il telecomando RAR-6NE1 è quindi compatibile byte-per-byte e bit-per-bit con quella classe, non solo strutturalmente. Possiamo generare comandi custom validi da zero.
|
||
|
||
L'ipotesi "frame periodico di temperatura ambiente dal telecomando" (sensore IR lato trasmettitore) è stata **verificata e scartata**: con la web UI di monitoraggio (vedi sotto) si è osservato un periodo di **~80 minuti di silenzio totale a telecomando fermo, con unità accesa**, e tutti i frame ricevuti sono risultati essere comandi da pressione tasto. Il telecomando **non invia trasmissioni spontanee periodiche**.
|
||
|
||
L'implementazione finale ESPHome **non** ha riusato la classe `IRHitachiAc`: è un
|
||
custom component che estende `climate_ir::ClimateIR` e costruisce i 28 byte +
|
||
checksum a mano (più portabile, nessuna dipendenza da IRremoteESP8266). Dettagli
|
||
nella sezione *Implementazione ESPHome (fase finale)* in fondo.
|
||
|
||
## File in questa cartella
|
||
|
||
| File | Scopo |
|
||
|---|---|
|
||
| `platformio.ini` + `src/main.cpp` | Progetto PlatformIO. Lo sketch cattura i segnali IR (ricevitore su **GPIO27**, ESP32 DevKitC) e li **logga in una web UI** accessibile in WiFi, con timestamp NTP, decodifica a 28 byte on-device e verifica checksum. Pensato per lasciare l'ESP vicino al condizionatore e osservare i frame nel tempo (vedi sezione "Monitor web"). Logga anche su seriale (formato `IRrecvDumpV2`) se collegato via USB. |
|
||
| `src/secrets.h` | Credenziali WiFi + hostname mDNS + timezone per lo sketch PlatformIO (formato C, da compilare). **Distinto da `secrets.yaml`.** |
|
||
| `hitachi-ir-dump.yaml` | Config ESPHome alternativa per dump raw via `remote_receiver` (`dump: all`). **Nota**: ESPHome non ha un decoder Hitachi generico in `remote_base` (il decode Hitachi esiste solo dentro i climate component `hitachi_ac344`/`hitachi_ac424`), quindi questo file produce solo raw non interpretato — usare il progetto PlatformIO per il riconoscimento automatico del protocollo. |
|
||
| `secrets.yaml` | Credenziali WiFi per la config ESPHome (placeholder, da compilare). |
|
||
|
||
## Monitor web (osservazione frame nel tempo)
|
||
|
||
Lo sketch `src/main.cpp` espone una web UI per registrare ogni trama IR ricevuta — utile per lasciare l'ESP32 vicino al condizionatore e verificare, ad esempio, se il telecomando (che ha un sensore di temperatura integrato) invia frame periodici "spontanei" all'unità.
|
||
|
||
**Setup:**
|
||
1. Compila `src/secrets.h` con SSID/password della rete WiFi (la stessa del PC da cui interroghi l'endpoint).
|
||
2. `pio run -t upload` con ESP collegato via USB, poi sposta l'ESP vicino al condizionatore (alimentato).
|
||
3. Apri `http://hitachi-ir.local/` (mDNS) o `http://<IP>/` (l'IP è stampato sul seriale al boot).
|
||
|
||
**Endpoint:**
|
||
| Endpoint | Risposta |
|
||
|---|---|
|
||
| `GET /` | Pagina HTML con tabella auto-refresh (ogni 3s): orario, Δ dal frame precedente, bits, rawlen, checksum OK/BAD, campi decodificati (power/mode/temp/fan/swing/silent/powerful/tasto) e i 28 byte. |
|
||
| `GET /api/captures` | JSON riassuntivo di tutte le catture nel ring buffer (ultime 30) + uptime/totale/IP. Per polling automatico. |
|
||
| `GET /api/raw?i=N` | Array raw in microsecondi della cattura con sequenza `N` (per ri-analizzare frame anomali che non decodificano a 28 byte). |
|
||
| `GET /api/clear` | Svuota il buffer. |
|
||
|
||
Il ring buffer tiene le ultime 30 catture in RAM (si perde al reset). Ogni cattura conserva anche il raw grezzo (fino a 460 valori) così i frame di lunghezza/struttura inattesa non vanno persi.
|
||
|
||
## Perché non è un protocollo "già supportato"
|
||
|
||
- Verificato su esphome.io: i soli climate component Hitachi nativi sono `hitachi_ac344` e `hitachi_ac424`.
|
||
- Verificato su `ir_Hitachi.h` (libreria IRremoteESP8266): i modelli di telecomando documentati per ciascuna variante (AC1, AC2, AC3, AC264, AC296, AC344, AC424) **non includono RAR-6NE1** né le sue varianti (RAR-6N1/6N2/6N3/6NE4/6N5).
|
||
- Empiricamente: lo sketch `IRrecvDumpV2`, che prova il decode con tutti i protocolli noti, riconosce solo *alcune* trame (es. lo stato OFF) come `HITACHI_AC` valido; la maggior parte risulta `UNKNOWN`.
|
||
|
||
## Metodo di decode (validato)
|
||
|
||
Le trame, una volta corrette per un bug di cattura ricorrente, risultano essere il protocollo **`HITACHI_AC` standard a 28 byte (224 bit)** — la classe più "base" della libreria (distinta da AC264/296/344/424) — con codifica a distanza:
|
||
|
||
- Header: mark ~3300-3400µs, space ~1700µs
|
||
- Bit: mark costante ~400µs, space `526µs`→bit `0`, `1296µs`→bit `1`
|
||
- 28 byte totali, MSB-first per byte, ordine byte diretto (nessuna inversione)
|
||
|
||
### Bug di cattura da correggere sempre
|
||
Molti dump (in particolare il primo dopo un periodo di idle) **perdono il mark iniziale lungo dell'header** (~3300-3400µs), registrando come primo valore solo la sua space (~1700µs). Risultato: l'array raw appare "shiftato" di una posizione.
|
||
|
||
**Come riconoscerlo:** guarda il primo valore del raw array.
|
||
- Se è **> 3000µs** → header completo, **scarta i primi 2 valori** (mark+space header) prima di accoppiare il resto.
|
||
- Se è **< 2000µs** (tipicamente ~1650-1720) → header tagliato, **scarta solo il primo valore**.
|
||
|
||
Poi accoppia i valori restanti `(mark, space)`: bit = `1` se `space > 800`, altrimenti `0`. Bit-pack MSB-first in byte, 28 byte totali.
|
||
|
||
Questo metodo è stato calibrato e validato contro una trama riconosciuta nativamente come `HITACHI_AC` dalla libreria (stato OFF, con checksum verificato dalla libreria stessa) — non è una supposizione.
|
||
|
||
## Mappatura campi (stato a 28 byte, indice da 0)
|
||
|
||
| Campo | Byte | Valori osservati | Note |
|
||
|---|---|---|---|
|
||
| Power | `[17]` bit0 | `0x00`=OFF, `0x01`=ON | Confermato, coincide col layout documentato in `ir_Hitachi.h` |
|
||
| Mode | `[10]` | Cool=`0x20`, Fan-only=`0x30`, Auto=`0x40`, Dry=`0xA0`, Heat=`0xC0` | Confermato con test isolato (solo mode cambiato) |
|
||
| Temp | `[11]` | 26°C=`0x2C`, 25°C=`0x4C` | Azzerato a `0x01` in Fan-only (temp non applicabile) |
|
||
| Fan speed | `[13]` | `0x40`=1/4, `0xC0`=2/4, `0x20`=3/4, `0xA0`=4/4, `0x80`=Auto | Confermato con test isolato (sequenza 3→2→1→Auto→4→3). Dry forza automaticamente `0xC0`. |
|
||
| Checksum | `[27]` | Cambia ad ogni trasmissione | **Algoritmo determinato e verificato su tutti i 18 stati raccolti finora (match perfetto, nessuna eccezione)** — è esattamente `IRHitachiAc::calcChecksum()` della libreria IRremoteESP8266 (`ir_Hitachi.cpp:173-178`). Vedi sezione dedicata sotto. |
|
||
| Swing Verticale | `[14]` | `0xE0`=ON, `0x60`=OFF | Confermato con test isolato (sequenza V+H→solo H→OFF→solo V→V+H) |
|
||
| Swing Orizzontale | `[15]` | `0xE0`=ON, `0x60`=OFF | Confermato con lo stesso test |
|
||
| Silent | `[26]` bit4 (`0x10`) | `0x10`=ON, `0x00`=OFF | Confermato con test isolato ON→OFF |
|
||
| Powerful | `[25]` bit2 (`0x04`) | `0x34`=ON, `0x30`=OFF | Confermato con test isolato ON→OFF |
|
||
| `[9]` | — | `0xE0` (idle/solo mode), `0xC0` (tasto power on/off), `0xB0` (tasto swing V), `0x70` (tasto swing H), `0x30` (tasto fan speed), `0x38` (tasto Silent), `0xA8` (tasto Powerful) | **Non è uno stato persistente**: codifica il *tasto/categoria appena premuto* sul telecomando, non il risultato — resta identico tra ON e OFF dello stesso tasto (es. `0x38` sia per Silent ON che OFF, `0xC0` sia per power ON che OFF). Confermato su 15+ trasmissioni isolate (swing, fan, silent, powerful, power). |
|
||
|
||
## Algoritmo di checksum (confermato)
|
||
|
||
Coincide esattamente con la classe `IRHitachiAc` standard della libreria IRremoteESP8266 (`ir_Hitachi.cpp`, righe 173-178). Verificato in Python su tutti gli 18 stati raccolti (baseline mode/temp/power, swing, fan speed) — match perfetto al 100%, nessuna eccezione:
|
||
|
||
```python
|
||
def reverse_bits(x, nbits=8):
|
||
r = 0
|
||
for i in range(nbits):
|
||
r |= ((x >> i) & 1) << (nbits - 1 - i)
|
||
return r
|
||
|
||
def calc_checksum(state): # state = 28 byte, l'ultimo e' il checksum stesso
|
||
s = 62
|
||
for b in state[:-1]: # primi 27 byte
|
||
s = (s - reverse_bits(b, 8)) & 0xFF
|
||
return reverse_bits(s, 8)
|
||
```
|
||
|
||
Implicazione: possiamo generare comandi custom mai catturati (es. combinazioni nuove di mode/temp/fan/swing) calcolando il checksum con questa formula, senza dover fare solo replay di raw catturati. Probabile che la classe `IRHitachiAc` esistente nella libreria possa essere riusata direttamente (o con un wrapper minimale) per l'implementazione ESPHome finale, anziché scrivere un protocollo custom da zero — da verificare.
|
||
|
||
### Stati di riferimento catturati (esadecimale, 28 byte)
|
||
|
||
```
|
||
OFF : 80 08 0C 02 FD 80 7F 88 48 C0 20 2C 00 20 E0 E0 00 00 00 00 00 00 00 01 80 30 00 40
|
||
ON (26C cool, fan 3/4): 80 08 0C 02 FD 80 7F 88 48 C0 20 2C 00 20 E0 E0 00 01 00 00 00 00 00 01 80 30 00 41
|
||
Cool 25°C : 80 08 0C 02 FD 80 7F 88 48 90 20 4C 00 20 E0 E0 00 01 00 00 00 00 00 01 80 30 00 7E
|
||
Fan only : 80 08 0C 02 FD 80 7F 88 48 E0 30 01 00 20 E0 E0 00 01 00 00 00 00 00 01 80 30 00 54
|
||
Auto : 80 08 0C 02 FD 80 7F 88 48 E0 40 2C 00 20 E0 E0 00 01 00 00 00 00 00 01 80 30 00 01
|
||
Heat : 80 08 0C 02 FD 80 7F 88 48 E0 C0 2C 00 20 E0 E0 00 01 00 00 00 00 00 01 80 30 00 FE
|
||
Dry : 80 08 0C 02 FD 80 7F 88 48 E0 A0 2C 00 C0 E0 E0 00 01 00 00 00 00 00 01 80 30 00 7E
|
||
Cool (return): 80 08 0C 02 FD 80 7F 88 48 E0 20 2C 00 20 E0 E0 00 01 00 00 00 00 00 01 80 30 00 7E
|
||
Fan 2/4 : 80 08 0C 02 FD 80 7F 88 48 30 20 2C 00 C0 E0 E0 00 01 00 00 00 00 00 01 80 30 00 5E
|
||
```
|
||
|
||
## Come riprodurre/continuare il lavoro
|
||
|
||
1. **Hardware**: ESP32 DevKitC + ricevitore IR (TSOP38xx/VS1838B) su GPIO27, 3.3V.
|
||
2. **Cattura**: `pio run -t upload -t monitor` da questa cartella (richiede PlatformIO). Puntare il telecomando e premere un tasto.
|
||
3. **Decode manuale**: usare il metodo sopra descritto (vedi anche script Python di calibrazione usati in sessione, non salvati in questa cartella — ricostruibili facilmente dal metodo descritto).
|
||
4. **Verifica checksum**: aprire `ir_Hitachi.cpp` su github.com/crankyoldgit/IRremoteESP8266 (classe `IRHitachiAc`, funzione `calcChecksum()`) e confrontare con gli stati di riferimento sopra.
|
||
|
||
## Prossimi passi
|
||
|
||
1. ~~**Implementazione ESPHome finale**~~ ✅ **FATTA** (2026-06-30) — custom component
|
||
`climate_ir`, vedi sezione *Implementazione ESPHome (fase finale)*. Resta solo lo
|
||
sweep interattivo completo dei comandi in TX da confermare sul campo.
|
||
2. (Opzionale) Test temperatura impostata su più valori per confermare la formula `tempC = reverseBits(byte[11]) / 2` su tutto il range (16–32°C), finora derivata da 25/26°C.
|
||
|
||
### Verificato e chiuso
|
||
- **Frame spontanei / sensore di temperatura del telecomando**: ipotesi **scartata**. Monitoraggio via web UI con ~80 min di silenzio totale a telecomando fermo (unità accesa); tutti i frame ricevuti sono comandi da pressione tasto. Il RAR-6NE1 non trasmette periodicamente.
|
||
- **byte[9]**: confermato = codice del tasto premuto (non stato persistente).
|
||
|
||
## Riferimenti
|
||
- https://esphome.io/components/climate/climate_ir — protocolli IR climate supportati da ESPHome
|
||
- https://github.com/crankyoldgit/IRremoteESP8266 — `src/ir_Hitachi.h` / `ir_Hitachi.cpp`
|
||
- Nota progetto su Obsidian: `projects/hitachi-ir-esphome.md`
|
||
|
||
---
|
||
|
||
## Implementazione ESPHome (fase finale)
|
||
|
||
Realizzata il **2026-06-30**. Sostituisce il monitor Arduino (`src/main.cpp`, che
|
||
resta come strumento di cattura/diagnostica) con un'integrazione ESPHome nativa che
|
||
espone il condizionatore come entità `climate` in Home Assistant, **bidirezionale**.
|
||
|
||
### Architettura
|
||
Custom **external component** che estende `climate_ir::ClimateIR`. Non riusa la
|
||
classe `IRHitachiAc` della libreria: costruisce i 28 byte e il checksum a mano
|
||
(stesso algoritmo verificato) ed emette/decodifica via i componenti nativi ESPHome
|
||
`remote_transmitter`/`remote_receiver` (RMT). Vantaggi: nessuna dipendenza esterna,
|
||
entità climate completa, TX **e** RX (sync col telecomando fisico).
|
||
|
||
### File (`esphome/`)
|
||
```
|
||
esphome/
|
||
├── hitachi-ir.yaml # config: wifi/api/ota + TX GPIO25 + RX GPIO27 + climate
|
||
├── secrets.yaml # template wifi
|
||
└── components/hitachi_rar6ne1/
|
||
├── __init__.py · climate.py # registrazione platform (API ESPHome 2025+/2026.x)
|
||
├── hitachi_rar6ne1.h # classe, costanti timing, mappa fan/swing/preset
|
||
└── hitachi_rar6ne1.cpp # build 28 byte + checksum + transmit_state + on_receive
|
||
```
|
||
Importazione: `hitachi-ir.yaml` e la cartella `components/` nella stessa directory di
|
||
config ESPHome (es. `/config/`), poi build/flash dall'add-on ESPHome di HA (primo
|
||
flash USB, poi OTA).
|
||
|
||
### Hardware
|
||
- **RX**: ricevitore IR (TSOP/VS1838B) su **GPIO27** (invertito, pull-up).
|
||
- **TX**: LED IR + driver a transistor su **GPIO25** — `GPIO25 →[1k]→ base NPN`;
|
||
`3V3 →[100R]→ LED IR(A→K) → collector`; `emitter → GND`. Portante 38 kHz.
|
||
|
||
### Mappatura campi → 28 byte (in `build_state_`)
|
||
Identica alla tabella sopra: mode `[10]`, temp `[11]`=`reverseBits(°C×2)`, fan `[13]`
|
||
(Quiet/Low/Med/High→1/2/3/4 + Auto), swing V `[14]`/H `[15]` (`0xE0`/`0x60`), power
|
||
`[17]` bit0, preset **Eco→Silent** `[26]` bit4, **Boost→Powerful** `[25]` bit2,
|
||
checksum `[27]`. `byte[9]` (codice tasto) impostato a `0xE0` via la costante
|
||
`kButtonCode` — non critico (vedi gotcha).
|
||
|
||
### Gotcha risolti (utili per progetti AC simili su ESPHome/ESP32)
|
||
1. **API `climate_ir` dipende dalla versione.** Su 2024.12.x serviva la costante
|
||
`CLIMATE_IR_WITH_RECEIVER_SCHEMA` + `register_climate_ir`; dalla 2025.x in poi
|
||
gli helper a funzione `climate_ir_with_receiver_schema(cls)` + `new_climate_ir`.
|
||
Il `climate.py` attuale usa la forma moderna (testato su **2026.6.3**).
|
||
2. **`external_components: path` deve puntare alla cartella *contenitore***
|
||
(`components`), non alla cartella del componente (`components/hitachi_rar6ne1`):
|
||
altrimenti `climate.py` viene scambiato per il core `climate` → *import circolare*
|
||
(`partially initialized module 'esphome.components.climate'`).
|
||
3. **RX troncato.** Il frame Hitachi è 224 bit ≈ 226 simboli RMT, ma l'ESP32
|
||
classico ne cattura di default ~192 → frame tagliato, decode fallito (nessun
|
||
`RX ok`). Fix: `rmt_symbols: 384` + `receive_symbols: 384` sul `remote_receiver`
|
||
(su questa versione di ESPHome; il vecchio `memory_blocks` non esiste più).
|
||
4. **`byte[9]` non è critico.** L'unità accetta il frame full-state a prescindere:
|
||
in TX un OFF con `byte[9]=0xC0` ha spento il condizionatore; in RX il telecomando
|
||
reale ha inviato `byte[9]=0x10` (= il default di `IRHitachiAc::stateReset`).
|
||
5. Tenere `dump: all` **spento** in esercizio: con frame lunghi blocca il loop
|
||
~200 ms (`remote_receiver took a long time`). Riattivare solo per debug raw.
|
||
|
||
### Stato di verifica (2026-06-30)
|
||
- ✅ **TX**: comando OFF dall'ESP → condizionatore spento (emettitore e accettazione
|
||
frame provati sul campo).
|
||
- ✅ **RX**: telecomando fisico → frame completo decodificato, checksum valido,
|
||
entità `climate` in HA aggiornata (mode/temp/fan/swing letti correttamente).
|
||
- ✅ Build/validazione su ESPHome 2026.6.3; fix RMT e path confermati dai log.
|
||
- ⏳ **Da confermare sul campo**: sweep completo dei comandi *dalla card HA* in TX
|
||
(tutti i modi, range temp, tutte le fan, swing V/H/Both, preset Eco/Boost). Il
|
||
protocollo è interamente validato, quindi atteso funzionante; manca solo la prova
|
||
esaustiva interattiva.
|
||
|
||
### Edge case & robustezza (2026-06-30, sera)
|
||
- **Range temperatura** dell'entità ristretto a **18–30 °C** (`kMinTempC`/`kMaxTempC`
|
||
in `hitachi_rar6ne1.h`), scelta conservativa. ⚑ *Flag aperto*: il range realmente
|
||
supportato dal RAR-6NE1 è da verificare agli estremi (16/32) — se accettati si può
|
||
riallargare.
|
||
- **Preset ↔ ventola accoppiati** (override `control()` in `hitachi_rar6ne1.cpp`):
|
||
come il telecomando reale, attivando Eco/Silent la ventola va al minimo (Quiet) e
|
||
Boost/Powerful al massimo (High); cambiando la ventola si esce dal preset. Evita
|
||
stati incoerenti in HA (es. Boost + Quiet).
|
||
- **Setpoint in modalità Auto**: confermato **assoluto** come in Cool (nessuna
|
||
modifica necessaria).
|
||
- **OFF**: confermato funzionante in **ogni** modo (`byte[9]=0xE0`).
|
||
- ⏳ **In sospeso**: semantica della temperatura in **Dry** — `byte[11]` reale da
|
||
catturare dal telecomando (per ora si invia il setpoint come in Cool).
|