# 1. 项目简介 # 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 $ 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 password ``` ## 配置系统静态IP ### 使用nmtui网络界面管理 `$ sudo nmtui` - 设置的静态IP地址网段需要和摄像机在同一个网段 - 无需设置网关,即为空。此项解决wifi上网和以太网上网冲突的问题。 - DNS设置为8.8.8.8 - 勾选始终不使用此网络于默认路由 ## 配置git和pip ``` # 设置git的usrenmae $ 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 ``` ## 配置定时任务 ``` $ sudo crontab -e # 公共定时任务 # 每小时0分执行 0 * * * * /usr/bin/bash /home/pi/lot_manager/bash/cron_delete_mp4.sh # 每天0:0分执行 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 ``` ## 初始化本地数据库 ``` $ 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 ``` 3. 配置信息: ``` ; 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/*.conf,files是用户定义的配置文件。 - [supervisorctl] 添加user=pi,使得普通用户得以执行。 - [program:\_\_mqtt\_\_] 添加mqtt配置信息 - 修改/etc/supervisor/conf.d的权限,sudo chmod 777 /etc/supervisor/conf.d 4. 设置开启启动supervisor ``` $ sudo systemctl enable supervisor ``` 5. 启动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 安装依赖脚本 # 4.supervisor参数配置 配置文件都在conf文件夹下。主要是common、main和device文件夹。 - conf - common 表示supervisor管理的进程配置文件 - device 设置不同device的配置,包括订阅主题、发布主题、用户名和密码等 - main 公共配置,包括域名设置、录像上传地址等 ### MQTT的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-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 ``` ### 公共配置 ``` # 域名地址 [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文件 # 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 ``` 2. 发送的消息体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主题,可以查看错误消息和命令执行结果 # 本地数据库字段设置 ``` 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='风向:0~360°') 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-65535Lux;0-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}" ``` # 技术路线图 - [x] 数据上传 - [x] 摄像机推流 - [ ] MQTT程序控制的返回结果,设置统一的返回状态码