用树莓派控制空调并且我还写了个网页来远程!

很久以前突发奇想,想像我的米家台灯一样,为宿舍的空调整一个远程控制系统,然后就慢慢地开始搞了,至于为什么现在才写这篇博客。。。嗯就是懒。

首先在前面说明一下这个文章以及我放在GitHub上的代码的具体内容可能只适用于上海大学南区宿舍(或者整个上大宝山校区?不知道,应该都是一个型号的)的不知道哪个型号的海尔空调,但是方法是通用的。

0x00 原理

程序实现

我的思路大概是这样的,先看看有没有开源方案直接对应我们的空调型号,然后有的话直接用,没有的话自己用红外接收器做逆向工程,把每个参数(其实也不用每个,就常用的功能找到就行)对应的红外码给找出来,最后再考虑前后端的交互问题。

另外红外信号的接收我是用的Linux上的lirc,具体配置和使用方法网上也有很多教程。

空调红外遥控的原理

红外遥控发射器组成了键扫描、编码、发射电路。当按下遥控器上任一按键时,TC9012即产生一串脉冲编码。遥控编码脉冲对 40kHz 载波进行脉冲幅度调制(PAM)后便形成遥控信号,经驱动电路由红外发射管发射出去。红外遥控接收头接收到调制后的遥控信号,经前置放大、限幅放大、带通滤波、峰值检波和波形整形,从而解调出与输入遥控信号反相的遥控脉冲。

好了,上面的我也没看懂。总之遥控的红外信号一般是一段时间序列,亮多少ms,暗多少ms(还是微秒来着?不清楚),再亮再暗,通过不同的时间来表达0和1。在Lirc上录制红外信号就会给你类似这样的时间序列:

pulse 516
space 1683
pulse 545
space 584
pulse 524
space 1678
pulse 548
space 578
...

这里面包含了一段二进制信息,接收器接收到这样的信号之后就能够根据得到的指令做出相应的动作。

此外,空调遥控器和一般的电视什么的遥控器不同。普通遥控器一般就是一个按键对应一个固定的信号,而空调遥控不仅包含按键,还会把空调状态的上下文(context)一并发送给空调,它包含了温度、风速等等参数,空调会根据这个context进行调整,而不是处理具体的某个按键。

硬件实现

这里我直接用身边能用的树莓派4B,红外接收器和发射器都是网上买的元器件,接线的话我参考的是这篇文章里的那个接线图(见“硬件连接”部分)。(主要是拍实物图的话看起来很乱,就不具体讲了)

0x01 寻找开源码库

我好像只找到了一个叫IRext的项目是开源的,别的好像都是收费什么的。然后在IRext的文档上面找到了使用方法,然后我下载了他们所有的海尔空调的解码库,用CPP写了一个demo来遍历所有的编码文件(发送一个空调开的指令),然而,都没有响应,如果有的话直接用就可以了。然后又Google了一波具体型号看看有没有类似的,结果还是失败了。

所以接下来要干的事情就是逆向!

0x02 逆向一波

配置好lirc之后,用你的红外遥控器,用控制变量的思想来进行逆向。在这里可以看到我所有用红外接收器产生的数据。

一般来说,遥控器发射数据时会带一个固定的头部信息,在我这里好像是4个脉冲,我的每个信号都是非常相近的时间序列,所以处理的时候就把这些头部给去掉了,然后按照网上其他空调的教程/介绍,找到了正文里面是用space xxxx(正文一定是pulse space pulse space这样的序列)也就是间隔时间的大小来代表0和1,我的遥控器的结果是1600左右为1,600左右为0,因为硬件不可能做到100%精确,所以在一定范围里就都算0和1。在lirc里面得到的序列,去掉头之后我直接丢到下面这个程序里面,输出它的0和1:

#include <bits/stdc++.h>
using namespace std;
int main() {
    freopen("ir-data-26off-2.txt", "r", stdin); 
    //就是pulse xxxx\nspace xxxx\n...的文件,从lirc里面复制出来的
    string line;
    int sp = 0, cnt = 0;
    while(getline(cin, line)) {
        if(line[0] == 's') {
            sscanf(line.c_str(), "space %d", &sp);
            if(sp != 0) {
                cout << (sp > 1000 ? '1' : '0') << (++cnt % 8 == 0 ? '\n' : ' ');
            }
        }
    }
    return 0;
}

会有类似这样的输出:

1 0 1 0 0 1 1 0
1 0 1 0 0 0 1 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0
0 1 1 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
1 0 0 0 0 1 0 1
1 0 0 0 1 1 0 1

为了方便我就一个字节一行了。

接下来要做的就是用空调遥控器对准你配置好的红外接收器,记录按下按钮的数据(一次按一个,然后保存一下,然后再重新开lirc记录,避免多个混淆了),比如按开记录一次,按关记录一次,按温度升高记录一次,温度下降记录一次……还有风速、风向、模式等等。最后把它们放在一起,看看它们之间的区别。例如开和关做对比,就可以找到空调开关状态对应的位在哪,以及我发现了最后一个字节实际上是校验和,这个文件是我不断测试之后整理出来的我们宿舍空调的遥控信号大部分功能对应的位置的记录。

按照这个思路,我们就可以生成正确的指令码,转换成时间序列,通过红外发射器发送出去,就可以了。是不是很简单(雾)(实际上这个环节花的时间还挺长的,毕竟变量有点多)。

0x03 写发送数据的程序

这个程序我们要实现两个功能,一是根据输入参数来组成对应的二进制码,就是上面那种,二是把这个二进制码转换成p/s的时间序列,调用红外发射器发送出去。

其实很简单,既然知道了每个字节/位对应什么功能,那写个程序生成这样的数组就可以了,毕竟只有自己用,不用考虑不同型号不一样什么的23333。然后按照0和1的时间再生成出最后的时间序列即可(就是不断地switch case(雾。

因为我人比较懒,所以特别底层的红外发射我直接引用了bschwind/ir-slinger,它是调用PI-GPIO库然后处理时间序列,发送出去的。

具体的代码可以看这里面decoder.cppmain.cpp文件。

0x04 前后端

这个就比较自由了,前端随便写好用好看能和后端通信(指HTTP GET/POST)就行。后端也只需要存一个状态以及调用发送程序就可以了。因为并发不可能高,所以我后端状态直接用一个status.json文件保存了,类似这样:

{"power":1,"temperature":26,"mode":0,"swing":1,"windspeed":0}

后端写一个GET /AC_Status用来读取status.json并返回,POST /AC_Status里接受开关、温度、模式、风向风速的参数,直接更改status.json文件并把相应的参数传递给底层程序发送。

虽然没什么用但是安全考虑还是加个HTTPS比较好,主要是别让不该知道的人知道你的前后端地址就行。

具体实现在lonelyion/irz里面的web(前端)和backend(后端)文件夹里面都有,此外irz文件夹就是前面提到那个底层程序的源码。

0x05 后记

如果你没有树莓派或者想用单片机也差不多的,单片机的话就要自己写发送时间序列的代码,(估计也可以参考ir-slinger那个项目的代码),以及如何做前端与后端通信的问题(树莓派因为有完整的Linux所以跑个.Net Core啊PHP啊NodeJS啊都可以的,单片机就有点麻烦,也许之后我会尝试一下),上大的同学可以直接参考irz/analysis-data/description.txt这个文件来做,或者读一下decoder.cpp的代码也能知道(注释也许可能大概写清楚了哪个是哪个)。

因为时间过去得有点久了所以具体踩了哪些坑也忘了,这篇文章主要还是提供一个思路,具体实现的话自己动手得成就感高得多。

哦对了,红外发射器最好要无阻挡地对准空调。

以上,瞎写了一些东西。

本页面的全部内容在 CC BY-NC-SA 4.0 协议之条款下提供,附加条款亦可能应用
本文链接:https://www.lonelyion.com/2019/control-aircon-with-web-and-raspberrypi/