分析一下格力空调遥控器 YAPOF3 的红外编码,现在中文互联网上只能找到 YB0F2
的一些分析资料,而且还都是一篇文章一堆人无脑的转来转去,美其名曰“原创”,其中也可能混杂大量的错误信息,但我手上没有 YB0F2 的空调也没法去验证。现在的空调用的是 YAPOF3 的空调,至少中文互联网上是一点资源都没有。
刚好最近有时间去GitHub上找了一下,找到一个叫 IRRemoteESP8266
的项目,里面有相关资料。仓库地址在 https://github.com/crankyoldgit/IRremoteESP8266
根据仓库里面的注释,可以看出,YAPOF3 被分类到了 Kelvinator 牌子下,其中还有YAP0F8,查看结构体可以看出,这个信号的编码用的是16字节长度,也就是128 bits。而目前中文互联网上流传的“格力红外编码解析(长码)”等都提到是35 bits加32 bits的组合,明显不符合。
并且根据我用红外接收管抓的数据来看,YAPOF3 发出的红外数据明显长度超过了 67 bits,反而是的确符合128 bits的长度。
接下来就进入正题,基于这个代码,以及一些实际测试,分析一下 YAPOF3 的红外数据。
电平定义
首先要确定此款信号用的红外电平定义。声明一下,由于手上没有示波器,然后自己买的是现成的红外模块,店家用的是自定义的数据格式,虽然有提供电平数据到他的数据格式的转换方式,但我还是没分清楚高低电平。所以以下的电平高和低是参考了中文互联网上的信息,如果你自己发现无效,可以尝试对调一下高低电平的定义。
根据ir_Kelvinator.cpp
中的定义,定义了一个基本的 tick
,为85 us,之后的高低电平时间都是基本 tick 的倍率。
电平名称 | 低电平时间 | 高电平时间 |
二进制 1 | 680 us | 1530 us |
二进制 0 | 680 us | 510us |
头部 | 9010 us | 4505 us |
连接码 | 680 us | 19975 us |
这些时间都不是固定的,有很大的弹性,根据测试,680 us 的低电平时间即使降低到 600 us依然有效;而 510 us的高电平时间增加到 600 us也依然是有效的。
通信格式
先直接看一下代码里面的定义
struct {
// Byte 0
uint8_t Mode :3;
uint8_t Power :1;
uint8_t BasicFan :2;
uint8_t SwingAuto :1;
uint8_t :1; // Sleep Modes 1 & 3 (1 = On, 0 = Off)
// Byte 1
uint8_t Temp :4; // Degrees C.
uint8_t :4;
// Byte 2
uint8_t :4;
uint8_t Turbo :1;
uint8_t Light :1;
uint8_t IonFilter :1;
uint8_t XFan :1;
// Byte 3
uint8_t :4;
uint8_t :2; // (possibly timer related) (Typically 0b01)
uint8_t :2; // End of command block (B01)
// (B010 marker and a gap of 20ms)
// Byte 4
uint8_t SwingV :4;
uint8_t SwingH :1;
uint8_t :3;
// Byte 5~6
uint8_t pad0[2]; // Timer related. Typically 0 except when timer in use.
// Byte 7
uint8_t :4; // (Used in Timer mode)
uint8_t Sum1 :4; // checksum of the previous bytes (0-6)
// (gap of 40ms)
// (header mark and space)
// Byte 8~10
uint8_t pad1[3]; // Repeat of byte 0~2
// Byte 11
uint8_t :4;
uint8_t :2; // (possibly timer related) (Typically 0b11)
uint8_t :2; // End of command block (B01)
// (B010 marker and a gap of 20ms)
// Byte 12
uint8_t :1; // Sleep mode 2 (1 = On, 0=Off)
uint8_t :6; // (Used in Sleep Mode 3, Typically 0b000000)
uint8_t Quiet :1;
// Byte 13
uint8_t :8; // (Sleep Mode 3 related, Typically 0x00)
// Byte 14
uint8_t :4; // (Sleep Mode 3 related, Typically 0b0000)
uint8_t Fan :3;
// Byte 15
uint8_t :4;
uint8_t Sum2 :4; // checksum of the previous bytes (8-14)
};
其中注释已经是写的很详细了,简单的来说,分为两种类型。
一种称为命令块,也就是前四个字节;另一种称为数据块,也就是命令块后面的四个字节。
完整的数据包括两组命令+数据,即:命令#1 + 数据#1 + 连接码 + 命令#2 + 数据#2。
而根据我的实际测试,第二组不发也是可以的,也就是只发第一组命令+数据空调也是有反应的,但不代表所有的空调都可以,建议还是发送完整的格式。
接下来说一下每个域的详细情况。一些空白的部分有可能是没用到,也可能是没有分析出来,就不说了。
数据域
模式(mode):
字面意思的模式,由于只有3个bits,最大也就只能表示7种。
目前定义了五种:
模式 | 值(十进制) |
自动 | 0 |
制冷 | 1 |
干燥 | 2 |
通风 | 3 |
制热 | 4 |
电源(power):
没什么好说的,0是关,1是开。
基础风扇(BasicFan):
由于不知道 Kelvinator 空调是什么情况,但格力的是只有一个风扇控制的好像,这个值一般来说和最后面的风扇是一样的,但由于这个只有两位,最大只能是三档。
当超过三档之后,这个位置就没有用了,更高的风扇由最后的风扇表示。设置成0代表自动模式。
自动扫风(SwingAuto):
我这个格力空调没有这个功能。。理论上也是0关闭,1打开。
温度(Temp):
温度的表示不是从0开始的,空调一般有个最低的温度,这个温度值是从最低温度开始的偏移量。例如一般空调最低温度是16,那么这里的0b0000就是16度;如果要表示20度,则这里应是0b0100。
强劲模式(Turbo):
有的空调可能会有强劲模式,我手上这个也有。
灯光(Light):
控制是否显示空调上的指示灯光。
负离子(IonFilter):
控制是否启用负离子功能,我的空调反正是没有。
XFan:
这一位非常的神秘,根据观察,在我的空调上根据模式的不同,有不同的含义。
当模式为除湿和制冷模式时,这一位表示是否启用干燥模式,0为关闭,1为启用。
而当模式为制热时,这一位代表是否启用电辅热,是反过来的,0代表启用电辅热,而1则代表的是关闭电辅热。
未知的部分和其他:
这里提到了 (possibly timer related) (Typically 0b01)
,也就是这一部分可能和定时器相关,典型的值是0b01,根据观察也确实是0b01。
同时还有一个命令块的结束标识,也是0b01。
上下扫风(SwingV)和左右扫风(SwingH):
主要控制扫风的,这一部分有点啰嗦,用的比较少,具体取值可以直接看代码里面的定义。
校验码1(Sum1):
第一部分的校验码,四个bits,计算方式为:
基础值为10,然后将前面四个字节的低位相加,再加上紧接着三个字节的低位相加,最后结果对256取余,然后取结果的低4位。那么如果是uint8_t的话,溢出之后相当于是取余了,可以不用取余。
静音模式(Quiet):
是否启用静音模式。
风速(Fan):
当风速低于或等于3的时候,这个值和前面的BasicFan
值一样,而当风速大于3的时候,BasicFan
固定设置成0b11
,此处的值则设置成相对于的风速。
校验码2(Sum2):
第二部分的校验码,计算方式和第一部分的一样,只不过起始位置是从第八个字节开始算的。
数据转换为红外电平
有了数据格式之后,接下来就是如何转换成相对于的红外电平。
代码里面用了一个 union 和位域的巧妙组合,实现了只需要转换 union 的另一个变种,即uint8_t[] Raw
即可。
那么具体的转换情况是,要考虑下位域在内存中的存放方式。
数据发送的时候按照低位在前,高位在后的方式发送,例如对于第一个字节来说,最低三位表示的是模式0b100,紧接着是电源开关0b1,以及0b00的自动风扇和0b0的无扫风。
那么第一个字节的完整表示是0b00001100,发送的时候逆过来变成了00110000。后面同理。
完整的电平还包括头和连接码等。
首先发送头部码,9010us的低电平和4505us的高电平。
然后发送实际的数据,发送前四个字节,根据0和1转换为前面提到的高低电平组合。
再接一个0b010的标识符转换成高低电平,以及一个连接码,也就是680us低电平和19975us的高电平。
这样就完成了命令块#1的发送,之后再发送后面四个字节,也就是数据块。
数据块结束之后需要发送一个长连接码,也就是680us的低电平,以及19975*2 = 39950 us的高电平。
这样就完成了第一部分的发送,有时候只发这一部分也可以控制空调。
第二部分也是需要首先发送头部码,也就是9000us的低电平和4500us的高电平。
然后是接下来的四个字节,一个0b010的标识符,一个连接码,和第一部分的是一致。
然后最后是四个字节,同样还有长连接码。
结尾
实际上前面还有很多细节没说,例如Pad
的填充;第九、十、十一字节和第一、二、三字节是一样的;还有一些没有名称的位域,都有典型的默认值什么的。
这些部分建议大家直接看原代码里面的注释,以及相对应的fixup()
函数,IRsend::sendKelvinator()
函数等,大部分复杂的逻辑都已经在上面涵盖到了。
我的空调是 YAP0F20,我也发现和网上说的 35+32 不一样……
看到你这篇文章后,我放弃了……。。。
你可以直接用 IRRemoteESP8266 项目里面的代码改一改。我就是从这里面改到esp32 idf上的。
好的,谢谢回复,我再试试。