
家里的美的空调用了好几年什么都好就是不带联网功能。下班路上想提前把空调开了一进门就凉快这个念头我琢磨了很久。一开始我想给内机换智能控制板研究一圈发现型号太杂拆机跳线风险太大直接放弃。后来我把方案换成了红外用ESP8266加一枚红外LED把原装遥控器的信号抓下来、解析出协议再送进Home Assistant做联动。这条路不动空调任何一根线成本几十块钱唯一要啃的骨头就是搞清楚遥控器发出的那串红外脉冲到底是怎么编码的。这篇文章把从协议解析到Home Assistant集成的全过程写下来适合手里有一台普通空调、又想低成本接入Home Assistant的折腾型朋友。1. 先说清楚红外方案和换面板到底差在哪1.1 普通空调的智能化困境绝大多数家用空调出厂时并不带联网模块尤其用了三五年的老机器遥控器就是它的全部控制入口。有人会想用智能插座来给空调断电重启但这个思路对空调基本无效空调待机功率很小插座断电后并不能唤醒它反而会丢掉所有设置。另一种常见想法是给内机更换一块支持联网的智能控制板可空调控制板型号实在太多拆机、跳线、匹配线序每一步都可能把好好的空调搞坏保修也就泡汤了。真正的问题在于空调的大脑只接受红外接收窗传入的指令而我们在不拆机的前提下能碰到的公开控制点就只有前面板那个红外接收窗。所以只要能让一个联网设备向这个接收窗发出和原装遥控器一模一样的红外信号智能化的路就通了。1.2 为什么红外是性价比最高的入口用红外方案控制空调有几个非常实际的优势完全不动空调本体没有任何保修风险硬件成本极低一个ESP8266开发板加两个红外器件就能开工通用性很强只要空调还带红外遥控器这套思路就能迁移后期维护简单出了问题大不了拔电重插代价也明确红外是单向通道Home Assistant只知道我下发了什么命令不知道空调实际回了什么而且发射端和空调之间要保证一定角度和距离不像网络控制那样无感。但对绝大多数家庭场景来说这个取舍完全值得。1.3 整条技术路线的全景整个项目拆开看其实是一条很清晰的链路用红外接收头把原装遥控器的信号抓包下来从原始脉冲里识别出美的空调的协议特征把时序转成二进制帧对照遥控器按键逐步反推出帧里面每一位的含义在ESP8266上写固件让它能按需生成并发送红外帧通过MQTT或ESPHome把控制接口交给Home Assistant在HA里配置climate实体写自动化后面每一环我都会展开讲包括我踩过的坑和最后的稳定运行经验。2. 硬件准备与红外发射电路2.1 元器件清单先列一份完整的物料表基本都是几块钱的东西元件规格数量用途ESP8266开发板NodeMCU或Wemos D1 Mini1块主控红外发射LED5mm 940nm波长1~2个发送指令红外接收头VS1838B或TSOP382381个抓包用NPN三极管S8050或2N22221个驱动红外LED电阻1kΩ、22Ω、330Ω各几个基极限流、LED限流面包板加杜邦线若干一套前期验证注意红外LED的波长选940nm和遥控器一致。买的时候别买成850nm的虽然也能用但发射效率和部分空调接收头的匹配度差一些。2.2 发射电路三极管为什么必不可少ESP8266的GPIO在3.3V下最大也就能输出10mA出头的电流直接驱动红外LED基本只能贴着脸控制距离稍远信号就衰减没了。要让空调隔几米识别红外LED的脉冲电流至少要50mA以上这就是必须加三极管的原因。推荐电路很简单GPIO4 ── 1kΩ ──┬── S8050基极 └── S8050发射极 ── GND 5V ── 22Ω ── 红外LED ── 红外LED- ── S8050集电极原理和计算也不复杂GPIO输出3.3V经过1kΩ电阻到基极基极电流大约2.6mA。S8050的放大倍数一般50到100倍足以让集电极的LED进入饱和导通状态。两只940nm LED串联压降约2.6V5V供电扣除三极管饱和压降0.3V限流电阻上分到大约2.1V到2.4V取22Ω时电流约100mA刚好在红外LED的脉冲工作范围内。我实测用这个电路把模块放在客厅茶几上正对空调前脸七八米内都能稳定触发。如果你只接一只LED限流电阻可以改成10Ω驱动电流更大一些。2.3 接收头的接法接收头只在协议抓包阶段使用装好之后可以一直留着也可以用完就拆。VS1838B这类接收头通常有三个引脚不同厂家的引脚顺序不一样拿到手先看型号丝印或数据手册。以最常见的封装为例OUT接GPIO14VCC接3.3VGND接GND这类接收头内部已经带了放大和解调电路输出的是解调后的脉冲电平IRremoteESP8266库可以直接处理不需要额外接上拉电阻。抓包时把遥控器对准接收头按一个键串口就能打印出完整脉冲序列。2.4 供电与布线的几个注意点整个红外电路工作在3.3V和5V低压侧比动220V安全得多但也不要觉得可以随便来。ESP8266在发射WiFi的时候电流波动很猛供电不稳会出现信号飘的情况后面我专门写了这个问题。布线方面有两点比较重要一是红外LED到三极管之间的线尽量短太长会引入电感和寄生电容导致脉冲边缘变形二是5V电源最好用一个质量可靠的USB充电头不要用电脑USB口凑合电流不够时WiFi一跑起来红外发射强度就掉链子。如果模块准备长期放在空调附近还要注意和变频外机保持至少30厘米的距离避免高频噪声干扰这个我实测过靠太近的确会出现偶发失灵。3. 协议解析第一步把原装遥控器的信号抓下来3.1 用IRremoteESP8266库打印原始脉冲抓包我强烈建议用IRremoteESP8266这个库它已经把市面上绝大多数空调协议都做了定义就算最后不用它发送单拿来做抓包工具也非常顺手。Arduino库管理器里直接搜IRremoteESP8266安装然后烧下面这个最小程序#include IRrecv.h #include IRutils.h const uint16_t kRecvPin 14; // GPIO14对应NodeMCU的D5 IRrecv irrecv(kRecvPin); decode_results results; void setup() { Serial.begin(115200); irrecv.enableIRIn(); Serial.println(ready); } void loop() { if (irrecv.decode(results)) { irrecv.dump(results); // 打印解码结果和原始脉冲 irrecv.resume(); } }烧好之后打开串口监视器拿起原装遥控器按下制冷26度自动风的按键串口会打印出一大段信息。里面最开头会显示协议类型如果是MIDEA说明库已经直接识别出来了后面跟着一长串RawTiming数组这就是我们真正需要分析的原始素材。3.2 从脉冲序列里认出美的协议的特征dumb函数打印出来的原始脉冲大概长这样uint16_t rawData[97] {4400, 4400, 550, 1600, 550, 550, ...};这里每个数字是微秒数正数代表载波点亮的时间负数前面的符号在库的打印输出里用来表示低电平时间。第一眼看上去全是数字但只要把美的协议的特征表记住很快就能对号入座。特征参数载波频率38kHz引导码高电平约4400μs引导码低电平约4400μs逻辑0550μs高 550μs低逻辑1550μs高 1600μs低数据长度48bit即6字节和NEC协议对比一下就很直观NEC的引导码是9000μs加4500μs逻辑1的负电平要1750μs。美的这套时序整体更紧凑引导码只有4400μs左右。用示波器或者逻辑分析仪看就是一串38kHz方波分组的脉冲包。3.3 原始timing如何转成01串把原始脉冲转成二进制方法其实特别机械引导码之后每一组550μs高电平 一段低电平就是一个数据位。低电平持续550μs左右记作0持续1600μs左右记作1。比如一段脉冲550, -550 → 0 550, -1600 → 1按这个规则把48位读出来再每8位一组转换成十六进制字节就得到了完整的6字节帧。我当时的做法是直接把raw数组复制到Python脚本里写个几行的转换函数自动生成字节比用眼睛数靠谱得多。这里有一个容易搞混的点库的打印输出和实际发送时的位序可能相反所以手算出来的字节顺序如果和库的解析结果不一样不用慌以库的解析为准手算只是为了验证理解。3.4 抓包实战技巧抓包是整个项目里最不能急的环节几个经验值得提前说有条件就上一台几十块钱的24MHz逻辑分析仪配合PulseView看时序比串口打印直观太多抓包时最好让ESP8266别连WiFiWiFi射频会引入时序抖动虽然多数时候不影响但追求精确就断开遥控器距离接收头20到50厘米按键一下一下按别长按长按会连续重复帧日志会被刷屏每个设置组合抓2到3遍对比确认帧数据稳定每抓一帧立刻在旁边备注含义比如cool_26_auto_swingoff后面解析时你就知道这一帧对应什么状态这些原始记录就是你后面最可靠的参照物比网上找的什么协议表都准。4. 美的空调红外帧结构拆解4.1 六字节数据帧的分工总览把抓到的48位数据排列成6个字节一个典型的帧长这样字节通常含义我的机器实测值Byte0机型/固定字节常见0x01、0x00或0xA10x01Byte1电源、模式、风量、摆风、扩展功能0x49Byte2温度编码0x09对应26度Byte3功能扩展/保留0x00Byte4功能扩展/保留0x00Byte5校验字节0xAD注意Byte0在不同批次、不同型号上差异很大有的美的是0x00有的是0xA1。没有统一的标准帧这也是为什么网上抄来的码经常在自己机器上不响应的原因。下面的拆解以我抓到的这台机器为例它和IRremoteESP8266库默认支持的Midea格式基本一致你可以把它当成一个学习模板再用差分法验证自己的机器。4.2 温度编码为什么从17度起步美的空调的设定温度范围一般是17到30度帧里用4bit编码数值等于实际温度减17。所以26度对应9十六进制写成0x0924度对应7写成0x0717度就是0x0030度是0x0D。这个规律也解释了为什么很多协议文档里温度是按17度偏移来描述的。如果你抓到一帧温度是21度Byte2应该是0x04多抓几个温度点对比一下就能确认。部分新款机型最低支持16度甚至更高上限这个一定要以自己的抓包结果为准不要想当然。4.3 模式、风量和摆风的组合规则在我这台机器上Byte1的位分配比较规整拆开看就是一张清晰的位表位含义0x49时的值bit0电源1开bit1-bit2风量00自动bit3垂直摆风0关bit4-bit6模式001制冷bit7水平摆风/其他0关IRremoteESP8266库里对模式值的定义是这样的模式库常量编码值自动kMideaACAuto0制冷kMideaACCool1除湿kMideaACDry2送风kMideaACFan3制热kMideaACHeat4风量对应关系风量库常量编码值自动风kMideaACFanAuto0低风kMideaACFanLow1中风kMideaACFanMed2高风kMideaACFanHigh3把这些组合起来我抓到的制冷、26度、自动风、关摆风这一帧Byte10x49、Byte20x09加上固定字节和保留字节就是01 49 09 00 00 AD。如果把温度改成24度Byte2变成0x07其他不动整帧变成01 49 07 00 00 AF。4.4 校验字节算法不对空调就装傻协议里最能坑人的就是校验字节。美的的校验算法是对前5个字节求和取低8位再用0x100减去这个低8位。拿01 49 09 00 00这组数算一遍01 49 09 00 00 0x53 0x100 - 0x53 0xAD所以完整帧是01 49 09 00 00 AD。温度改成24度那组01 49 07 00 00 0x51 0x100 - 0x51 0xAF完整帧是01 49 07 00 00 AF。校验字节只要算错空调接收后就像没看见一样完全不响应这点后面我会专门讲怎么排查。4.5 用抓包数据反推自己空调的协议表如果你的空调和我的帧结构对不上别急着抄网上代码用单变量法自己反推过程一点都不神秘抓一帧基准状态比如制冷、自动风、26度、关摆风只改温度到27度再抓一帧和基准帧做对比温度字节的变化就暴露了只改风量只改模式只开关摆风每次只动一个变量把两帧的数据按字节做XOR差异位就是该功能对应的控制位重复几次建立一张完整的变量-位映射表这个方法对任何品牌的红外空调都适用本质就是控制变量法。很多协议解析文章写得神乎其神真上手之后你会发现只要耐心一点一个晚上就能把常用功能的位表摸清楚。5. ESP8266固件生成、发送红外帧5.1 用IRremoteESP8266库别自己造轮子协议解析清楚了发送环节就没必要再手搓定时器了。IRremoteESP8266库已经内置了Midea协议的支持包括38kHz载波生成、时序精度控制、校验字节计算都比自己用delayMicroseconds拼信号可靠得多。有人担心用库是黑盒不踏实。我的观点恰好相反库是经过大量用户验证的它的时序处理比你手写的要稳。你想验证协议理解得对不对可以先用库发一帧再用接收头抓回来对比能对得上就说明你的位表没问题也说明库里实现的协议和你的机器匹配。5.2 最小可用的IRMideaAC发送代码下面这段就是整个发送部分的最小骨架Arduino环境下编译通过就能用#include IRremoteESP8266.h #include IRsend.h #include IRMideaAC.h const uint16_t kIrLed 4; // GPIO4对应NodeMCU的D2 IRMideaAC ac(kIrLed); void setup() { ac.begin(); ac.setPower(true); ac.setMode(kMideaACCool); ac.setTemp(26); ac.setFan(kMideaACFanAuto); } void loop() { ac.send(); delay(60000); }这段程序每60秒发一次制冷26度自动风的指令。ac.setTemp(26)内部会自动完成17度偏移setFan和setMode也都有对应的常量和位处理不用自己拼接字节。需要摆风、节能、健康等功能时按库头文件里的API名调用就行例如setSwingVertical(false)、setSwingHorizontal(false)。实测经验是发送一次80%能成功发送两次基本100%。所以我在实际项目里会把每次命令循环发两三次间隔50到80毫秒反正空调对重复帧也没有副作用。5.3 接上MQTT让Home Assistant能下发命令要让Home Assistant来控制最简单的方式是ESP8266通过MQTT订阅几个主题。这里用一个轻量的JSON方案演示也可以用后面独立主题的方案看你的习惯。#include ESP8266WiFi.h #include PubSubClient.h #include ArduinoJson.h const char* ssid 你的WiFi; const char* password 你的密码; const char* mqtt_server 192.168.1.10; WiFiClient espClient; PubSubClient client(espClient); IRMideaAC ac(4); void sendCommand(const char* topic, byte* payload, unsigned int length) { String msg; for (unsigned int i 0; i length; i) msg (char)payload[i]; if (String(topic) bedroom_ac/power/set) { ac.setPower(msg ON); ac.send(); } else if (String(topic) bedroom_ac/temp/set) { ac.setTemp(msg.toInt()); ac.send(); } else if (String(topic) bedroom_ac/mode/set) { if (msg heat) ac.setMode(kMideaACHeat); else if (msg dry) ac.setMode(kMideaACDry); else if (msg fan_only) ac.setMode(kMideaACFan); else if (msg auto) ac.setMode(kMideaACAuto); else if (msg off) ac.setPower(false); else ac.setMode(kMideaACCool); ac.send(); } else if (String(topic) bedroom_ac/fan/set) { if (msg low) ac.setFan(kMideaACFanLow); else if (msg medium) ac.setFan(kMideaACFanMed); else if (msg high) ac.setFan(kMideaACFanHigh); else ac.setFan(kMideaACFanAuto); ac.send(); } }主循环里只需要维护WiFi和MQTT连接收到消息就执行上面的逻辑。因为红外是一锤子买卖命令发出去之后不需要回读状态HA自己会维护当前设置的记忆。5.4 丢包与重发发送时序要不要加保护红外发送是阻塞式的一帧大约百来毫秒期间WiFi收发会暂时卡顿实测对MQTT控制没什么影响。但有两点值得注意第一连续多条命令别发太急。有些空调处理完一帧之后需要一小段时间才会进入可接收状态我遇到过HA一次下发模式加温度加风量三条指令全在1秒内到达结果空调只响应了前两条。解决办法是在自动化里给动作加个1到2秒的延时或者干脆在固件里做一个小的队列每条命令间隔200毫秒再发。第二如果你的HA里同时有好几个实体都在给同一台空调下命令尽量把控制收敛到一个自动化里避免并发。真遇到并发就靠ESP8266那边的串行处理兜底反正它发完一条才处理下一条。6. Home Assistant侧的两种接法6.1 路线AMQTT Climate组件逐项映射ESP8266固件写好了HA这边的MQTT Climate配置可以把空调暴露成一个标准的温控实体支持在卡片上直接调温度、切模式、改风量。YAML配置大概是这样的climate: - platform: mqtt name: 卧室空调 modes: - off - auto - cool - dry - fan_only - heat fan_modes: - auto - low - medium - high power_command_topic: bedroom_ac/power/set mode_command_topic: bedroom_ac/mode/set temperature_command_topic: bedroom_ac/temp/set fan_mode_command_topic: bedroom_ac/fan/set current_temperature_topic: bedroom_ac/current_temp temp_step: 1这里current_temperature_topic是可选的如果你在ESP8266上还挂了DHT22之类的温湿度传感器可以把当前室温发上来HA的温控卡片就能显示实际温度和设定温度的对比。没有传感器也可以正常工作最多就是卡片上的当前温度显示不出来。要注意通过power_command_topic关机时HA发给MQTT的payload是OFF通过mode_command_topic切到off时payload是off大小写不一样固件里两种情况最好都兼容。6.2 路线BESPHome的midea_ir组件如果你不想写固件而且你的空调格式和ESPHome内置的Midea协议匹配能走ESPHome就尽量走ESPHome这是最快的路。ESPHome早期版本就支持Midea红外空调直接在YAML里声明即可esphome: name: bedroom-ac platform: ESP8266 board: nodemcuv2 wifi: ssid: your_wifi password: your_password api: logger: climate: - platform: midea_ir name: Bedroom AC transmitter: pin: GPIO5 carrier_frequency: 38kHz min_temperature: 17 max_temperature: 30烧录之后Home Assistant通过API发现设备自动生成一个climate.bedroom_ac实体温度、模式、风量、开关全都映射好了不需要在HA里写任何YAML配置。ESPHome的方案还自带OTA更新和日志功能维护起来很舒服。需要提醒的是ESPHome内置的midea_ir依赖IRremoteESP8266的协议实现它对主流美的机型覆盖已经很好但如果你遇到温度能设置但空调没反应的情况多半是协议细节不匹配这时候就得退回自写固件的路线按自己抓包的协议表来做。6.3 自动化示例温度超过30度自动开空调有了实体之后自动化就是顺理成章的事情。比如夏天想实现室温超过30度自动开空调制冷26度automation: - alias: 室温超过30度自动开空调 trigger: - platform: numeric_state entity_id: sensor.living_room_temperature above: 30 action: - service: climate.turn_on target: entity_id: climate.bedroom_ac - service: climate.set_temperature target: entity_id: climate.bedroom_ac data: temperature: 26再配合一个下班回家的场景触发条件改成zone或者手机GPS进入家庭区域动作里多一个delay让空调在预计到家前10分钟启动就能实现人没到家空调先凉快的效果。6.4 两套方案的取舍对比对比维度MQTT自定义固件ESPHome上手难度高要处理WiFi、MQTT、JSON解析低一个YAML文件搞定灵活性高什么指令都能自定义受内置协议支持范围限制未知型号支持只要抓到协议就能做需要ESPHome已支持的协议HA实体生成手工配置MQTT Climate自动生成Climate实体维护成本固件和HA配置都要管基本只维护ESPHome一份YAML我的建议很直接如果你的美的空调能被IRremoteESP8266识别或者你只是想快点用上先上ESPHome。如果你做这个项目的目的是研究协议、或者你的机器冷门到库都不认识那就老老实实走自写固件加MQTT收获也大得多。7. 实测踩坑记录距离、供电、型号差异与校验坑7.1 发射距离和角度的坑我第一次搭电路时图省事让红外LED直接接在GPIO上3.3V只有十几毫安驱动电流结果空调离模块不到两米都爱理不理。换成三极管加5V供电、限流电阻22Ω的方案之后发射距离直接提升到七八米。还有一个容易被忽略的点空调的红外接收窗通常在前面板靠近显示屏的位置模块摆放时要尽量对这个方向。客厅如果杂物多LED稍微被挡一下信号就可能触发不了。后来我把模块挪到空调正对面的书架上问题就再没出现过。7.2 WiFi一跑红外就飘供电噪声ESP8266一建立WiFi连接射频收发瞬间的电流尖峰很大如果USB电源质量一般5V电压会被拉出明显的纹波红外LED的发射强度跟着波动空调就会偶尔抽风不响应。一开始我以为是代码问题折腾好久才发现是供电。解决办法是给电路板上并一个470μF的电解电容放在5V和GND之间靠近红外LED的位置。加上之后同样的代码和位置连续测试几十次都没有失手。如果你用的是锂电池或者充电宝供电也要留意输出电流能不能跟上。7.3 同一个品牌也可能有完全不同的帧结构美的空调近年来的产品迭代很快不同批次的遥控器编码方式并不完全一致。我见过同一时期不同型号的美的红外帧总长度就有48bit和56bit两种Byte0固定值也各不相同。网上有人分享的代码你直接抄过来大概率是发了个寂寞。所以我在前面反复强调抓包的重要性它不只是仪式感而是整个项目的根基。哪怕别人告诉你他的帧格式和我的一样你也应该在自家遥控器上验证一遍花不了几分钟。7.4 校验字节算错的薛定谔反应如果你修改了一帧里面的某个字段但没有同步更新校验字节空调的反应通常是什么都不做。不开机、不响、不闪灯就像你的红外LED根本没在工作。这个薛定谔反应最容易让人误判成硬件问题。我的排查方法是分两步走先用接收头或者原装遥控器抓一帧已知正常的原始码用sendRaw原样发出去如果空调响应了说明发射电路没问题剩下的就是帧内容或校验字节的问题。这时候再去核对前5个字节的累加和以及0x100减法的计算步骤九成能找出毛病。7.5 提升长期稳定性的几个改动项目稳定运行之后有几个细节值得顺手优化面包板换成洞洞板加焊点杜邦线的氧化和松动是长期运行的大敌红外LED的引脚套上热缩管防止和别的焊点短路模块固定在一个不碍事但正对空调的位置别藏在电视柜背面保留串口和烧录引脚方便以后改协议或调日志定时开关机的自动化放在HA里做ESP8266固件保持纯粹只负责收发指令这套东西在我家客厅已经连续跑了大半年中间只因为挪动家具重新调过一次模块位置。如果你也要做类似的项目我的建议很简单先花一个晚上把抓包和帧结构搞明白再决定是走ESPHome还是自写固件。协议这东西一旦你亲眼看到那串0和1是怎么被拆出来的后面所有方案都只是时间问题。