树莓派折腾记——射频控制开关

前几日看了一篇知乎上面的从零开始打造智能开关的教程https://zhuanlan.zhihu.com/p/34219448,问了作者几个问题之后,便摩拳擦掌想试试,将自己的过程,还有安装中遇到的坑记录于此。建议保留这个网页,看看知乎教程之后,再回来看这个,因为知乎教程由于年代原因,坑还是蛮多的。

树莓派初始化

安装系统,连接wifi这一套操作网上一抓一大把,就直接从安装了系统开始吧,由于我不搞啥图像之类的,显示器也只是在第一次用的时候采用,所以图形化界面简直是鸡肋,平常我就接一根电源线和一个gpio,因此直接选择用lite版本的系统

换源

由于墙的原因,树莓派默认的源,巨慢无比,严重干扰后面的操作,强烈建议换源,但是没有vim换源的操作都很困难,于是只能卑微用默认源先安装了vim

建议使用清华源,里面的教程还是比较详尽的https://mirror.tuna.tsinghua.edu.cn/help/raspbian/

下面是我的树莓派3B的Debian 10版本

1
2
3
4
5
6
# 编辑 `/etc/apt/sources.list` 文件,删除原文件所有内容,用以下内容取代:
deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main non-free contrib rpi
deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main non-free contrib rpi

# 编辑 `/etc/apt/sources.list.d/raspi.list` 文件,删除原文件所有内容,用以下内容取代:
deb http://mirrors.tuna.tsinghua.edu.cn/raspberrypi/ buster main ui

然后sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y

来一次大升级

新建用户并sudo

建议新建一个用户自己来玩一玩,默认的pi用户也不是不能用,只是那个密码有点难输,再加上强迫症有点难受

sudo adduser username 添加用户和用户信息,设置密码

adduser username sudo完事儿

开启ssh

默认是安装好了openssh-server的,但是没有开,于是sudo systemctl enable ssh 开机自启, sudo systemctl start ssh开启ssh

随后就可以通过ssh的方式来连接树莓派进行操作了,方便好多。

折腾射频模块

安装wiringpi

网上教程大多数是

1
2
3
git clone git://git.drogon.net/wiringPi (git我假设大家已经安了哦,没有的话搜一下安装很简单的哦)
cd wiringPi
./build

然而这个作者早就关闭这个仓库了,进入官网也提示手动下载源码的方式无效,但是新版树莓派已经的apt里面已经自带这个了,只需要sudo apt-get install wiringpi -y即可

当你安装了之后,使用gpio -v查看gpio情况的时候,问题就来了,报错

1
2
3
4
5
6
7
Unable to determine hardware version. I see: Hardware : BCM2835,

expecting BCM2708 or BCM2709.
If this is a genuine Raspberry Pi then please report this
to projects@drogon.net. If this is not a Raspberry Pi then you
are on your own as wiringPi is designed to support the
Raspberry Pi ONLY.

带着这个问题我找了好久,官方也不提供下载包了,甚至还找到了你电的一个仓库里面有作者删除之后的遗留

https://github.com/mm1994uestc/WiringPi

然而下载下来手动build似乎也无济于事,github上面逛了一圈issue,说是要升级pi4j,官方的是1.1,升级成1.2之后就好了,然而升级了之后也没有啥用

最后找到了一篇,叫安装wiring-pi,出现这个是wiring-python的库没更新导致的,得,按照流程一顿操作,问题解决

1
2
3
4
5
6
7
8
sudo apt-get install swig2.0 python-dev git python-pip python3-pip -y
git clone --recursive https://github.com/neuralpi/WiringPi-Python.git
cd WiringPi-Python/WiringPi
sudo ./build
cd ..
swig -python wiringpi.i
sudo python setup.py install
sudo python3 setup.py install

一通操作之后,gpio终于有了响应

安装433Utils

由于我买的是个433MHz的控制器,所以直接git上面下载一个就是了,好在这个433Utils比较方便一下子就找到了

1
2
3
git clone --recursive git://github.com/ninjablocks/433Utils.git
cd 433Utils/RPi_utils
make

make之后,由于你的针脚是要自己定义的。

打开RFSniffer.cpp(接收器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
RFSniffer

Usage: ./RFSniffer [<pulseLength>]
[] = optional

Hacked from http://code.google.com/p/rc-switch/
by @justy to provide a handy RF code sniffer
*/

#include "../rc-switch/RCSwitch.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>


RCSwitch mySwitch;



int main(int argc, char *argv[]) {

// This pin is not the first pin on the RPi GPIO header!
// Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/
// for more information.
int PIN = 2;// 大概在这个位置,26行,改成你想要的针脚

if(wiringPiSetup() == -1) {
printf("wiringPiSetup failed, exiting...");
return 0;
}

然后是codesend.cpp和send.cpp如法炮制,改成一样的就行

codesend.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
Usage: ./codesend decimalcode [protocol] [pulselength]
decimalcode - As decoded by RFSniffer
protocol - According to rc-switch definitions
pulselength - pulselength in microseconds

'codesend' hacked from 'send' by @justy

- The provided rc_switch 'send' command uses the form systemCode, unitCode, command
which is not suitable for our purposes. Instead, we call
send(code, length); // where length is always 24 and code is simply the code
we find using the RF_sniffer.ino Arduino sketch.

(Use RF_Sniffer.ino to check that RF signals are being produced by the RPi's transmitter
or your remote control)
*/
#include "../rc-switch/RCSwitch.h"
#include <stdlib.h>
#include <stdio.h>


int main(int argc, char *argv[]) {

// This pin is not the first pin on the RPi GPIO header!
// Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/
// for more information.
int PIN = 0;// 大概在27行,改成你想要的针脚

// Parse the first parameter to this command as an integer
int protocol = 0; // A value of 0 will use rc-switch's default value
int pulseLength = 0;

send.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
Usage: ./send <systemCode> <unitCode> <command>
Command is 0 for OFF and 1 for ON
*/

#include "../rc-switch/RCSwitch.h"
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[]) {

/*
output PIN is hardcoded for testing purposes
see https://projects.drogon.net/raspberry-pi/wiringpi/pins/
for pin mapping of the raspberry pi GPIO connector
*/
int PIN = 0;// 大概在17行,改成你想要的针脚
const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" };

注意很坑的一点是,这个定义的针脚不是物理针脚,是Wiring Pi 针脚

这里放上针脚对应图

树莓派GPIO引脚定义

定义好你的接收器和发射器的GPIO,不一定要按照教程里面的定义,自己想怎么用怎么用,只要是GPIO就行,改好了之后重新make一下就好了

截获和发送开关信号

运行sudo ./RFSniffer 之后就可以对着接收器狂按按钮,看看输入是个啥,记下来数字对应的功能,你的输入大概是这个样子

1
2
Received 3984646
Received 3984646

然后通过sudo ./codesend 3984646的方式就能够调用发射器发送相同的信号了,用同样的方式模拟其他按钮的功能,记录下来发送即可

使用flask部署

安装flask简单快捷,比java springMVC之类的简单多了

pip3 install flask

然后我的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from flask import request, Flask, render_template
import os
import json

app = Flask(__name__)


@app.route('/', methods=['POST', 'GET'])
def helloWorld():
return render_template('index.html')



@app.route('/action', methods=['POST'])
def action():
data = request.form
data = json.loads(list(data.to_dict().keys())[0])
code = data['code']
on_cmd = '/root/433Utils/RPi_utils/codesend 1919810'
off_cmd = '/root/433Utils/RPi_utils/codesend 1145141'
if code == 0:
print('received off command')
os.system(off_cmd)
elif code == 1:
print('received on command')
os.system(on_cmd)
else:
print('unknown command')


if __name__ == '__main__':
app.run(debug=True, port=11451, host='0.0.0.0')

模板文件如下,发现由于只有1个设备,而且教程这么写传参收不到参数,于是我给魔改了一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<html>
<head>
<style type="text/css">
body {
margin: 0px;
font-size: 20px;
font-family: sans-serif;
}
table {
width: 100%;
text-align: center;
}
table tr th:first-child,table tr td:first-child{
width:120px;
}
button {
border: none;
color: white;
padding: 5px 1px;
width: 100%;
}
</style>
<script>
function sendAction(code) {
ajax = new XMLHttpRequest();
ajax.open("post", "/action", true);
ajax.setRequestHeader( "Content-Type" , "application/x-www-form-urlencoded");
ajax.send(JSON.stringify({"code": code}));
}
</script>
<title>我的智能家居</title>
</head>
<body>
<table>
<tr>
<th>设备</th><th>打开</th><th>关闭</th>
</tr>
<tr>
<td>小灯</td>
<td>
<button style="background-color:#59b968;" onclick="sendAction(1)">ON</button>
</td>
<td>
<button style="background-color:#fd6a5f;" onclick="sendAction(0)">OFF</button>
</td>
</tr>
</table>
</body>
</html>

谅解一下一个不会前端的菜鸡硬改出来html代码吧/(ㄒoㄒ)/~~

找到本地运行sudo python3 app.py,挂在后台就行了

然后本地调通,能用,但是每次都要输入192.168.1.48:11451太麻烦了,能不能转化成网址的形式

端口映射到公网并安上一个域名

frp端口映射

玩linux基本上都知道的神器,爽的一批,好在之前花了10刀开了1年的辣鸡小鸡,不过跑这种应用应该蛮够了,在有公网IP的下载frp

1
2
3
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz
tar -zxvf frp_0.33.0_linux_amd64.tar.gz
cd frp_0.33.0_linux_amd64

然后编辑一下里面的frps.ini

1
2
[common]
bind_port = 8844

开个screen或者是用tmux,运行./frps -c frps.ini挂着即可

随后在树莓派上面下载

1
2
3
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_arm.tar.gz
tar -zxvf frp_0.33.0_linux_arm.tar.gz
cd frp_0.33.0_linux_arm

编辑一下里面的frpc.ini

1
2
3
4
5
6
7
8
9
[common]
server_addr = x.x.x.x #你服务器的公网IP
server_port = 8844 #你刚才定义的端口

[tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 11451
remote_port = 8845 # 随便指定一个

然后也开个screen或者是tmux运行./frpc -c frpc.ini挂在后台

然后就可以通过公网IP:端口的方式来访问你的网站了

配置域名

这个直接nginx一把梭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server{
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /home/you/cert1.pem;
ssl_certificate_key /home/you/privkey1.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
server_name subdomain.domain.main;


location / {
proxy_pass http://127.0.0.1:8845/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

具体这个原理见上一篇博文,用nginx配https

但是部署到了公网之后有个很头痛的问题,万一别人也知道了这个网站,然后不也能控制了?

解决方法就是加一个密码验证,但是nginx的密码验证在手机端跑得并不好,非常难受

1
2
3
4
htpasswd -c /root/passwd username
New password:
Re-type new password:
Adding password for user username

然后修改nginx配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server{
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /home/you/cert1.pem;
ssl_certificate_key /home/you/privkey1.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
server_name subdomain.domain.main;


location / {
#新增下面两行
auth_basic "Please input password"; #这里是验证时的提示信息
auth_basic_user_file /root/passwd;
proxy_pass http://127.0.0.1:8845/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

然后就折腾完了,发现效果还8错