lot_manager/README.md

720 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 1. 项目简介
📃 **<font size="4">Lot_manager</font>**:基于**MQTT**(客户端-服务器的消息发布/订阅传输协议)、**FFmpeg**(多媒体视频处理工具)、**Supervisor**Linux进程控制系统的**物联网数据上传**、**视频推流**和**视频录像**应用。
⛓️ 本项目大致实现原理如图所示,包含两个模块:视频推流模块和物联网数据管理模块。
1. 视频推流模块使用MQTT的客户端连接MQ服务器**EMQX**MQ服务端通过发布消息如推流命令、停止推流命令、录像上传命令等MQTT客户端接收指令后通过supervisor启动相应的进程从而启动服务将视频推流至**ZLMediaKit**服务器。
2. 物联网数据管理模块数据上传通过MQTT客户端进行连接服务器如果连接成功则通过supervisor启动数据上传服务并发布数据到服务端。如果连接失败或者断开了连接则通过supervisor停止数据上传服务则将数据保存到本地(**sqlite**)。
![image](https://yjxiao.oss-cn-shanghai.aliyuncs.com/flow.png)
### 技术路线图
- [x] 视频推流
- [x] FFmpeg
- [x] 物联网数据管理
- [x] MQTT程序控制
- [ ] MQTT程序控制的返回结果设置统一的返回状态码
# 2. 开发环境部署
## 软件要求
#### 已经测试过的系统
* Debian GNU/Linux 12(bookworm) Linux version 5.16.17+
## 环境安装
```
# 首先,确信你的机器安装了python 3.11.2
$ python --version
Python 3.11.2
# 同步本地软件包列表与远程软件源中的最新软件包
$ sudo apt update
# 安装supervisor进程管理
$ sudo apt install supervisor
$ supervisord -v
4.2.5
# 安装FFmpeg推流管理
$ sudo apt install ffmpeg -y
$ ffmpeg -version
ffmpeg version 5.1.4-0+deb12u1 Copyright (c) 2000-2023 the FFmpeg developers
built with gcc 12 (Debian 12.2.0-14)
# 安装git
$ sudo apt install git
# 设置时区为上海
$ sudo timedatectl set-timezone Asia/Shanghai
$ sudo hwclock --systohc
$ sudo hwclock --hctosys
$ date
Thu Dec 14 15:13:45 CST 2023
# 更新py库
$ pip3 install --upgrade pip
```
## 配置wifi
```
# 使用arp协议查看以太网接口的摄像机地址需要将摄像机连接以太网
$ sudo arp -a
# 系统设置静态IP
# 使用nmcli radio wifi查看wifi开启状态
$ nmcli radio wifi
enabled
# 连接wifi
$ sudo nmcli dev wifi connect <SSID> password <PASSWORD>
```
## 配置系统静态IP
### 使用nmtui网络界面管理
`$ sudo nmtui`
* 设置的静态IP地址网段需要和摄像机在**同一个网段**
* **无需设置网关即为空。此项解决wifi上网和以太网上网冲突的问题**
* DNS设置为8.8.8.8
* 勾选始终不使用此网络于默认路由
![image](https://yjxiao.oss-cn-shanghai.aliyuncs.com/%E5%9B%BE%E7%89%871.png)
![image](https://yjxiao.oss-cn-shanghai.aliyuncs.com/%E4%BB%A5%E5%A4%AA%E7%BD%91%E8%AE%BE%E7%BD%AE.png)
## 配置git和pip
```
# 设置git的username
$ git config --global user.name xxx
# 设置git的email
$ git config --global user.email xxx
# 设置git拉取代码时用户名和密码的存储
$ git config --global credential.helper store
$ git clone https://gitea.lihaink.cn/xyj/lot_manager.git
$ cd lot_manager
# 安装项目依赖
$ pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
```
## 创建设备名称(系统唯一标识名称)
该部分创建核桃派系统的设备名称,用于标识。设备名称创建在**主目录**下。
```
# lihai_lot_walnutpi_dev_1 代表1号设备可以自行设置
$ cd ~
$ echo 'lihai_lot_walnutpi_dev_1' | tee device_name
lihai_lot_walnutpi_dev_1
```
## 创建视频录像保存目录
录像保存在主目录下的mp4目录下。
```
$ mkdir /home/pi/mp4
```
## 配置定时任务
配置一些定时任务如录像只保存2天。物联网数据只保存3天。对于32G的内存卡只要超过20G就进行删除录像直至录像小于20G。
```
$ sudo crontab -e
# 公共定时任务
# 每小时0分执行
0 * * * * /usr/bin/bash /home/pi/lot_manager/bash/cron_delete_mp4.sh
# 每天00分执行
0 0 * * * /usr/bin/python /home/pi/lot_manager/delete_lot_data_3_days.py
# 针对32G内存卡的定时额外加这个
# 每隔15分钟进行
*/15 * * * * /usr/bin/python /home/pi/lot_manager/delete_than20G.py
```
## 初始化本地数据库
本项目采用sqlite进行数据的保存数据库文件存放在主目录下。
```
$ python /home/pi/lot_manager/create_db.py
```
## 配置supervisor文件
1. 安装完supervisor后会在/etc/supervisor目录下存在supervisord.conf文件和conf.d目录。
```
$ cd /etc/supervisor/
$ ls
conf.d supervisord.conf supervisord.conf.bak
```
2. 对supervisord.conf文件进行配置
首先备份默认配置,然后进行配置。
```
$ sudo cp supervisord.conf supervisord.conf.bak
$ sudo vim supervisord.conf
```
配置信息:
```
; supervisor config file
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0777 ; sockef file mode (default 0700)
[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)
user=root
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
user=pi
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[inet_http_server]
host = 127.0.0.1
port = 9001
[program:__mqtt__]
directory=/home/pi/lot_manager
command=/usr/bin/python MQTT.py
user=pi
autostart=true
autorestart=true
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
priority=1
[include]
files = /home/pi/lot_manager/conf/common/*.conf
```
上面是已经修改好的配置文件,修改了其中几项:
- [unix_http_server]
chmod改为0777使得普通用户得以执行。
- [include]
files = /etc/supervisor/conf.d/*.conffiles是用户定义的配置文件。
- [supervisorctl]
添加user=pi使得普通用户得以执行。
- [program:\_\_mqtt\_\_]
添加mqtt配置信息
- 修改/etc/supervisor/conf.d的权限sudo chmod 777 /etc/supervisor/conf.d
1. 设置开启启动supervisor
```
$ sudo systemctl enable supervisor
```
2. 启动supervisor
```
$ sudo systemctl start supervisor
```
# 3. 项目结构
* lot_manager
* bash
* cron_delete_mp4.sh 定时删除录像脚本
* modify_device_name.sh 修改设备唯一名称脚本
* record.sh 录像脚本
* start_data_upload.sh 开启数据上传脚本
* start_push_stream.sh 启动推流脚本
* stop_data_upload.sh 停止数据上传脚本
* stop_push_stream.sh 停止推流脚本
* stream.sh 推流命令脚本
* conf
* common
* data_upload.conf 数据上传配置
* mqtt.conf mqtt配置
* push_stream.conf 推流配置
* record.conf 录像配置
* sensor_to_local.conf 数据保存在本地
* sensor_to_server.conf 数据发布到mq服务器
* device
* device.conf 不同设备的配置
* example
* cron_for_32G.conf 对于32G卡的定时任务配置
* cron_for_64G.conf 对于64G卡的定时任务配置
* supervisord.conf 配置supervisor文件
* main
* common.conf 公共配置
* tmp
* device_name 需要修改的设备名称
* zhanguan
* topic.conf 展馆的特殊设备
* db/ 本地数据库管理
* MQTT.py
* data_upload.py 数据上传程序
* api.py 本地数据库api
* config.py 读取各种配置文件
* create_db.py 创建数据库程序
* delete_lot_3_days.py 删除数据库中早于3天的数据
* delete_than20G.py 当录像超过20G直接删除直到小于20G
* sensor_to_local.py 数据保存在本地
* sensor_to_server.py 数据发布到服务器
* tool.py 工具
* git_push.sh 推送git脚本
* git_update.sh 更新git脚本
* install.sh 安装依赖脚本
## MQTT程序控制 -MQTT.py
![image](https://yjxiao.oss-cn-shanghai.aliyuncs.com/mqtt.png)
## 物联网数据上传 -data_upload.py
![image](https://yjxiao.oss-cn-shanghai.aliyuncs.com/data_upload.png)
## sensor_to_server.py
![image](https://yjxiao.oss-cn-shanghai.aliyuncs.com/sensor_to_server.png)
## 项目的配置文件 -config.py
通过**configparser**读取.conf文件。
项目的配置文件均在conf目录下
* conf
* common 表示supervisor管理的进程配置文件
* device 设置不同device的配置包括订阅主题、发布主题、用户名和密码等
* main 公共配置,包括域名设置、录像上传地址等
```
import configparser
import subprocess
p = subprocess.Popen(['cat /home/pi/device_name'], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
# 设备名称,必须要有
device_name = out.decode('utf-8').strip()
# 读取配置
config = configparser.ConfigParser()
# 读取公共配置
config.read('conf/main/common.conf')
# 域名
broker = config.get("broker", "host")
# 端口这里必须是int类型
port = config.getint("broker", "port")
# 录像地址
post_record_list_url = config.get("record", "post_record_list_url")
post_record_url = config.get("record", "post_record_url")
# 发布消息的认证pwd
publish_pwd = config.get("publish_pwd", "value")
# 读取设备配置
config.read('conf/device/device.conf')
# 订阅的主题
subscribe_topic = config.get(device_name, "subscribe_topic")
# 发布的主题
publish_topic = config.get(device_name, "publish_topic")
# 用户
username = config.get(device_name, "username")
# 密码
password = config.get(device_name, "password")
# 特殊配置
config.read('conf/zhanguan/topic.conf')
zhanguan_device_name = config.get("device", "name")
# tool配置
mp4_path = '/home/pi/mp4'
```
# 4. Supervisor服务参数配置
配置文件都在conf文件夹下。主要是common、main和device文件夹。
* conf
* common 表示supervisor管理的进程配置文件
* device 设置不同device的配置包括订阅主题、发布主题、用户名和密码等
* main 公共配置,包括域名设置、录像上传地址等
### supervisor服务配置
- directory表示执行命令时首先进入这个文件夹
- command表示要执行的命令
- user必须是pi
- autostart表示随开机或supervisor重启时跟着启动
- autorestart表示启动失败自动重启3次
```
[program:__mqtt__]
directory=/home/pi/lot_manager
command=/usr/bin/python MQTT.py
user=pi
autostart=true
autorestart=true
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
priority=1
```
### 数据上传的supervisor配置文件
```
[program:data_upload]
directory=/home/pi/lot_manager
command=/usr/bin/python data_upload.py
user=pi
;是否随开机自启 或者reload自启动
autostart=true
;失败重启
autorestart=true
;重启次数
restart_times=3
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
```
### 推流的supervisor配置文件
注意不要跟随开机或supervisor重启启动
```
[program:push_stream]
directory=/home/pi/lot_manager/bash
command=/usr/bin/bash stream.sh
user=pi
autostart=false
autorestart=true
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
```
### 录像的supervisor配置文件
```
[program:record]
directory=/home/pi/lot_manager/bash
command=/usr/bin/bash record.sh
user=pi
;是否随开机自启 或者reload自启动
autostart=true
;失败重启
autorestart=true
;重启次数
restart_times=3
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
```
### 数据保存在本地的supervisor配置文件
```
[program:sensor_to_local]
directory=/home/pi/lot_manager
command=/usr/bin/python sensor_to_local.py
user=pi
;是否随开机自启 或者reload自启动
autostart=false
;失败重启
autorestart=true
;重启次数
restart_times=3
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
```
### 数据保存在mq服务器的supervisor配置文件
```
[program:sensor_to_server]
directory=/home/pi/lot_manager
command=/usr/bin/python sensor_to_server.py
user=pi
;是否随开机自启 或者reload自启动
autostart=false
;失败重启
autorestart=true
;重启次数
restart_times=3
redirect_stderr=true
stopsignal=TERM
stopasgroup=True
```
## 不同设备的配置
1. 订阅的主题表示MQTT控制时发布的主题比如对设备lihai_lot_walnutpi_dev_1进行推流。
2. 发布消息的主题,表示**物联网数据上传**时发布消息的主题。
3. username和password是MQTT连接服务器的用户名和密码。
```
# 设备1-32G
# 编号
[lihai_lot_walnutpi_dev_1]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_1
# 发布消息的主题
publish_topic=camera_1
username=lihai_lot_land_1
password=123456
####################################
# 设备2-32G
[lihai_lot_walnutpi_dev_2]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_2
# 发布消息的主题
publish_topic=camera_2
username=lihai_lot_land_1
password=123456
####################################
# 设备3-64G
[lihai_lot_walnutpi_dev_3]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_3
# 发布消息的主题
publish_topic=camera_3
username=lihai_lot_land_1
password=123456
####################################
# 设备4-64G
[lihai_lot_walnutpi_dev_4]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_4
# 发布消息的主题
publish_topic=camera_4
username=lihai_lot_land_1
password=123456
####################################
# 设备5-64G
[lihai_lot_walnutpi_dev_5]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_5
# 发布消息的主题
publish_topic=camera_5
username=lihai_lot_land_1
password=123456
####################################
# 设备6-64G
[lihai_lot_walnutpi_dev_6]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_6
# 发布消息的主题
publish_topic=camera_6
username=lihai_lot_land_1
password=123456
```
### 公共配置
1. host和port表示MQTT的服务器地址。
2. post_record_list_url是上传录像的目录的地址。
3. post_record_url是上传录像的地址。
```
# 域名地址
[broker]
host=mqtt.lihaink.cn
port=1883
[record]
post_record_list_url=https://ceshi-iot.lihaink.cn/api/index/file_list
post_record_url=https://ceshi-iot.lihaink.cn/api/index/upload
[publish_pwd]
value=123456
```
# 5. ffmpeg推流设置
推流的脚本为stream.sh内容如下
```
#!/bin/bash
device_name=`cat /home/pi/device_name`
/usr/bin/ffmpeg -rtsp_transport tcp -re -i rtsp://admin:123456@192.168.0.123:554/mpeg4 -c copy -preset fast -r 20 -flvflags no_duration_filesize -f rtsp -rtsp_transport tcp rtsp://47.108.186.87:554/live/$device_name
```
对于ffmpeg的参数设置如下
* \-rtsp\_transport\_tcp tcp 表示以tcp进行获取流保证数据完整性
* \-re 表示推流设置
* \-i 表示输入流,这里是摄像机地址
* \-c 表示直接复制源流对象,不进行编码
* \-preset fast表示快速传输
* \-r 20表示以帧率20传输
* flvflags no\_duration\_filesize表示其他一些错误排除
* \-f rtsp -rtsp\_transport tcp 表示输出的文件格式输出为rtsp流也是tcp传输
* rtsp\://47.108.186.87:554/live/\$device\_name表示推流地址
# 6.录像保存设置
录像的脚本为record.sh内容如下
```
#!/bin/bash
RTSP_URL="rtsp://admin:123456@192.168.0.123:554/mpeg4" # RTSP流的URL
OUTPUT_DIR="/home/pi/mp4" # 录像文件保存的目录
RECORD_DURATION=900 # 录像时长(秒)
# 获取当前时间
CURRENT_TIME=$(date +"%Y-%m-%d_%H-%M-%S")
# 构建录像文件名
OUTPUT_FILE="${OUTPUT_DIR}/${CURRENT_TIME}.mp4"
# 开始录制RTSP流
/usr/bin/ffmpeg -rtsp_transport tcp -i "${RTSP_URL}" -c copy -c:a aac -s 1920x1080 -r 15 -crf 35 -t "${RECORD_DURATION}" -f mp4 "${OUTPUT_FILE}"
```
参数设置如下:
* \-rtsp\_transport\_tcp tcp 表示以tcp进行获取流保证数据完整性
* \-i 表示输入流,这里是摄像机地址
* \-c 表示直接复制源流对象,不进行编码
* \-c\:a aac表示音频编码格式为aac
* \-s 1920x1080是分辨率裁剪
* \-r 15是帧率改为15
* \-crf 35是改变恒定传输速率值越大压缩越好但质量越差默认值是28
* \-t 表示900秒一次保存一次
* \-f mp4是保存为mp4文件
# 7. MQTT程序控制配置发布消息和订阅消息
### 设备的编号配置信息
设备的配置为:
```
[lihai_lot_walnutpi_dev_1]
# 订阅的控制主题,必须和系统设置的相同
subscribe_topic=lihai_lot_walnutpi_dev_1
# 发布消息的主题
publish_topic=camera_1
username=lihai_lot_land_1
password=123456
```
其中,
* subscribe\_topic 表示订阅的主题,只有订阅后才能进行控制
* publish\_topic 表示数据发布的主题
* username 表示客户端进行连接的用户名
* password 表示客户端进行连接的密码
### 控制设备(推流、数据上传、录像等)
1. 发布的主题
```
lihai_lot_walnutpi_dev_1
```
1. 发送的消息体payload
example:
```
{
"msg":"exec",
"data":"sudo ifconfig",
"pwd":"123456"
}
```
### msg类型
msg表示消息类型包括
- push\_stream 设备启动推流
```
发布主题lihai_lot_walnutpi_dev_1
{
"msg":"exec",
"pwd":"123456"
}
```
* close\_stream 设备关闭推流
```
发布主题lihai_lot_walnutpi_dev_1
{
"msg":"close_stream",
"pwd":"123456"
}
```
* record\_list 录像文件列表
```
发布主题lihai_lot_walnutpi_dev_1
{
"msg":"record_list",
"pwd":"123456"
}
```
* record 上传选定文件
```
发布主题lihai_lot_walnutpi_dev_1
{
"msg":"record_list",
"data":"2023-12-13_11-11-11.mp4"
"pwd":"123456"
}
```
* status 查看supervisor运行状态
```
发布主题lihai_lot_walnutpi_dev_1
订阅success
{
"msg":"status",
"pwd":"123456"
}
```
* reload 重启supervisor
```
发布主题lihai_lot_walnutpi_dev_1
订阅success,error
{
"msg":"reload",
"pwd":"123456"
}
```
* update 更新git
```
发布主题lihai_lot_walnutpi_dev_1
订阅success,error
{
"msg":"update",
"pwd":"123456"
}
```
* exec 执行linux命令
```
发布主题lihai_lot_walnutpi_dev_1
订阅success,error
{
"msg":"exec",
"data":"supervisorctl tail record"
"pwd":"123456"
}
```
### data
* data在执行linux命令和获取录像时必须设置。
* 在执行命令时data则是需要执行的命令获取录像时需要指定需要的录像名称
* 订阅success和error主题可以查看错误消息和命令执行结果
# 8. 本地数据库字段设置
```
class LOT_DATA(BaseModel):
create_time: int = Field(None, description='创建时间(时间戳) ')
wind_speed: float = Field(None, description='风速:(0到30)m/s ')
wind_direction: float = Field(None, description='风向:0360°')
ambient_temperature: float = Field(None, description='环境温度:℃')
ambient_humidity: float = Field(None, description='环境湿度:%RH')
carbon_dioxide: float = Field(None, description='二氧化碳0~5000ppm')
ambient_air_pressure: float = Field(None, description='环境气压0~120KPa')
rainfall: float = Field(None, description='雨量mm')
ambient_lighting: float = Field(None, description='环境光照0-65535Lux0-20万Lux')
soil_temperature: float = Field(None, description='土壤温度:-40~80℃')
soil_moisture: float = Field(None, description='土壤湿度0-100%RH')
soil_conductivity: float = Field(None, description='土壤电导率0-20000μS/cm')
soil_PH: float = Field(None, description='土壤PH:3~9PH')
soil_potassium_phosphate_nitrogen: float = Field(None, description='土壤磷酸钾氮的标准值在140-225mg/kg')
soil_potassium_phosphate_phosphorus: float = Field(None, description='土壤磷酸钾磷的标准值在57-100mg/kg,')
soil_potassium_phosphate_potassium: float = Field(None, description='土壤磷酸钾钾的标准值在106-150mg/kg')
```
### 本地数据库存放位置
```
# 存放的路径
KB_ROOT_PATH = "/home/pi/"
# 数据库名称
DB_ROOT_PATH = os.path.join(KB_ROOT_PATH, "lot_data.db")
# 连接URI
SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_ROOT_PATH}"
```
### 数据库操作api
本项目采用**sqlalchemy**对sqlite进行数据的增删改查。api服务为项目的api.py文件。
api包括的功能
* add 添加数据到数据库
* delete 删除3天之前的数据