0%

树莓派远程监控

github:https://github.com/maplesugarr/raspberrypi-camera

motion使用

树莓派csi夜视摄像头

开始

执行raspi-config打开csi摄像头

执行raspistill -o test.jpg拍张照片测是摄像头是否可用

摄像头参数

帧速 30FPS
最高分辨率 2592*1944

motion安装配置

1.apt-get -y install motion #安装motion

2.motion中默认配置的设备是videodevice /dev/video0

执行motion命令后,会提示motion找不到divice

motion文档中 https://motion-project.github.io/motion_config.html 的方法

1
2
3
nano /etc/modules
bcm2835-v4l2
reboot

3.nano /etc/default/motion

1
start_motion_daemon=yes

4.nano /etc/motion/motion.conf

配置好的motion具有可以从网络查看录像,自动保存有运动物体的照片到文件夹,等功能

1
2
3
4
5
6
daemon on
stream_port 8081 #视频流端口
stream_localhost off #本地视频流禁止
webcontrol_port 0 #关闭web control
webcontrol_localhost on
webcontrol_html_output on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
width 2592 #最高分辨率图像
height 1944 #实测证明,motion对不同的分辨率会裁剪一部分,模糊一点。
framerate 30 #为了省硬盘可以设的很小
threshold 50000 #发生变化的像素数,如果有超过这个像素数的图像发生了变化,系统就会开始录像,自然这个值要根据你的摄像头的分辨率而定,自然这个值越小越灵敏,建议设置到1%以上,否则容易有很多虚警。
lightswitch 15 #忽略光线变化范围的比率,motion会忽略在这个面积范围以内的光线变化
minimum_motion_frames 1 #忽略运动的帧数,让motion只有在连续几帧以上发现运动的时候才会开始录像
pre_capture 2 #记录下开始运动前的两帧图像
ffmpeg_output_movies off #不保存视频
locate_motion_mode on #给运动物体用方框标出
target_dir /data/motion_save #图片保存的路径
stream_maxrate 30 #这个设置大些,网络监控会比较流畅
stream_motion off #没有检测到运动时,帧率是否保持为1fps
picture_filename %Y%m%d/%v-%Y%m%d%H%M%S-%q #按天分文件夹存储
stream_quality 20 #默认50,网络摄像头传输质量,由于分辨率很大,设置小一些比较流畅
1
2
3
4
5
# 0 = disabled
# 1 = Basic authentication
# 2 = MD5 digest (the safer authentication
stream_auth_method 0 #开启密码认证,实践证明,2的话,有时候明明密码是对的,却让你重新输入。
stream_authentication #网页查看摄像头的用户名和密码

5.启动motion

1
2
ps -ef|grep motion|grep -v grep|awk '{print $2}'|xargs kill -9 #停止motion
motion #启动motion

flask web

下来看看页面

1.使用两个舵机组成云台控制摄像头。

RPi.GPIO库也能控制舵机,不过会有抖动,这是software pwm。需要hardware pwm。RPIO库利用 DMA 为 Raspberry Pi 实现 PWM,不再抖动。

​ RPIO.PWM provides PWM via DMA for the Raspberry Pi, using the onboard PWM module for semi-hardware pulse width modulation with a precision of up to 1µs.
​ With RPIO.PWM you can use any of the 15 DMA channels and any number of GPIOs per channel. Since the PWM is done via DMA, RPIO.PWM uses almost zero CPU resources and can generate stable pulses with a very high resolution. RPIO.PWM is implemented in C (source); you can use it in Python via the provided wrapper, as well as directly from your C source.

按照作者说明安装,回报错

1
2
3
4
git clone https://github.com/metachris/RPIO.git
cd RPIO
sudo python setup.py install
RPIO 0.10.1报错SystemError: This module can only be run on a Raspberry Pi!

需要安装https://github.com/metachris/RPIO/tree/v2

1
2
3
git clone https://github.com/metachris/RPIO/tree/v2
cd RPIO-2/
python3 setup.py install

2.安装相关库

1
2
3
apt-get -y install python3-flask
pip3 install flask_wtf
pip3 install flask_login

3.为了节省资源,没有安装数据库,把用户名和密码的hash信息放到了文件里。

4.uwsgi部署flask

pip3 install uwsgi #安装

uwsgi –socket 0.0.0.0:5000 –protocol=http -w run_manager:app #这样就能启动了

配置uwsgi服务

nano /etc/systemd/system/uwsgi.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=uwsgi instance to serve manager
After=network.target

[Service]
User=root
Group=root
RestartSec=30
Restart=on-failure
WorkingDirectory=/raspi2/webs/manager
ExecStart=/usr/local/bin/uwsgi --ini uwsgiconfig.ini --protocol=http
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target

systemctl daemon-reload
systemctl disable uwsgi
systemctl enable uwsgi
systemctl restart uwsgi
systemctl status uwsgi

uwsgiconfig.ini

1
2
3
4
5
6
7
8
9
10
11
12
13
[uwsgi]
# uwsgi 启动时所使用的地址与端口
socket = 0.0.0.0:5000
# 指向网站目录
chdir = /raspi2/webs/
# python 启动程序文件
wsgi-file = run_manager.py
# python 程序内用以启动的 application 变量名
callable = app

master=true # 启动主线程
processes=2 # 设置工作进程的数量
threads=2 # 设置每个工作进程的线程数

5.uwsgi线程阻止关机

谷歌了一下,没有找到解决办法,uwsgi的文档也不是很友好。只能hack的方法解决了,关机前把uwsgi结束任务。

touch /usr/local/bin/cleanup.sh
chmod a+x /usr/local/bin/cleanup.sh
nano /usr/local/bin/cleanup.sh

1
ps -ef|grep uwsgi|grep -v grep|awk '{print $2}'|xargs kill -9

nano /lib/systemd/system/cleanup.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Run command at shutdown
Requires=network.target
DefaultDependencies=no
Conflicts=reboot.target
Before=shutdown.target

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/bin/true
ExecStop=/bin/bash /usr/local/bin/cleanup.sh

[Install]
WantedBy=multi-user.target

systemctl disable cleanup
systemctl enable cleanup
systemctl restart cleanup
systemctl status cleanup

6.motion的监控页面通过iframe直接嵌入到自己的web界面中,不能改变大小,有cross domain的问题,所以使用flask代理motion监控页面,同时还省去了motion的密码配置,使用flask统一认证。

1
2
3
4
5
6
@app.route('/motion')
@login_required
def motion():
req = requests.get("http://127.0.0.1:8081", params=request.args, stream=True)
#chunk_size=1024,性能最好
return Response(stream_with_context(req.iter_content(chunk_size=1024)), content_type=req.headers['content-type'])

7.nginx代理flask web

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen xxx;
server_name flaskweb.site;

location ^~ / {
proxy_pass http://127.0.0.1:5000/;
#以下三个命令,目的是将代理服务器收到的用户的信息传到真实服务器上
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

frp内网穿透

服务器frps

1.安装frps

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

2.配置frps

nano frps.ini

1
2
3
4
[common]
bind_addr = 0.0.0.0
bind_port = xxx #frpc连接的端口
token = xxx #验证frpc

3.打开相应端口

1
2
3
4
5
ufw allow xxx #frps的bind_port
ufw allow xxx #代理frpc的端口
ufw allow xxx #代理frpc的端口
ufw allow xxx #代理frpc的端口
ufw reload

4.配置服务

nano /etc/systemd/system/frps.service

1
2
3
4
5
6
7
8
9
[Unit]
Description=frps daemon

[Service]
ExecStart=/apps/frp_0.21.0_linux_amd64/frps -c /apps/frp_0.21.0_linux_amd64/frps.ini
Restart=always

[Install]
WantedBy=multi-user.target

systemctl disable frps
systemctl enable frps
systemctl restart frps
systemctl status frps

树莓派frpc

1.安装frpc

1
2
wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_linux_arm.tar.gz
tar -zxvf frp_0.21.0_linux_arm.tar.gz -C /apps

2.配置frpc

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
[common]
server_addr = xxx.com
server_port = xxx #bind_port
token = xxx
#表示自动重试
login_fail_exit = false

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = xxx #本地ssh端口
remote_port = xxx #服务器代理树莓派ssh的端口
# 是否开启加密(流量加密,应对防火墙)
use_encryption = true
# 是否开启压缩
use_compression = true

[flask_web]
type = tcp
local_port = xxx #本地flask_web端口
remote_port = xxx #服务器代理树莓派flask_web的端口
# 是否开启加密(流量加密,应对防火墙)
use_encryption = true
# 是否开启压缩
use_compression = true

3.配置服务

nano /etc/systemd/system/frpc.service

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=frpc daemon
After=network.target

[Service]
ExecStart=/apps/frp_0.21.0_linux_arm/frpc -c /apps/frp_0.21.0_linux_arm/frpc.ini
Restart=always

[Install]
WantedBy=multi-user.target

systemctl disable frpc
systemctl enable frpc
systemctl restart frpc
systemctl status frpc

服务器配置域名访问

配置好frp就可以外网访问了,只是通过服务器ip:端口这种形式访问,需要配置域名访问。

服务器nginx配置

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
server {
listen 80;
server_name manager.xxx.com;
#所有请求转到https的443端口处理
#301 代表永久性转移
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name manager.xxx.com;

location ^~ / {
#由于我使用的是nginx的docker,172.18.0.1是网桥,访问它相当于访问主机
proxy_pass http://172.18.0.1:21024/;

#以下三个命令,目的是将代理服务器收到的用户的信息传到真实服务器上
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location ^~ /.well-known/acme-challenge/ {
alias /usr/share/nginx/html;
}

#letsencrypt生成的文件
ssl_certificate /etc/nginx/httpscert/www.xxx.com/fullchain.pem;
ssl_certificate_key /etc/nginx/httpscert/www.xxx.com/privkey.pem;

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets on;

ssl_dhparam /etc/nginx/httpscert/www.xxx.com/dhparam.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# 一般推荐使用的ssl_ciphers值: https://wiki.mozilla.org/Security/Server_Side_TLS
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK';
ssl_prefer_server_ciphers on;

}#server end

跳转带不带端口号问题

这样访问域名就可以访问到树莓派上的flask web了,跳转的时候也没有问题。但是内网访问,内网ip:端口,跳转的时候就不带有端口了,这样就出问题了。

解决方法:树莓派上的nginx配置,需要带上端口号

1
proxy_set_header Host $host:$server_port;

这样内网跳转是带上端口号了,但通过域名外网访问也会带上端口号,又出错了。

所以,nginx需要判断是否是从外网访问的。

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
server {
listen xxx;
server_name rasp2-manager.site;

location ^~ / {
proxy_pass http://127.0.0.1:5000/;

#以下三个命令,目的是将代理服务器收到的用户的信息传到真实服务器上
#需要设置端口,这样flask中的redirect(url_for('login'))才会带上端口
#~* 为不区分大小写匹配
#如果是从外网子域名访问的就不带端口
#nginx语法检测特别严格,if和后面括号以及变量等号这些元素都要有空格
#if ($host ~* "^[A-Za-z0-9_-]+\.xxx\.com$") {
# proxy_set_header Host $host; #Unlike proxy_pass, you cannot put proxy_set_header inside an if block. You can only put it in http/server/location block.
#}
#如果从外网访问就不带端口号,从内网访问就带端口号。
set $esb $host:$server_port;
if ($host ~* "^[A-Za-z0-9_-]+\.xxx\.com$") {
set $esb $host;
}
proxy_set_header Host $esb;

proxy_set_header X-Real-IP $remote_addr;
#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

这样问题就完美解决了。