joyfulhouse/esphome-quietcool
GitHub: joyfulhouse/esphome-quietcool
通过逆向工程还原的 ESPHome 固件,使 Home Assistant 能够经 433 MHz 射频直控 QuietCool 全屋通风扇,无需云服务或 OEM 网关。
Stars: 0 | Forks: 0
# esphome-quietcool
这款 ESPHome 固件可以通过 QuietCool 原生的 433.92 MHz 无线电链路,直接从 Home Assistant 控制 **QuietCool 全屋/山墙通风扇** —— 无需云服务,无需 OEM 网关,无需 BLE。其 RF 协议是**从 OEM 手持遥控器的固件中逆向工程得出的**(基于 STM32 dump + SDR 抓包);本仓库是对该分析结果的一项独立、净室实现。
## 功能
- **直接 RF 风扇控制** — 支持 关闭 / 低速 / 中速 / 高速 以及速度感知的 1 / 2 / 4 小时定时器,并以精确的 OEM 数据帧进行传输。
- **学习模式** — 只需按两次 OEM 遥控器即可捕捉您风扇的 4 字节发送方 ID。入网无需抓包或提取固件;该 ID 会持久化存储在 NVS 中,并在重启和 OTA 后依然保留。请参阅 [学习模式](#learn-mode--porting-to-your-own-fan)。
- **Home Assistant 原生 API** — 提供标准的 `fan` 实体以及诊断信息(TX/RX 计数器、最后一条命令、已学习的发送方 ID、电池电压/电量)。
- **观测状态接收** — 严格验证 OEM 遥控器的按键并将其映射到 HA 实体中,**绝对不会进行二次传输**(无 RF 回声)。
- **设备端 OLED** — 动画风扇图标、HH:MM:SS 定时器倒计时、三个由 HA 中继的温度(室内/室外/阁楼,配有语义图标),以及 WiFi / API / 电池状态栏。温度源可从 HA UI 中进行配置,而非硬编码。
- **安全第一** — 在启动、OTA 后、API 重连时、从恢复的状态中或从接收到的数据帧中,绝对不会进行传输。已通过多模型对抗性审查。
- **多开发板与多风扇** — 共享同一份配置,每个设备仅需使用轻量级的包装层。
## 支持的硬件
| 开发板 | 无线电模块 | MCU | 配置文件 | 状态 |
| --- | --- | --- | --- | --- |
| LilyGO TTGO LoRa32 **V2.1** (433 MHz) | SX1278 (SX127x) | ESP32 | `quietcool-lora32.yaml` | 已在真实风扇上验证 |
| Heltec / HiLetgo ESP32 LoRa **V3** (433–510 MHz) | SX1262 (SX126x) | ESP32-S3 | `quietcool-lora-v3.yaml` | 编译通过;等待硬件启动调试 |
V3 移植版在 SX1262 上重现了完全相同的 2-FSK 配置(ESPHome 的 `sx126x` 组件暴露了相同的比特率/频偏/同步字/前导码/可变长度调节选项)。它编译无误,但尚未在真实硬件上运行过 —— 有几个引脚(状态 LED 极性、VBAT ADC 分压器和 RX 滤波器带宽)在文中作为 `PIN CONFIDENCE`(引脚确认)项进行了标注,以便在首次启动调试时确认。请参阅 [docs/hardware.md](docs/hardware.md)。
两者在传输前都需要连接 **433 MHz 天线**。
### 购买渠道
- **LilyGO TTGO LoRa32 V2.1 (433 MHz)** — 本项目基于此参考开发板构建并验证:
- **HiLetgo ESP32 LoRa V3 (SX1262, 0.96" OLED, 433–510 MHz 天线)**:
## 快速开始
```
# 1. 安装 ESPHome(推荐使用 uv)
uv venv .venv && uv pip install --python .venv/bin/python esphome
# 2. 提供 secrets
cp secrets.yaml.example secrets.yaml # then edit
# 3. 验证、构建、刷写(首次使用 USB,之后使用 OTA)
.venv/bin/esphome run quietcool-lora32.yaml
```
然后,在 Home Assistant(ESPHome 集成)中采用该设备,并通过 [学习模式](#learn-mode--porting-to-your-own-fan) 将您的风扇绑定到其中。
## 文档
- [docs/protocol.md](docs/protocol.md) — RF 配置、帧格式、命令字节
- [docs/firmware-analysis.md](docs/firmware-analysis.md) — 逆向工程过程:内存映射、寄存器配置、命令字节反汇编、单机 ID 机制
- [docs/hardware.md](docs/hardware.md) — 开发板、接线、天线、购买链接
- [docs/display.md](docs/display.md) — OLED 布局、图标语言、预览渲染器
- [docs/deployment.md](docs/deployment.md) — 多设备模式 + 真实的双风扇安装案例
## 仓库结构
```
quietcool-lora32.yaml # TTGO LoRa32 V2.1 / SX1278 — shared base config
quietcool-lora32-upstairs.yaml # example second device (includes the base)
quietcool-lora-v3.yaml # Heltec/HiLetgo ESP32-S3 / SX1262 port
secrets.yaml.example # copy to secrets.yaml (gitignored)
tests/ # config regression tests (pytest/unittest)
tools/ # display renderer + fan-frame generator
fonts/ images/ # OLED assets (MDI webfont, fan bitmaps)
docs/ # protocol, firmware analysis, hardware, display
```
## 安全
全屋风扇会吸入/排出大量空气。在通电之前:请打开足够的窗户补充空气,确认燃烧设备不会发生倒灌,并保留一个可用的 OEM 控制器作为备用。此固件绝对不会自行发射信号 —— 仅在明确按键或收到 Home Assistant 命令时才会传输。
## 学习模式 / 移植到您自己的风扇
每个 QuietCool OEM 的发送方 ID 都是以 `CB` 开头的四个字节;其 RF 配置和命令格式是通用的。因此,此固件可以通过现有的接收路径从 OEM 遥控器中学习风扇的 ID。
### 针对其他风扇的首次启动流程
1. 在编译之前,请将 `quietcool_lora32_ccrome.yaml` 中的顶层 substitution 修改为:
substitutions:
quietcool_sender_id: "0x00000000"
这特意设置为普通的 substitution,而不是 `!secret`,这样可移植的配置就不会产生额外的密钥文件依赖性。签入的默认值对于此安装为 `0xCB004739`。
2. 正常刷入。当持久化的 ID 为零时,启动会进入自动学习模式,OLED 屏幕会显示 `LEARN / REMOTE X2`。自动学习模式会保持武装状态 - 根据需要重新开启其 120 秒的监听窗口 - 最长可持续到**开机后 15 分钟**,这是基于假设安装人员在首次开机时就在现场。超过此时间上限后,它将完全解除武装,OLED 将恢复到正常(未配置/关闭)布局;无论哪种情况,在未配置状态下 TX 依然会拒绝传输。请参阅下文的“手动重新学习和忘记”以在之后重新武装。
3. 按 OEM 遥控器上的命令键,等待超过 600 毫秒,然后在 60 秒内再次按下遥控器。两次单独的按键是必须的工作流程:只有真实的状态命令帧(速度/持续时间按键)才能启动或确认候选者,因此 OEM 在一次按键中的三次 45 毫秒重复无法自我确认,并且被动的 `66 66` 唤醒/状态查询永远无法自行完成学习 - 这一要求同时也阻止了停放着的、未配置的设备从无意中听到的查询/命令串扰中接收到邻近安装的 ID。
4. 接受后,OLED 会短暂显示 `LEARNED / ID SAVED`,`Remote Sender ID` Home Assistant 文本传感器会发布一个值(例如 `CB 00 47 39`),并且该 ID 会被强制提交到 NVS。在设置 ID 之前,`tx_burst` 会记录错误并拒绝传输或增加 `TX Count`。只有携带有效速度/持续时间状态命令(匹配的命令字节、真实的速度半字节、真实的持续时间半字节)的、以 `CB` 为前缀的六字节帧才能成为候选;即使是来自所有者自己遥控器的 `66 66` 查询也会被拒绝。第二个有效帧必须在第一个帧之后超过 600 毫秒但在 60 秒内携带相同的 ID。不同的有效发送方将重新启动两帧计数,这阻止了附近邻居的一次性遥控按键通过另一个发送方启动的候选者来完成确认。学习帧由 RX 和存储路径消耗,绝对不会发布风扇状态或触发任何 TX 操作。
### 手动重新学习和忘记
- 按下 Home Assistant 中的 `Learn Remote ID` 按钮,或者按住开发板的 PRG 按钮 5-10 秒。这将打开一个 120 秒的手动窗口,除非确认了新的候选者,否则当前存储的 ID 将保持不变。现有的 1-5 秒 PRG Off 手势会在 4999 毫秒时结束,因此手势不会重叠。一旦首次启动的 15 分钟窗口过去,这是重新武装学习的必由之路。
- 按下 `Forget Remote ID` 可立即向 NVS 写入零,发布 `unset`,并重新进入自动学习模式(它拥有全新 15 分钟的上限,因为忘记本身就是一种故意的本地/HA 操作),直到确认替换遥控器为止。Forget 还会持久抑制编译时的默认值:即使在编译时使用了非零的 `quietcool_sender_id`,on_boot 也**不会**在下一次重启时静默重新植入该值,因此 Forget 的状态在重启和 OTA 之后依然保持被遗忘状态。后来成功的 学习 会清除该抑制。第三方构建仍应将 substitution 保持在 `0x00000000`。
`learned_sender_id` 使用 ESPHome 的 restored globals 存储。学习到的 ID 可以在普通重启、OTA 以及后续保留相同 global 的固件更新中存续。完整的 flash/NVS 擦除会将其删除(连同 Forget 抑制标志一起);在执行此类擦除后,启动时会应用非零的编译时种子,或者在种子为零时启动自动学习。
接受条件需要来自同一以 `CB` 为前缀的发送方的两次匹配突发(间隔超过 600 毫秒)—— 这是一个 **双突发邻居保护机制**,这样共享频段上来自邻居风扇的单个杂散帧就无法为您的控制器进行配置。在学习窗口武装时,OLED 会显示 `LEARN / REMOTE X2`,成功后会短暂显示 `LEARNED / ID SAVED`(在 `docs/display-previews/learn-active.png` 和 `docs/display-previews/learn-confirmed.png` 中预览)。
## 出处与许可证
这是一项独立的逆向工程成果。433 MHz 载波和 2-FSK 特性是通过 SDR 捕获确定的;确切的寄存器配置、帧格式、发送方 ID 机制和命令字节结构是通过对 OEM 遥控器的 STM32 固件进行转储和反汇编来恢复的(请参阅 [docs/firmware-analysis.md](docs/firmware-analysis.md))。一个早期的社区概念验证([ccrome/quiet-cool-rf-remote](https://github.com/ccrome/quiet-cool-rf-remote))指出了大概的实现思路,但在实际的实现中并未被采用。
代码、工具和文档均采用 MIT 许可证(请参阅 [LICENSE](LICENSE))。OEM 固件本身并未被重新分发 —— 此处仅记录了独立得出的关于协议的事实。“QuietCool” 是其所有者的商标;本项目不附属于 QuietCool,也未获得其认可。
标签:ESP32, ESPHome, Home Assistant, 射频通信, 智能家居, 物联网, 逆向工具