Files
Hitachi-IR-Climate-ESPHome-…/README.md
Francesco Zanin 1a9eb82d0f Initial commit: Hitachi RAR-6NE1 climate via ESP32/ESPHome
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>
2026-06-30 15:50:26 +02:00

17 KiB
Raw Blame History

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) 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:

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 (1632°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


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 GPIO25GPIO25 →[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 climateimport 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 1830 °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 Drybyte[11] reale da catturare dal telecomando (per ora si invia il setpoint come in Cool).