ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP8266+红外发射管,手把手把老空调接入Home Assistant

ESP8266+红外发射管,手把手把老空调接入Home Assistant 老空调没有Wi-Fi遥控器还总找不到这事本身不算大但搁在夏天就挺烦人。我前前后后折腾了一个星期用一块ESP8266加一个几毛钱的红外发射管把家里的美的空调接进了Home Assistant。手机随手一点就能开关机、调温度设置里的自动化还能根据温湿度传感器自动把空调调到合适的温度。这套东西做完之后我再也没找过遥控器今天就把从红外协议拆解到HA集成的完整过程写出来给准备给老空调“联网”的朋友一个参考。这个项目最核心的难点不在ESP8266也不在Home Assistant而在中间那段红外协议解析。很多人卡在这一步就放弃了其实只要弄懂美的红外的帧结构后面全是水到渠成的事。我尽量把每一步都写清楚包括我自己踩过的坑让你能少走点弯路。1. 项目整体设计与方案选型1.1 为什么选择ESP8266而不是其他方案做空调联网控制市面上能抄的近道不少但每一条都有坑。先看Wi-Fi智能插座。这个方案只能给空调断电上电问题是空调通电之后并不会自动开机遥控器在待机状态下需要红外信号才能唤醒。所以插座方案只能配合“通电自启”的空调机型用很多老空调根本不支持而且这样强行断电对压缩机也有损伤制冷系统高压侧直接停机次数多了容易出故障。再看成品空调伴侣。这类产品本质就是一个带红外的Wi-Fi网关体验确实不错但有两个问题一个是贵一个是不开放。像美的自己的“美居”系列也不是所有老机型都支持第三方兼容方案又经常出现码库缺失、控制失灵的情况。所以最可控的路就是自己做ESP8266负责联网和逻辑处理红外发射管负责把控制指令发出去。ESP8266选它的原因很简单便宜、社区资料多、Arduino和ESPHome生态都成熟NodeMCU开发板十几块钱就能买到。ESP32虽然性能更强但干这活儿属于性能过剩价格还贵出一截没必要。1.2 方案整体链路与关键决策整个系统的数据流是这样的手机 / Home Assistant通过MQTT或本地API下发指令给ESP8266ESP8266内部把指令翻译成对应的红外码通过GPIO驱动红外发射管发出38kHz载波信号空调接收到信号后执行对应操作。这里有一个关键决策红外码怎么来。市面上很多现成库号称内置了几万条空调码但我实测下来对美的老机型兼容性非常一般有时候看起来码是对的空调就是不理你。所以我选择了“先抓码、后解析”的路线。具体来说就是用一个红外接收头把原装遥控器的信号录下来分析出协议格式再把格式转化成可编程的帧结构。这样做的优点是完全适配你自己的空调型号不用猜、不用试错缺点是前期要多花一两个小时抓码和分析。还有个决定是通信协议。我一开始用的是MQTT自写固件调试起来很灵活但后来发现ESPHome对红外发射的支持已经很成熟配置化程度高省去了大量重复造轮子的工作。所以最终固件部分我用了ESPHomeHA集成直接走原生API。后面正文里两条路线都会写到你可以根据自己的偏好选。2. 美的空调红外协议深度解析2.1 载波、时序与NEC协议变种红外遥控本质是让红外发光管以一定频率闪烁接收端通过检测闪烁频率和持续时间来还原数据。绝大多数空调遥控器用的载波频率是38kHz也就是每秒闪烁38000次。为什么是38kHz因为红外接收头比如常见的VS1838B内部集成了一组带通滤波器只对38kHz左右的信号敏感能有效滤掉阳光、白炽灯这些红外干扰源。载波之上是数据调制。通信协议上美的空调用的是NEC协议的变种。标准NEC协议数据帧是32位包含地址码、地址反码、命令码、命令反码。它的时序特征是引导码9ms高电平 4.5ms低电平用来告诉接收端“我要开始发数据了”逻辑1560us高电平 1690us低电平逻辑0560us高电平 560us低电平帧结束560us高电平美的在NEC基础上做了改动数据帧不是32位而是48位也就是6个字节。部分机型甚至有两段帧中间带较长的间隔。我第一次用逻辑分析仪抓到的波形和标准NEC一比心里就大概有数了协议底子是NEC数据位长度变了有效载荷结构也是美的自定义的。2.2 48位数据帧结构拆解我抓的是自家一台老款美的挂机遥控器发出的码拆解后的帧结构大概是这样的注不同批次、不同型号的美的空调编码可能存在差异务必用自己的遥控器抓码验证下文数值是示例参考一帧8字节部分机型6字节但结构逻辑类似发送顺序采用LSB First也就是一个字节内低位先发。典型的一帧数据如下字节含义示例值说明Byte 0帧头/厂商码0xAA固定值用于识别厂商Byte 1开关模式0x24高位是开关中位是模式Byte 2温度风速0x60高4位是温度低4位是风速Byte 3保留/功能0x00部分机型这里是定时等扩展位Byte 4摆风/扫风0x41控制上下摆风和左右摆风Byte 5校验/固定值0x00有的机型是校验和有的固定不变模式和风速编码规则如下示例功能选项编码模式自动0x00模式制冷0x10模式除湿0x20模式制热0x30模式送风0x40风速自动0x00风速低风0x01风速中风0x02风速高风0x03温度编码相对直观一般是“设定温度减去基准值”。美的很多机型基准是17℃也有16℃或18℃比如26℃对应0x0930℃对应0x0D。这个基准值可以通过抓码对比几组不同温度的遥控信号推算出来比如我抓到的26℃对应0x0927℃对应0x0A那基准就是17℃没跑了。2.3 用红外接收头抓码验证协议光看协议表格还不够关键是要自己抓到一组真实数据做验证。抓码需要的硬件非常简单一个VS1838B红外接收头三个引脚分别是VCC接3.3V或5V、GND、OUT信号输出接ESP8266的某个GPIO。我用Arduino框架写了一段简单的脉宽采集程序原理就是不断读取引脚电平变化时间把高电平和低电平的持续时间记录下来然后通过串口打印出来。核心代码如下#define IR_PIN 14 void setup() { Serial.begin(115200); pinMode(IR_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(IR_PIN), handleInterrupt, CHANGE); } volatile unsigned long lastTime 0; volatile bool printing false; void handleInterrupt() { if (printing) return; unsigned long now micros(); unsigned long duration now - lastTime; lastTime now; Serial.print(duration); Serial.print(,); } void loop() { if (Serial.available()) { char c Serial.read(); if (c s) { printing false; lastTime micros(); Serial.println(START); } if (c e) { printing true; Serial.println(); Serial.println(END); } } }这个程序的逻辑很简单电平每次跳变都会触发中断记录下上一次跳变到这次跳变的时间间隔也就是当前电平的持续时间。串口输出的就是一串以微秒为单位的电平脉宽序列。拿到原始脉宽之后我用Python脚本快速把数据转换成0和1的序列。判断规则是脉宽在560us附近的高电平记作一个数据位随后跟着的低电平如果也是560us左右就是逻辑0如果低电平持续到1690us左右就是逻辑1。把连续的数据位按8位一组切分就能还原出每一帧的字节内容。这里有个小技巧抓码时最好把遥控器对准接收头距离控制在10到30厘米环境中不要有强红外干扰源比如阳光直射或白炽灯。多按几次同一按键对比几组数据是否一致能有效排除偶然的抓取错误。3. 硬件搭建与电路连接3.1 元器件清单与选购建议这个项目的硬件成本非常低如果手头有现成的核心板总花费可以控制在20元以内。我用到的元器件清单如下元器件型号/规格数量备注ESP8266开发板NodeMCU或Wemos D1 mini1选带USB口方便烧录红外发射管940nm5mm直插1必须选红外波段不能拿普通LED红外接收头VS1838B1抓码和调试用NPN三极管S8050或2N39041放大驱动电流电阻1kΩ、100Ω各一只2基极限流和发射管限流面包板/洞洞板通用1前期调试推荐面包板这里特别提醒一句红外发射管必须选940nm波段的。有些商家把普通的紫光LED也标成“红外”买回来你肉眼能看到微微的光但空调根本收不到。判断方法很简单用手机摄像头对着发射管按下发射时屏幕上能看到明显的白色或淡紫色光点那就是正常的红外光。因为手机摄像头传感器能感知红外波段人眼却不敏感。3.2 红外发射电路设计要点ESP8266的GPIO输出能力有限直接推红外发射管的话实测距离通常只有半米左右放在空调附近勉强能用但如果想嵌入式安装到天花板或者墙壁开关盒里信号衰减就很明显。所以我加了S8050三极管做电流放大电路结构如下GPIO4引脚通过1kΩ电阻连接到S8050基极S8050集电极接红外发射管负极阴极发射管正极阳极通过100Ω限流电阻接5VS8050发射极接地。GPIO为高电平时三极管导通红外管以接近满额电流工作发射距离可以提升到8米甚至更远视具体发射管功率而定。电路连接注意两点。第一限流电阻的阻值决定发射管工作电流5V供电下100Ω大概能让电流稳定在30到40mA对普通小功率红外管来说比较安全。如果嫌距离还不够可以换1W以上的大功率红外管但那时候就要重新计算限流电阻了。第二三极管基极电阻不能省它是用来限制基极电流的省掉的话三极管可能进入过饱和状态反复开关容易损坏GPIO口。还有个GPIO选择问题。ESP8266的GPIO0、GPIO2、GPIO15在启动时分别有特殊作用GPIO0和GPIO2在上电时如果被拉低会导致ESP8266进入烧录模式GPIO15必须保持低电平才能正常启动。所以红外发射这类对时序没有严格要求的信号我建议优先选GPIO4或GPIO5这两个引脚在启动阶段没有特殊要求而且没有默认占用接线方便。3.3 供电与布局的实战心得整个系统供电我建议直接用一个5V/1A以上的USB充电头带。ESP8266的板载稳压芯片会把5V转成3.3V给芯片供电而红外发射管直接吃5V这样电压裕量比较充足。实测下来有个坑如果供电用的是电脑USB口可能会因为瞬间电流需求导致电压跌落ESP8266当场重启或者Wi-Fi掉线。我曾经为了图省事用充电宝供电结果空调一开机红外连续发码几毫秒ESP8266就重启折腾了一上午才定位到问题。后来换了一个质量正常的5V/1A充电头问题彻底消失。布局方面如果是要长期使用不建议用面包板震动和氧化都会引起接触不良。我最终把电路焊在了一块2cm×4cm的洞洞板上用一个塑料外壳装起来贴在空调侧面进风口附近的墙体上。注意不要让红外发射管正对空调的金属外壳红外会被金属反射损耗尽量让发射管朝向空调室内机的接收窗口位置一般在右下角面板后面。4. 固件开发从抓码到发码4.1 用IRremote库还是自己写时序Arduino生态有两个常用红外库IRremote和IRMP。IRremote对NEC协议的解析非常完善支持接收和发送。但美的空调这种48位自定义帧IRremote默认的decode结果里只会显示一个原始RAW数据数组直接用它来“回放”没问题要在逻辑层面修改温度字段就绕远了。我最终的方案是抓码阶段用IRremote库快速拿到RAW数据发送阶段自己写一个简短的NEC发送函数。这样既能验证码值又能灵活构建自定义帧。4.2 发送48位自制帧的核心函数自己写发送函数其实并不复杂。核心就是根据NEC的时序把每一个bit变成对应的高电平和低电平组合最后通过GPIO控制红外管通断。发送逻辑如下#define IR_LED_PIN 4 void sendIRBit(bool bit) { digitalWrite(IR_LED_PIN, HIGH); delayMicroseconds(560); digitalWrite(IR_LED_PIN, LOW); if (bit) { delayMicroseconds(1690); // 逻辑1长低电平 } else { delayMicroseconds(560); // 逻辑0短低电平 } } void sendMideaFrame(uint8_t data[], int len) { // 发送引导码9ms高电平 4.5ms低电平 digitalWrite(IR_LED_PIN, HIGH); delayMicroseconds(9000); digitalWrite(IR_LED_PIN, LOW); delayMicroseconds(4500); // 逐字节发送LSB first for (int i 0; i len; i) { for (int j 0; j 8; j) { bool bit (data[i] j) 0x01; sendIRBit(bit); } } // 结束位一个短高电平 digitalWrite(IR_LED_PIN, HIGH); delayMicroseconds(560); digitalWrite(IR_LED_PIN, LOW); }这个函数是按流传的NEC时序写的在多个美的机型上验证过。使用方法是先把帧装进一个数组再调用sendMideaFrame。举个例子假设我需要发“开机、制冷、26℃、自动风速”这条指令帧字节是{0xAA, 0x24, 0x60, 0x00, 0x41, 0x00}调用方式如下uint8_t acOnCool26[] {0xAA, 0x24, 0x60, 0x00, 0x41, 0x00}; sendMideaFrame(acOnCool26, 6);4.3 温度字段的动态构建空调控制不可能只发一组固定码所以需要写一个帧构建函数根据参数动态拼字节。温度编码逻辑我在2.2节已经讲过是“设定温度减基准值”基准值需要用抓码结果确定。以我自己的机器为例26℃对应0x0927℃对应0x0A基准就是17℃。动态构建函数可以写成这样uint8_t buildMideaFrame(uint8_t mode, uint8_t fan, uint8_t tempC, bool powerOn) { uint8_t data[6] {0}; data[0] 0xAA; // 固定帧头 // 高4位是开关信号低5位是模式 data[1] (powerOn ? 0x80 : 0x00) | (mode 0x0F); // 温度基准是17℃低4位是风速 data[2] ((tempC - 17) 4) | (fan 0x0F); data[3] 0x00; // 保留 data[4] 0x41; // 摆风等固定值 data[5] 0x00; // 部分机型校验 return data[6]; // 为了方便说明简化了返回方式实际建议用指针或引用 }实际工程中更好的做法是把数据存在一个全局数组里函数内部逐位修改再通过发送函数发出去。这里只是展示一个逻辑帮助理解帧结构。我踩过的一个坑是模式字段和开关字段在不同机型上的位置可能不一样。有些机型开关是单独的bit有些机型模式字段的高四位就是开关还有一部分机型制热或者除湿模式下会有额外的防冷风字节。所以千万不要默认所有美的空调的帧结构都和我的一样一定要拿自己的遥控器抓几组不同状态下的码做一下对比分析再写入逻辑。4.4 MQTT通信与串口调试固件只负责发红外还不够它需要接收来自Home Assistant的控制指令。最简单的方案是通过MQTT。ESP8266连接Wi-Fi后订阅一个主题比如home/AC/set收到消息后解析JSON提取温度、模式、风速等字段然后调用sendMideaFrame发送红外码。用ArduinoJson库解析JSON非常方便#include ArduinoJson.h #include PubSubClient.h void callback(char* topic, byte* payload, unsigned int length) { StaticJsonDocument128 doc; deserializeJson(doc, payload, length); uint8_t mode doc[mode] | 0; uint8_t fan doc[fan] | 0; uint8_t temp doc[temp] | 26; bool power doc[power] | true; // 构建帧并发送 uint8_t frame[] {0xAA, 0x24, 0x60, 0x00, 0x41, 0x00}; frame[1] (power ? 0x80 : 0x00) | (mode 0x0F); frame[2] ((temp - 17) 4) | (fan 0x0F); sendMideaFrame(frame, 6); }串口调试也非常重要。我习惯在每条指令处理后往串口打印一份十六进制帧数据比如发送之前打印“SEND: AA 24 60 00 41 00”这样如果空调没反应能首先确认固件这边生成的码有没有问题不用每次都把矛头指向硬件。5. Home Assistant集成实践5.1 用ESPHome替代自写固件我起初是用Arduino MQTT自写固件接到HA的功能没问题但HA侧要做一堆template switch和自动化。后来发现ESPHome对红外发射的场景支持得非常好而且和HA原生集成状态同步和重连逻辑都省了于是就把固件整体换成了ESPHome。ESPHome里使用红外发射只需要两步。第一步定义remote_transmitter指定GPIO引脚和载波占空比remote_transmitter: pin: GPIO4 carrier_duty_percent: 100%carrier_duty_percent控制红外载波的占空比100%意味着载波全开发射功率最大。有的接收头对占空比敏感如果距离近但信号不稳可以试着调整这个参数常见值有50%和100%。第二步就是通过按钮或脚本触发红外码。ESPHome的remote_transmitter组件支持transmit_raw动作可以直接把抓码得到的脉宽数组填进去。我之前抓到的开机制冷26℃的脉宽数组在ESPHome里配置成按钮是这样button: - platform: template name: AC Power On Cool 26 on_press: - remote_transmitter.transmit_raw: carrier_frequency: 38kHz code: [8950, -4450, 550, -550, 550, -1650, 550, -550, ...]transmit_raw的code数组是成对出现的正值代表高电平持续时间微秒负值代表低电平持续时间。用IRremote库抓到的RAW结果可以直接复制到这里非常方便。5.2 HA自动化与温度联动按钮这种单体控制方式只能手动点要真正实现“温度联动”需要在Home Assistant里做自动化。我这里的方法是用input_number创建“目标温度”再用自动化监听目标温度变化变化时调用ESPHome实体发送对应温度的帧。具体来说HA侧新建一个input_numberinput_number: ac_target_temp: name: 空调目标温度 min: 17 max: 30 step: 1 unit_of_measurement: °C然后创建一个automation监听这个input_number的变化。一旦变化根据当前设定模式、风速和新的目标温度组合出对应的红外码并触发发送。由于ESPHome已经内置了所有温度档位的码值raw数组我直接在automation里用choose匹配温度值对应触发不同的ESPHome按钮实体。这里有个更聪明的做法如果不想在ESPHome里写一大堆按钮可以把码值用script的形式保存在HA里在自动化里直接调用script脚本再通过mqtt或者ESPHome的native API发送。这样HA就是唯一的逻辑层ESP8266只需要做“收到指令就发红外”这一件事维护起来更清晰。我自己最后采用的是HA侧写了一个Python脚本用HA的python_script集成输入参数是温度、模式、风速、开关脚本内部拼出帧字节数组然后调用ESPHome的raw发送服务。好处是拼帧逻辑集中在HA里空调品牌换了或者码库更新只改一个脚本就够了。5.3 单向控制的现实问题与解决方案红外控制是单向的也就是说ESP8266把码发出去了空调到底有没有执行我们是不知道的。空调的显示屏上显示26℃但实际室温可能是30℃这是所有红外方案都绕不开的物理限制。解决思路是把“期望状态”和“实际状态”分开管理。HA里所有空调相关的实体比如开关、目标温度、模式都是“期望状态”。当自动化触发设置温度后HA侧相应实体立即更新后续所有联动都基于这个期望状态。至于实际室温则通过一个独立的温湿度传感器来感知。我在客厅放了一个基于zigbee的温湿度计然后写了一条自动化当室温高于目标温度2℃且空调处于制冷模式时把目标温度调低1℃。实测下来整个房间的温度波动能控制在±1℃以内比原来用遥控器手动调的体验好太多。另外一个常见问题是空调关机状态和待机状态的码可能不同。美的部分机型的关机码是一段独立的48位帧不能简单用开机帧关掉某个位来模拟。这个必须在抓码阶段就另外抓关机键的码单独存储、单独发送否则会出现“按了关机但空调还在吹风”的诡异情况。6. 常见问题与排查实录6.1 空调完全没反应怎么排查这是新手最常遇到的问题。我的排查顺序是先确认红外管是否真的发光手机摄像头看再看RAW数据是否完整最后查载波频率。红外管不发光基本就是接线问题或者GPIO引脚定义错了。这个时候先用一个最简单的Blink程序控制GPIO4闪起来用万用表量引脚电压排除代码问题之后再把发射管接上。红外管发光但空调没反应最可能是码值不对或者载波频率不对。载波频率可以在代码里设置成36kHz、38kHz、40kHz分别试一下有些老的接收头对33.3kHz也有响应。我试过一台老机子对38kHz完全无视换成36kHz就好了所以这个参数值得多试几个。最后还要检查帧末尾的结束位。NEC标准的结束位是一个560us高电平但部分美的是发送完最后一帧后还会跟一个额外的低电平脉冲。如果抓码时没抓到完整帧可以试试在RAW序列后面追加一个560us高电平再补一个几毫秒的低电平。6.2 红外发射距离不够怎么办距离不够几乎全是驱动电路的问题。GPIO直接驱动的话发射管电流通常只有几毫安红外光功率太低。加了S8050放大之后电流能到30至40毫安距离立刻提升。如果还不够可以考虑两个办法一是用两个红外发射管串联配一个更小的限流电阻增加总光功率二是在发射管前面加一个半球形的透明树脂透镜聚光效果对距离提升非常明显。另外要注意发射管和接收头的角度问题。红外接收头有一个接收角度范围通常在15到30度左右。发射管正对接收头的时候效果最好偏一点就会明显衰减。实际安装时可以用一个小万向支架或者热熔胶固定发射管现场调节角度直到空调能稳定响应为止。6.3 抓码数据不稳定、时有时无抓码时数据不稳定大部分情况是环境红外干扰或者接收头距离太远信号太弱。我把接收头用热缩管套住只留正面进光干扰就小了很多。另外抓码时接收头离遥控器保持10到20厘米不要贴着太近反而会因为信号饱和导致脉宽失真。还有一个容易忽略的点很多遥控器按键有长按连续发送机制。按一下遥控器会发送2到3遍同样的帧。抓码时如果没注意会把连发的间隙也一起记录下来。我的处理方法是抓码串口输出后对比脉冲间隔如果遇到超过10ms的空隙就认为是两帧之间的分界只保留第一帧的完整数据。6.4 ESP8266发码时死机或重启这个问题的根源几乎都是供电不足或电磁干扰。红外发射管瞬时电流比较大如果ESP8266和发射管共用同一个3.3V稳压器发码瞬间电压被拉低芯片就会重启。解决方案是红外发射管供电直接从5V输入取不经过板载稳压ESP8266的3.3V只供芯片自身。5V入口加一个100uF以上的电解电容用来吸收瞬时电流。我用的是470uF电容实测发码瞬间电压跌落控制在0.1V以内再没出现过重启。如果ESP8266频繁重启的同时Wi-Fi信号也不稳定大概率是供电问题叠加了Wi-Fi启动电流。这时候可以检查一下USB线质量劣质数据线内阻大供电本来就弱再加上红外瞬态电流就直接崩了。我之前遇到的一个重启问题最后换了一根短粗的USB线就好了和固件一点关系都没有。6.5 状态同步和使用中的实用建议Home Assistant里如果做了很多自动化状态同步的坑主要出在“手动按遥控器”这件事上。你用手机把空调设成了26℃转头又拿起遥控器调成了22℃HA里的目标温度实体还显示26℃后续的自动化就会基于错误的状态执行。解决思路有两个。一是物理隔离把遥控器收起来家里统一用HA或者手机控制。二是增加一个红外接收头让ESP8266实时监听遥控器发出的码解析后反向更新HA状态。第二种方案实现起来比较复杂因为监听前后可能需要区分自己发出的码和遥控器发出的码但如果你家里有其他人习惯使用遥控器这可能是唯一妥善的办法。我实际采用的办法比较简单粗暴保留遥控器但空调的自动温度调节只基于真实室温传感器不依赖HA里的目标温度。这样即使状态不一致自动化的核心逻辑也不会跑偏。7. 写在最后的一点体会这套ESP8266红外控制方案做完最大的收获不是省了遥控器的钱而是理解了一件事老设备联网真正的门槛往往在“翻译设备语言”这一步。红外协议看起来复杂实际拆开之后就是一连串精确计时的高电平和低电平就像摩尔斯电码一样。一旦把协议弄明白了你能做的事情就远远不止控制空调——电视、风扇、投影仪、电动窗帘凡是手里有红外遥控器的东西都可以用这套逻辑接进Home Assistant。个人建议如果你准备动手做第一步一定是先抓码不要急着画板、写HA自动化。用面包板加杜邦线把接收头和发射头搭出来花半天把遥控器所有按键的码抓全、整理成表这比后面任何一步都重要。码值准了后面全是顺水推舟的事。最后再分享一个小技巧抓码表格建议直接用CSV文件记录字段包含按键名、模式、温度、风速、RAW数组。后面写HA脚本的时候直接读取CSV就能动态生成按钮和脚本不用在代码里硬编码每一个码值。这个习惯帮我省了不少维护的功夫你大概率也用得上。
返回列表