家庭自动化 / 家庭设备发布 内容更新

ESP32 红外遥控器:先发对一个码,再做空调控制

固定有FET驱动的Adafruit收发模块,从实际NEC捕获到单次重放,再分开检查Daikin完整状态帧、光学发送与家电动作。

读完能做什么
获得主NEC学习/单次重放工程和独立Daikin状态编码变体,把捕获、请求、光学与设备实际反馈分开验收。
开始之前
已完成对应ESPHome前置,能核模块型号/针序、桥USB日志和低压供电;已有可管理的HA。
所需器材
DevKitC-1 v1.1 N8R8、桥USB数据线、Adafruit PID5639两LED发射板、PID5939接收板(V/H选择)、独立稳压5V≥1A、共地、STEMMA PH线;可核型号的自有遥控/设备、WP7113ID红LED、330Ω±5%0.25W;匹配ARC43xxx协议的空调仅作条件变体
版本组合
参考板:官方ESP32-S3-DevKitC-1 v1.1 / WROOM-1-N8R8;8MB Flash / OPI PSRAM软件:Python3.12.13 / ESPHome2026.9.1 / 原生ESP-IDF5.5.5;macOS arm64真实编译具体模块:PID5639/PID5939;NEC主工程与ARC43XXX独立变体HA资料:Core2026.10.2;配置检查、模板和设备运行待验
适用范围

S3 v1.1 N8R8;PID5639外供5V/3.3V逻辑,PID5939仅供3.3V接GPIO5;主配置NEC,变体仅上游ARC43XXX编码族,不声明未知家电兼容。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/home-devices.md;home-devices-sources.json含实际来源、章节和SHA。对象:H06具体器件与固定版本验证日期:
构建 / 配置已通过
docs/esp32-evidence/home-devices-builds.json、home-devices-configs.json;原始Exit0日志与源/生成CPP/镜像SHA。对象:ESP32-S3 N8R8;主NEC及独立Daikin两个配置版本:ESPHome2026.9.1 / ESP-IDF5.5.5 / Python3.12.13验证日期:
仿真不适用
home-devices-host.json记录同源头主机检查Exit0;仅帧资格/会话/策略纯逻辑,不作器件模拟。对象:无硬件/电气/HA模拟
本站实板待验证
无本站烧录、电气、UART/光学/风扇/HA运行或长期实验;observations.csv仅表头。对象:指定实板、模块、现场与Home Assistant

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 家庭设备

控制按钮可点,只证明入口存在

本例把链路分为捕获到码、软件请求发送、实际光学收到、家电执行四层。主工程只从真实完整NEC帧学习两个字段,人工按Replay才发一次;没有预置电视码、自动扫描或开机发命令。另附独立Daikin变体展示完整状态协议,条件不同、二选一刷写。

软件计数或本地灯只确认请求分支走过。即使另一接收器收到相近码,也未证明家电收到了,更未证明空调压缩机或房间响应。本站未执行光路/家电/HA实验,所有对应记录留空。

参考:[3] ESPHome2026.9.1 NEC字段、LSB顺序与重复定义[5] 固定Daikin ARC43XXX完整状态编码[6] climate_ir启动恢复、控制发送和状态发布

02 / 家庭设备

GPIO给信号,板载FET承担脉冲电流

固定PID5939接收板,V/H选V并对准竖直头:V+板3V3、Sig到GPIO5/J1-5、GND共地。它是38kHz包络接收器,不测载波/距离;不向它供5V再把输出接S3。发射固定PID5639原有两LED及FET,不加第三颗:V+独立稳压5.0V≥1A、Sig到GPIO6/J1-6、GND共地,外部正端不接板5V/3V3。

厂家5V条件两LED脉冲合计约400mA,经外供/FET而非GPIO;3.3V逻辑高使其发光。33%仅载波占空,不是整机恒定平均电流预算。只作短遥控突发,不能常HIGH作照明;红灯和厂商距离也非本站光学/距离结果。GPIO4经330Ω接WP7113ID只作请求提示,先全断电核V+/GND/Sig和线色标记。

模块供电 / 信号
PID5939接收,选V板3V3 / Sig GPIO5,inverted接收
PID5639发射,原两LED独立5V≥1A / Sig GPIO6,板载FET
两路负端与板GND共地;不并两路正电源
GPIO4普通LED330Ω串联,非IR执行确认

参考:[1] PID5639板载FET、逻辑与脉冲电流[2] PID5939供电、输出与V/H选择[7] 官方S3 v1.1 GPIO4/5/6及桥日志[9] PID5639固定原理图:AO3406、两颗940nm与SIG门极路径

03 / 家庭设备

只重放最近真实捕获的完整NEC

ir_session.h与host共用。每次完整解码保留16位address/command,不拿别的工具32位串机械切半;当前codec按LSB顺序处理。200ms内重收忽略、缓存十分钟过期、重启丢弃,人工请求至少间隔1秒,发后500ms忽略可能自收。另一个遥控完整帧可能覆盖缓存,发送前要核日志。

command_repeats=1指该codec同一完整帧的16位命令重复数,不是所有长按短repeat的计数;短repeat不能当新完整学习码。Learned NEC Available是缓存资格,不诊断接收器在线。无收到码可因没人按键、光路或断线,不能自动等同故障或成功。

计时统一取IDF5.5.5的esp_timer_get_time(),以64位保留启动后微秒再除1000转毫秒,不把32位millis扩大后冒充长期时钟。超时比较不会因约49.7天的32位完整周期让旧状态复活;这是同源注入时刻检查,不是已运行49天的实测。

代码 / 配置
// SPDX-License-Identifier: MIT
#pragma once
#include <cstdint>
#ifdef ESP_PLATFORM
#include "esp_timer.h"
#endif
namespace h06 {
#ifdef ESP_PLATFORM
// IDF monotonic microseconds since boot, widened before conversion; no 32-bit millis cycle.
inline uint64_t now_ms(){return static_cast<uint64_t>(esp_timer_get_time())/1000ULL;}
#endif
class Session {
 public:
  bool learn(uint64_t now,uint16_t address,uint16_t command){
    if(sent_&&now>=tx_at_&&now-tx_at_<500)return false; // ignore possible self-reception
    if(seen_&&now>=learned_&&now-learned_<200)return false;
    address_=address;command_=command;learned_=now;seen_=true;++captures_;return true;
  }
  bool available(uint64_t now)const{return seen_&&now>=learned_&&now-learned_<=600000;}
  bool request(uint64_t now){
    if(!available(now)||(sent_&&(now<tx_at_||now-tx_at_<1000))){++rejected_;return false;}
    tx_at_=now;sent_=true;++requests_;return true;
  }
  uint16_t address()const{return address_;}
  uint16_t command()const{return command_;}
  uint32_t captures()const{return captures_;}
  uint32_t requests()const{return requests_;}
  uint32_t rejected()const{return rejected_;}
  bool tx_indicator(uint64_t now)const{return sent_&&now>=tx_at_&&now-tx_at_<200;}
  const char* phase(uint64_t now)const{return !seen_?"NO_LEARNED_NEC":!available(now)?"LEARNED_EXPIRED":"NEC_AVAILABLE";}
 private:
  uint16_t address_=0,command_=0;
  uint64_t learned_=0,tx_at_=0;
  uint32_t captures_=0,requests_=0,rejected_=0;
  bool seen_=false,sent_=false;
};
inline Session& session(){static Session value;return value;}
}

参考:[3] ESPHome2026.9.1 NEC字段、LSB顺序与重复定义[10] IDF5.5.5 esp_timer:64位启动后单调微秒

04 / 家庭设备

主工程:先日志学习,再人工单次请求

完整YAML保存为esp32-esphome-ir-remote.yaml,与ir_session.h同目录。复用H01固定Python3.12.13/ESPHome2026.9.1/原生IDF5.5.5,填私有secrets,先config、compile,再run --device真实桥口,115200读取NEC/raw;下载README含从venv到热点/USB恢复的完整命令。

用自己可核型号的38kHz NEC遥控器,短按至少间隔0.3秒,保存同键的字段与raw原日志。Replay在缓存或间隔不合时拒绝;Transmit Request Count只计请求,GPIO4仅提示200ms,无设备ACK。接收头避免与自发直射,抑制窗口不证明光路可靠;不增加发射电流来掩盖接线问题。

代码 / 配置
substitutions:
  node_name: hixinwu-h06
esphome:
  name: ${node_name}
  friendly_name: Hixinwu H06
  min_version: 2026.9.1
  includes:
    - ir_session.h
esp32:
  board: esp32-s3-devkitc-1
  variant: ESP32S3
  flash_size: 8MB
  flash_mode: qio
  flash_frequency: 80MHz
  toolchain: esp-idf
  framework:
    type: esp-idf
    version: 5.5.5
psram:
  mode: octal
  speed: 80MHz
logger:
  baud_rate: 115200
  hardware_uart: UART0
  level: DEBUG
api:
  encryption:
    key: !secret api_key
  reboot_timeout: 0s
ota:
  - platform: esphome
    encryption:
      key: !secret api_key
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 0s
  ap:
    ssid: "${node_name} Recovery"
    password: !secret fallback_password
    ap_timeout: 90s
captive_portal:

remote_receiver:
  id: ir_rx
  pin:
    number: GPIO5
    inverted: true
    mode: INPUT_PULLUP
  dump: [nec, raw]
  filter: 50us
  idle: 10ms
  buffer_size: 4kB
  on_nec:
    then:
      - lambda: |-
          if(h06::session().learn(h06::now_ms(),x.address,x.command))
            ESP_LOGI("h06","LEARN address=0x%04X command=0x%04X repeats=%u (not appliance feedback)",x.address,x.command,x.command_repeats);
remote_transmitter:
  id: ir_tx
  pin: GPIO6
  carrier_duty_percent: 33%
switch:
  - platform: gpio
    id: indicator
    pin: GPIO4
    internal: true
    restore_mode: ALWAYS_OFF
button:
  - platform: template
    name: Replay Learned NEC
    on_press:
      - if:
          condition:
            lambda: return h06::session().request(h06::now_ms());
          then:
            - remote_transmitter.transmit_nec:
                transmitter_id: ir_tx
                address: !lambda return h06::session().address();
                command: !lambda return h06::session().command();
                command_repeats: 1
            - logger.log: "TX requested once; no appliance acknowledgment"
          else:
            - logger.log: "TX rejected: no fresh NEC or <1s since previous request"
binary_sensor:
  - platform: template
    name: Learned NEC Available
    lambda: return h06::session().available(h06::now_ms());
text_sensor:
  - platform: template
    name: IR Session Phase
    lambda: return {h06::session().phase(h06::now_ms())};
    update_interval: 1s
sensor:
  - platform: template
    name: NEC Capture Count
    lambda: return h06::session().captures();
    accuracy_decimals: 0
    update_interval: 1s
  - platform: template
    name: Transmit Request Count
    lambda: return h06::session().requests();
    accuracy_decimals: 0
    update_interval: 1s
  - platform: template
    name: Rejected Request Count
    lambda: return h06::session().rejected();
    accuracy_decimals: 0
    update_interval: 1s
interval:
  - interval: 50ms
    then:
      - lambda: |-
          if(h06::session().tx_indicator(h06::now_ms())) id(indicator).turn_on();
          else id(indicator).turn_off();

参考:[1] PID5639板载FET、逻辑与脉冲电流[2] PID5939供电、输出与V/H选择[3] ESPHome2026.9.1 NEC字段、LSB顺序与重复定义[4] 固定receiver RMT范围与transmitter实现

05 / 家庭设备

空调先核帧族,再用完整状态编码器

空调常同时编码模式、温度、风速与摆风。变体只对应固定daikin源码标出的ARC43XXX族:38kHz、35字节拆8/8/19三段,末段校验,编码温度10–30℃/1℃步。它不是整个品牌兼容表,本例没有已确认匹配的实体内机型号。品牌相同不能替代协议核对。

保留原遥控/内机型号,录OFF、COOL25℃、COOL27℃及风速/摆风各三遍,逐项核前缀、段长、时序、字段与校验。原遥控有效但格式不符时停止套用,选有匹配依据的组件另验。变体idle=10ms把约32.3ms段间隔分开,以19字节状态段交给上游接收器;初始40ms超过S3当前RMT上限的真实失败与修复已保留,不是所有长帧都该把idle加长。

参考:[4] 固定receiver RMT范围与transmitter实现[5] 固定Daikin ARC43XXX完整状态编码[6] climate_ir启动恢复、控制发送和状态发布

06 / 家庭设备

独立变体的HA状态仍是请求状态

本节是可单独构建的daikin-arc43xxx.example.yaml,不能直接叠在主YAML后面。选择该变体前先完成帧族匹配。它没有主工程的NEC缓存,也没有本地自动启动动作。climate_ir setup可能恢复旧设定点,但不在setup发送;用户控制才发码并发布请求状态,收到原遥控也可更新状态。

HA添加ESPHome集成、真实IP/key后核Daikin Requested State。该状态不能证明内机收码、已运行或到温,变体也没有实测室温传感器。先用原遥控恢复已知状态,再人工逐项核本体反馈;不要让未验证实体进入自动化。HA运行与家电结果均待测。

代码 / 配置
substitutions:
  node_name: hixinwu-h06-daikin
esphome:
  name: ${node_name}
  friendly_name: Hixinwu H06 Daikin Requested
  min_version: 2026.9.1
esp32:
  board: esp32-s3-devkitc-1
  variant: ESP32S3
  flash_size: 8MB
  flash_mode: qio
  flash_frequency: 80MHz
  toolchain: esp-idf
  framework:
    type: esp-idf
    version: 5.5.5
psram:
  mode: octal
  speed: 80MHz
logger:
  baud_rate: 115200
  hardware_uart: UART0
  level: DEBUG
api:
  encryption:
    key: !secret api_key
  reboot_timeout: 0s
ota:
  - platform: esphome
    encryption:
      key: !secret api_key
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  reboot_timeout: 0s
  ap:
    ssid: "${node_name} Recovery"
    password: !secret fallback_password
    ap_timeout: 90s
captive_portal:
remote_receiver:
  id: ir_rx
  pin:
    number: GPIO5
    inverted: true
    mode: INPUT_PULLUP
  dump: raw
  filter: 50us
  idle: 10ms
  buffer_size: 8kB
remote_transmitter:
  id: ir_tx
  pin: GPIO6
  carrier_duty_percent: 33%
climate:
  - platform: daikin
    name: Daikin Requested State
    transmitter_id: ir_tx
    receiver_id: ir_rx

参考:[5] 固定Daikin ARC43XXX完整状态编码[6] climate_ir启动恢复、控制发送和状态发布[8] Home Assistant官方ESPHome集成入口与加密配置

07 / 家庭设备

为请求、光学和家电分别留证据

先录10次间隔足够的短按,长按另录完整帧/短repeat差异。第二轮对自有设备的非关键功能单次重放,分三列记录Request、Optical、Appliance;空调匹配后才测OFF、COOL25/27、风速、摆风,遵守厂商保护延时,不快速反复启停。未做列为空,不把预期照抄observations或capture-log.csv。

无解码先核接收供电、V/H、朝向与38kHz兼容,raw有而NEC无先查协议。发射异常先断电查外供5V、共地、Sig与FET板,不用GPIO供LED。空调不响应先用原遥控恢复,停止自动化,再核协议/朝向/供电。上传或网络失败用桥USB、BOOT/EN和90秒恢复热点;主缓存重启清空,变体旧设定点不当设备同步。

参考:[1] PID5639板载FET、逻辑与脉冲电流[2] PID5939供电、输出与V/H选择[3] ESPHome2026.9.1 NEC字段、LSB顺序与重复定义[5] 固定Daikin ARC43XXX完整状态编码

08 / 家庭设备

完整编译和会话检查,各有明确范围

主NEC工程和独立Daikin变体分别执行config/compile,版本、输入、生成CPP、原日志与镜像摘要进入证据。host直接检查同一ir_session.h的无缓存、字段、过期、限速、自收抑制、提示计时、重启和64位跨32位周期,没有复制测试模型。

这些不模拟光学、NEC/Daikin真实解码、RMT时序或家电动作;器件/HA均未运行。下一步可以将已逐项验过的本地命令接到实体按钮,继续保留请求与执行的区别。

2026-10-18已实际执行config与完整固件compile,均Exit0;主NEC OTA 886576字节,Daikin变体884272字节。三组host同源检查均Exit0;这些是软件结果,未执行烧录/HA/实物试验。

代码 / 配置
clang++ -std=c++17 -Wall -Wextra -Werror host_check.cpp -o /tmp/h06-host
/tmp/h06-host

参考:[3] ESPHome2026.9.1 NEC字段、LSB顺序与重复定义[4] 固定receiver RMT范围与transmitter实现[5] 固定Daikin ARC43XXX完整状态编码[6] climate_ir启动恢复、控制发送和状态发布

BACK TO THE SOURCE

资料来源与版本说明

固定上游源码和厂家具体章节已阅读,逐字摘要与范围见docs/esp32-evidence/home-devices.md;独立复核/实板结果另记。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. PID5639板载FET、逻辑与脉冲电流
  2. PID5939供电、输出与V/H选择
  3. ESPHome2026.9.1 NEC字段、LSB顺序与重复定义
  4. 固定receiver RMT范围与transmitter实现
  5. 固定Daikin ARC43XXX完整状态编码
  6. climate_ir启动恢复、控制发送和状态发布
  7. 官方S3 v1.1 GPIO4/5/6及桥日志
  8. Home Assistant官方ESPHome集成入口与加密配置
  9. PID5639固定原理图:AO3406、两颗940nm与SIG门极路径
  10. IDF5.5.5 esp_timer:64位启动后单调微秒