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

233 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (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
- 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 **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 **Dry**`byte[11]` reale da
catturare dal telecomando (per ora si invia il setpoint come in Cool).