免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

VRF中央空调接入HomeAssistant:NodeRed解析RS485私有协议实战

VRF中央空调接入HomeAssistant:NodeRed解析RS485私有协议实战 1. 为什么VRF中央空调接HomeAssistant会卡在“私有协议”这一步如果你家里装的是大金、日立、三菱电机、东芝这类进口品牌的VRF中央空调大概率会遇到同一个尴尬空调本身是支持智能控制的但官方APP用起来一言难尽想要接到HomeAssistant里统一管理却找不到现成集成。网上翻来覆去就是那几个开源项目要么只支持特定型号要么需要额外购买价格不菲的官方网关搞了半天还是没法“无缝对接”。VRFVariable Refrigerant Flow变制冷剂流量系统和家用分体机最大的区别在于一台室外机带动多台室内机室内机通过一条总线串联通信。这套总线协议在绝大多数品牌里都是闭源的而且每个品牌、甚至同一个品牌的不同系列协议都可能不一样。更麻烦的是很多品牌在楼宇自控市场里的做法是“你有需求就得买我的网关”买回来之后协议文档还不一定给你只有一串串十六进制报文。这就逼着想折腾的人走一条“旁路”——既然空调外机和控制面板之间本来就靠RS485通信那我直接从总线上把数据截出来自己解析再转发给HomeAssistant。这个思路不算新鲜但真正落地的时候坑远比想象的多。而NodeRed在这里扮演的角色不是万能的魔法棒而是一个“翻译官调度员”从串口读字节流、解析私有协议、转换成HA能认的MQTT或实体状态、接收指令并下发。我最后选型NodeRed而不是直接写Python脚本或ESPhome固件原因有几个第一NodeRed的流式编程天然适合处理“串口字节流不停进来、我需要判断帧边界、解析字段、按状态变化推送”这类事件驱动场景第二调试时随时可以插一个debug节点看中间数据不用改了代码还要重启服务第三后续加自动化逻辑直接在同一个面板里编排不用再开一个自动化引擎。如果你也卡在这个阶段我可以负责任地说NodeRed确实是这个场景下综合成本最低的方案。2. 摸清485总线家底硬件选型和链路搭建里的细节2.1 RS485总线的基础认知RS485是一种半双工差分通信标准用A/B两根线传输数据靠两根线之间的电压差来表示逻辑0和1。和我们更熟悉的RS232用正负电压单端传输相比RS485的优势是抗干扰能力强、传输距离长最长1200米、支持多点挂接一条总线最多32个节点或更多具体看收发器型号。VRF空调的内外机通信普遍采用这个物理层正因为它是多点总线我们才能在不破坏原有通信的前提下“旁听”或“插话”。但要注意RS485是半双工的——同一时刻总线上只能有一个人说话。空调外机通过轮询的方式逐台询问室内机状态室内机按地址号应答整个通信是一问一答的节奏。如果我们在总线上也主动发帧就必须严格遵守总线的时序不能在内机应答或者外机轮询的间隙乱发否则会污染总线轻则这个设备无响应重则整条线瘫痪。2.2 硬件清单和接线要点这套方案里最核心的硬件就是USB转RS485适配器。我踩过的坑是不能贪便宜买那种几块钱的CH340转485小板原因不是芯片不行而是很多小板的收发切换电路做得粗糙在高速轮询的空调总线上一旦发生收发切换延迟就会丢帧。推荐用带自动收发切换功能的工业级适配器比如基于MAX13487或ISL3170方案的USB转485实测在9600波特率下连续抓包24小时没有出现错帧。接线位置的选择上最稳的是空调室内机的控制线接线端子。每台室内机旁边都会有一个X1/X2或者P/Q之类的通信端子室内机之间就是靠这两根线串起来的。把USB转485的A端接空调的通信线正端B端接负端注意不要接反接反了是完全收不到数据的。另外如果总线上已经有面板、集中控制器等设备旁听不影响它们正常工作但如果你要主动下发控制帧就要注意总线上是否有其他主机会和你冲突。我最初的方案是直接在室外机主控板上接后来发现室外机内部的通信频率和室内机总线不一样协议也是另一套纯属给自己挖坑。所以这里给一个明确的建议要抓就抓室内机之间的那条总线或者室内机和有线面板之间那条线这才是我们需要的“内机通信总线”。2.3 把Linux主机或树莓派和USB转485接上接线完成之后在运行NodeRed的主机上确认一下设备是否被识别。如果你用的是Debian/Ubuntu或树莓派系统插上USB转485后执行ls /dev/ttyUSB*或者dmesg | grep tty正常情况下会出现/dev/ttyUSB0之类的设备节点。如果设备没出现先换个USB口再查驱动如果是CH340芯片Linux内核自带驱动不用额外装FTDI芯片同理。这里有一个很多人忽略的点串口设备的权限。NodeRed默认运行用户可能没有/dev/ttyUSB0的读写权限表现就是串口节点打开失败。把运行NodeRed的用户加入dialout组sudo usermod -aG dialout $USER之后重启服务这个问题就可以解决。为了避免系统重启后设备节点名漂移导致NodeRed连不上建议用udev规则给USB转485固定一个别名比如/dev/ttyUSB_AC这样配置就不会因为插入顺序变化而失效。3. 最费时间的一步把私有协议“翻译”成人话3.1 先抓数据别急着猜很多人拿到USB转485之后第一反应是上网搜这个品牌的协议文档。如果搜不到就开始焦虑。其实没必要协议逆向没有想象中那么难核心方法论就一句话先抓足够多的样本再找规律。抓包工具我推荐用NodeRed串口节点临时搭一个最小监听流一个串口节点波特率从9600开始试数据位8、无校验、停止位1这是绝大多数空调总线的默认配置如果全是乱码再试偶校验和19200/4800等常见波特率后面接一个debug节点把收到的hex打印出来。就这么裸听一晚上你会积累几千帧数据。这些数据就是你逆向协议的基础。在抓包过程中建议把空调遥控器对着内机操作一遍开机、关机、调温度到16℃、调到30℃、切换模式、调风速、扫风。每做一个动作总线上的报文会立刻变化这些带有变化特征的数据就是你重点分析的样本。3.2 帧结构识别的通用套路拿到hex数据之后第一步是把连续字节流切成“帧”。RS485通信一般是异步帧格式一帧有完整的首尾特征。最常见的切帧方式有两种固定长度帧以及帧头长度字段。固定长度的帧最简单所有报文都是同样字节数比如某些品牌的内机状态上报就是20字节定长。你只要发现大部分报文都落在同一个长度上就基本能确定帧长了。带长度字段的帧要多做一步找到长度字节位置确认它是固定偏移位的然后按照长度把帧切出来。实际抓包数据里经常会出现一包里面包含两帧半这种粘包现象这在NodeRed里是很经典的处理场景后面我会说怎么切。切完帧之后给每一帧标上序号、时间戳和原始hex然后你就开始找规律有没有哪个字节的值和操作开机/关机强相关有没有哪个字节的值在温度调节时跟着变化内机地址固定在哪几个字节这里我举一个真实品牌的例子当然具体协议我不能贴全但可以告诉你规律长什么样。假设帧格式是AA 55 01 02 03 04 0D那AA 55是帧头01是内机地址02是功能码表示状态上报03是开关机状态04是设定温度0D是校验。当你用遥控器调到16℃再调到30℃第5个字节从0x10变成0x1E那基本就实锤了。校验字节通常是把前面所有字节做一个加和或异或这就需要你多试几种算法累加取低字节、异或、CRC8等等。你可以在NodeRed里直接用function节点写一个循环把几种常见校验算法跑一遍看哪个能和最后一字节对应上。3.3 私有协议的“私有”到底在哪里提到私有协议很多人以为像密码一样高深实际上大部分所谓私有协议只是厂家偷懒或故意不给资料本身没有加密只是帧格式和寄存器映射不对公众开放。这意味着你不需要破解什么加密算法只需要把报文和实际行为的对应关系摸出来即可。当然也有个别极端案例比如某些品牌在内机和外机通信之间加了自己的加密逻辑。真遇到这种情况先冷静评估你对室内机的控制是发生在室内机总线上的而室内机本身往往不加密加密的是外机对室内机的轮询。所以旁听和下发控制都走室内机这条线基本可以绕开加密。我建议把整理出来的协议字段画一张表类似这样偏移长度字段名说明取值范围01帧头固定0xAA-11内机地址1-160x01-0x1021功能码0x02状态上报-31开关机1开0关-41设定温度实际值4016℃对应0x38...............这张表是你后续写解析逻辑的“宪法”维护好它整个项目就成功了一半。我用这个方法逆向过两套不同品牌的私有协议最快的花了一晚上最慢的花了三个周末。慢的原因主要是某些远程控制功能靠遥控器无法触发必须靠线控器操作导致样本一直缺规律一直凑不齐。4. NodeRed流编排从串口字节流到HA实体的完整链路4.1 串口读取与帧切割节点设计前面的准备工作做完就到了真正“写代码”的环节。NodeRed的核心概念是流Flow一个流就是一组节点和连线每个节点处理一件事消息在节点间通过msg.payload传递。这个模式非常适合串口数据处理你可以把一条处理链拆成串口读取→缓冲组帧→协议解析→MQTT发布→HA自动发现注册每一步都能单独调试。串口节点本身在NodeRed里是通过node-red-node-serialport这个节点提供的配置好串口设备路径、波特率、数据位、校验位之后它会持续往流里吐数据。但这里有个问题串口节点每次吐出来的msg.payload是一个Buffer里面可能只有几个字节也可能有几十个字节完全没有按帧边界切好。所以第一步要做一个“字节流收拢组帧”的function节点。我实现这个节点的思路大概是这样用一个context.buffer来缓存还没处理完的字节每次新数据进来追加到缓存的末尾然后循环判断如果缓存长度小于最小帧长说明数据还不够等下一包如果缓存里有合法的帧头就从头开始解析一帧如果剩余字节不够一帧长度先hold住等下一包补把完整的帧切出来放进msg.payload往下送剩下的残留在缓存里继续循环处理这个做法在单片机通信里叫“状态机组帧”在NodeRed的function节点里用JavaScript实现并不复杂。补充一个关键细节判断帧完整性的时候最好同时校验长度字段和校验字节而不是只数长度。因为空调总线上的环境可能有干扰单纯凑够长度但校验过不了解析出来的字段就可能完全是错的。4.2 协议解析用可读对象代替裸hex帧切好之后接着做协议解析。这一步的目标是把裸的十六进制Buffer转换成一个结构化的对象比如{ deviceId: 1, cmd: status_report, power: true, setTemp: 24.5, mode: cool, fanSpeed: 3, roomTemp: 26.8, errorCode: null }在function节点里写解析逻辑的时候我建议不要在一堆字节下标里写死魔法数字而是先把Buffer转成数组然后用“读一个字段推进一个游标”的方式写。这样后面万一协议版本有差异只需调整游标偏移数组即可。比如const buf msg.payload; let offset 0; // 帧头检查 if (buf[0] ! 0xAA) { return null; } offset 1; // 设备地址 const deviceId buf[offset]; // 功能码 const cmd buf[offset]; // 状态位bit0是开关机bit1是模式低bit... const status buf[offset]; const power (status 0x01) 1; // 设定温度有0.5℃精度实际值原始值/2 某个偏移 const setTempRaw buf[offset]; const setTemp setTempRaw / 2;这段伪代码缺少很多真实协议里的细节但结构是对的。我需要特别提醒温度是几乎所有空调协议里最容易踩坑的地方。有的品牌用“实际温度40”表示避免负数有的品牌是0.5℃一个步进原始值直接除以2还要判断奇偶有的干脆把整数部分和小数部分拆成两个字节。4.3 通过MQTT Discovery让HA自动生成实体协议解析出来之后下一步就是把数据送进HomeAssistant。方式有几种用MQTT静态配置、HA REST API、或者直接部署NodeRed的HA节点。我推荐MQTT Auto Discovery因为你把实体声明为climate类型之后HA会自动在“配置→设备与服务→MQTT”下创建设备和实体不需要手动写configuration.yaml也不需要重启HA。每个实体通过一个以homeassistant/climate/xxx/config为主题的MQTT消息来声明。配置JSON里最关键的是availability_topic、state_topic、command_topic、temperature_command_topic、mode_command_topic这些字段。这里有个必须注意的细节HA的climate平台对MQTT主题的格式有严格要求如果你直接发布整个实体的stateHA不会自动把字符串“24.5”变成你要的数值。很多人的实体在HA里显示“不可用”或者跳来跳去原因就是这个基本盘没做对。我的做法是用两个独立主题一个状态主题只发布当前状态JSON一个命令主题接收HA下发的目标设置。NodeRed里监听命令主题解析msg.payload里的temperature、mode、fan_mode等字段然后转成空调私有协议的控制帧下发给串口。这样做的好处是职责分明以后要扩展别的功能比如定时、场景只需要再监听新的命令主题即可。在下发控制的时候有一个动作顺序要注意很多空调品牌同一时间只能处理一个控制动作或者控制帧之间必须有几百毫秒间隔。如果你从HA里同时改了温度和模式NodeRed一股脑把三帧连续发下去内机大概率会忽略后面的帧。所以我在发送控制帧之前总是用一个队列节点做串行化每帧发完之后延时200ms再发下一帧。4.4 状态轮询还是被动监听很多人在设计时都会纠结是让NodeRed定时主动查询空调状态还是只被动监听空调自己上报的数据我的答案很明确被动监听为主主动轮询为辅。空调内机会在状态变化时主动上报比如遥控器改变温度、内机检测到室温变化总线马上就有数据。被动监听的好处是省流量、不干扰总线、实时性也高。但问题在于你无法保证所有状态都会主动上报。有些品牌的内机只在“状态变化”和“周期性心跳”时上报无法感知空调实际是否掉线。所以额外加一个兜底方案每5分钟由NodeRed主动发一帧查询命令确认空调是否在线。如果连发三次都没有任何响应就认为该内机掉线把HA里的availability设为离线。5. 实际调试中绕不开的坑数据粘包、字节序与可靠性5.1 粘包和半包真实总线的“基本面”我在前面提到粘包这里展开说一下它为什么在空调485总线里几乎是必然发生的。485总线上各个设备是轮询通信的但一个设备的数据是一个完整帧一个帧发出后总线上不会立刻安静可能紧接着就有另一个设备应答。USB转485适配器往上位机上报数据时经常把好几帧的数据打包成一个TCP/UDP数据块交给串口驱动反映到NodeRed里就是串口节点一次性吐出来一长串Buffer里面包含两帧甚至更多帧也可能刚好是某一帧被切成了两半。组帧节点就是为这个服务的。但要注意一个边界情况如果数据里出现帧头相同的内容比如某个字段的值恰好等于0xAA你的“找帧头”逻辑就会误判。为了避免这种情况切帧的时候不能只找帧头还要结合长度和校验先把“按长度切”做一遍再校验通过就认为这帧有效不通过才退回“搜帧头”模式。5.2 字节序和负温度别让自己的解析“歪了楼”协议解析里最容易出错的几个点我挨个说清楚。第一是16位数值的字节序。比如室温可能是16位的高位在前大端还是低位在前小端你在分析抓包数据时如果只看到一堆字节很容易想当然。我的建议是所有多字节数值都先按大端解析然后对照实际室温判断如果差得离谱再按小端试。另一个更直接的判断技巧是用制冷模式把空调设定温度从16℃调到30℃观察对应字节的递增规律低位递增就是小端高位递增就是大端。第二是负数处理。有些品牌把温度偏移量存成有符号数比如用“当前温度原始值-128”来表示负温度。如果解析时忘了处理符号扩展零下温度会变成一百多度HA里显示出来就像“165.5℃”一开始会以为是传感器坏了实际上就是符号位没处理。第三是小数精度。HA的climate实体支持temperature_step字段0.5℃步进是中央空调最常见的。如果你的解析没有考虑半度所有温度总是整数那只是看着粗糙不是致命问题。但如果你的协议里温度值是实际值乘2你忘了除以2那HA里的温度就是实际值的两倍空调设26℃你这边显示52℃控制下发的温度再一下变全乱。5.3 串口断了怎么办NodeRed进程“活”但数据不流的场景USB转485适配器插在树莓派或软路由上最怕遇到USB口暂态断开又重连。Linux下表现为/dev/ttyUSB0消失又重现NodeRed串口节点的连接句柄已经失效但它不会自动重连。结果是整个流的节点都还在运行串口却一滴数据都不进HA里的温度永远停留在最后时刻看起来像“系统死了”但其实没有。针对这个问题我在串口节点前面包了一层“看门狗”逻辑每30秒检查一次串口节点的连接状态或者用node-red-contrib-serial-monitor这类节点做监听一旦检测到串口连接异常就触发一个“重置串口节点”的动作重新打开串口。还有一个办法是给USB转485适配器做一个USB HUB供电开关检测到设备消失时用GPIO把那个USB口的电源断掉再重新供上强制USB设备物理复位。这招听起来很野但在实际无人值守的部署环境里非常有效。5.4 多台内机的地址映射和组织一条VRF总线上挂着的室内机可能有七八台每一台都有一个地址。你要做的第一件事是从协议里确认地址字节的位置然后把地址映射成HA里的实体名称比如客厅空调、主卧空调。很多人图省事直接用地址数字做entity_id过两天自己都分不清哪个是哪个。HA侧建议引入area和device registry的概念。每个内机注册成一个独立的device把实体归属到对应区域这样在HA的仪表盘里看到的就是“客厅空调”“主卧空调”这样清晰可见的设备列表而不是一串难懂的字节。另外不要忘了给每个设备设置unique_id否则HA重启后实体可能会重复注册新旧实体杂在一起。unique_id我通常用品牌缩写总线地址协议版本组合字符串生成保证稳定不变。6. 能耗统计、场景联动和后续优化方向6.1 用NodeRed从功率信号估算空调能耗VRF空调接入HA之后下一步自然而然会想到能耗统计。绝大多数家用空调本身没有独立的电表接口但有一种间接方案如果你的空调外机支持上报运行电流或功率字段很多商用级VRF内机并不直接上报功率但在某些型号的数据帧里外机会在运行时长或电流检测后附带一个负载率字节可以把这个字节解出来作为功率估算的输入。载荷率的意思是当前压缩机的输出能力比例值从0到100。用载荷率乘以空调的额定功率再乘以一个环境修正系数就能得到一个相对可信的实时功率估算值。NodeRed在这个场景里可以一边监听状态帧一边用function节点把解析后的功率数据通过MQTT发到HA的energy平台。HA的Energy Dashboard建议的数据源是statistics也就是你只需要把MQTT值经过一个sensor实体上传然后HA会自动定期采样生成日/周/月的能耗统计。我用这个方法在中央空调上跑了大半年和实际电费单比对了几个月份误差在15%以内对于决策“今晚开一小时还是开一晚上”已经足够了。6.2 峰谷电价与自动化场景拿到能耗数据之后如果你所在地区有峰谷电价就可以在HA里做“电价策略联动”在低谷电价时段来临之前如果室内温度还没有达到设定值就把空调提前开机预冷或预热。NodeRed里可以装一个node-red-contrib-sun-position之类的节点去计算日出日落也可以用固定时间段的简单判断。我的真实做法是写一个Flow每小时触发一次读取当前电价时段配置和室内温度判断是否进入预冷预热窗口。判断条件写死之后再把这个Flow的节点通过HomeKit或HA的自动化面板开放给家里其他成员控制。6.3 把私有协议“翻译层”独立成服务随着内机数量增多NodeRed的Flow会变得越来越臃肿。我建议在早期就把“协议解析”和“业务逻辑”分离。最简单的做法是把组帧、协议解析、帧过滤这些代码放到一个独立的function节点里然后用一个全局变量来承载协议配置表不要散落在各个节点里。更进一步的做法是把这个翻译层拆成一个独立的MQTT微服务用Node.js或Python跑一个进程纯粹负责串口采集和协议解析NodeRed只负责业务编排和HA对接。这样做的好处是如果以后你想换掉NodeRed改用其他自动化引擎协议层还能源源不断产出标准MQTT消息不会推倒重来。7. 写在最后的一些经验和建议这套方案折腾下来我最想强调的一点是先花时间把协议表整理清楚再碰NodeRed。很多人在串口都还没接到数据的时候就开始拖节点结果解析逻辑改来改去最后发现是帧头都找错了白白浪费好几天。协议是地基NodeRed只是地上部分的积木地基不牢全靠后面补越补越乱。另外如果你准备长期跑这套系统建议把NodeRed的部署环境做得稍微“工业”一点用Docker跑NodeRed把数据卷挂载出来定期备份flows.json串口适配器选带隔离的工业级产品树莓派供电用好的电源而不是手机充电头在NodeRed外面套一层systemd或supervisor做进程守护避免NodeRed自己崩了之后空调直接失联。这些都是我实际踩过跟头之后才补上的说多了都是经验。最后补一个关于安全的小提醒这套系统如果把HA暴露到公网一定要开严格的身份认证和HTTPS因为一旦你的HA被控制家里所有接入设备都会受影响。我的建议是配好HA的长期访问令牌MQTT broker只监听内网公网访问统一走带认证的反向代理不要让这些智能家居服务直接裸奔。毕竟接入空调的目的是让生活更舒服不是让家更“开放”。
返回列表