# 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:///` (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).