格力空调 YAPOF3 红外编码

分析一下格力空调遥控器 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 的倍率。

电平名称低电平时间高电平时间
二进制 1680 us1530 us
二进制 0680 us510us
头部9010 us4505 us
连接码680 us19975 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()函数等,大部分复杂的逻辑都已经在上面涵盖到了。

发表评论

您的电子邮箱地址不会被公开。