478 lines
14 KiB
C++
478 lines
14 KiB
C++
/*
|
||
本程序用于设备验证
|
||
验证通过执行以下功能:
|
||
1.启动RTSP推流
|
||
2.启动系统各项服务,用于和双目交互
|
||
3.启动mqtt,与App进行数据交互
|
||
*/
|
||
|
||
#include <iostream>
|
||
#include <cstdlib>
|
||
#include <string>
|
||
#include <thread>
|
||
|
||
#include <mqtt/async_client.h>
|
||
|
||
#include <boost/process.hpp>
|
||
|
||
#include <nlohmann/json.hpp>
|
||
|
||
#include "Netra.hpp"
|
||
#include "NetRequest.hpp"
|
||
#include "encrypt.hpp"
|
||
|
||
using namespace std;
|
||
using namespace QCL;
|
||
using namespace ntq;
|
||
using namespace encrypt;
|
||
using namespace chrono_literals;
|
||
namespace bp = boost::process;
|
||
|
||
bp::child rtsp_proc; // 服务器进程
|
||
bp::child video_proc; // 视频推流进程
|
||
bp::child net_proc; // 开启网络进程
|
||
|
||
// 文件设置路径
|
||
string filepath = "../../InitAuth/conf/.env";
|
||
string cameraPath = "/opt/rknn-yolov11/.env";
|
||
// string passwd = "/home/orangepi/InitAuth/pwd/.env";
|
||
|
||
// 云端Web认证接口
|
||
const string url = "http://116.147.36.110:8095/device/validateDevice";
|
||
const string mqtt_url = "tcp://192.168.12.1:1883";
|
||
const string clientId = "RK3588_SubTest";
|
||
const string Topic = "/bsd_camera/cmd";
|
||
const int Qos = 1;
|
||
|
||
std::atomic<bool> isRunning(true); // 全局运行标志
|
||
std::atomic<bool> confirm(true); // 发送卡和ID
|
||
|
||
// 媒体对象
|
||
struct Media
|
||
{
|
||
bool mirror; // 镜像
|
||
bool flip; // 翻转
|
||
bool occlusion; // 遮挡
|
||
} media{};
|
||
|
||
// 输入输出对象
|
||
struct OutSignal
|
||
{
|
||
bool outPutMode; // 警报输出电平 true--高电平,false -- 低电平
|
||
bool inPutMode; // 触发输入模式 true--高电平,false -- 低电平
|
||
} algor{};
|
||
|
||
// 获取SIM卡号
|
||
string GetSimICCID(const string &tty = "/dev/ttyUSB2");
|
||
|
||
// 开始进行设备验证
|
||
bool verification();
|
||
|
||
// 解析接收的数据
|
||
template <class T>
|
||
void CalculateInfo(T &conf, const string &json);
|
||
|
||
// 创建服务器
|
||
// TcpServer *MyServer;
|
||
// mqtt client
|
||
mqtt::async_client client(mqtt_url, clientId);
|
||
|
||
// 打开RSTP服务器
|
||
void OpenRTSP();
|
||
|
||
// 视频推流
|
||
void VideoStream();
|
||
|
||
// 退出信号捕捉
|
||
void Exit(int sig);
|
||
|
||
// 开启网络(4G模块)
|
||
void StartNet();
|
||
|
||
// 开启服务
|
||
void StartService();
|
||
|
||
// mqtt初始化
|
||
void mqttInit();
|
||
|
||
// 接收消息回调
|
||
void getMsgCallback(mqtt::const_message_ptr msg);
|
||
|
||
int main(int argc, char *argv[])
|
||
{
|
||
// 开启4G模块
|
||
// StartNet();
|
||
// this_thread::sleep_for(chrono::seconds(2));
|
||
|
||
// 关闭全部信号
|
||
blockAllSignals();
|
||
signal(SIGINT, Exit); // 捕获Ctrl+C信号
|
||
|
||
// 开启验证
|
||
if (verification() == false)
|
||
{
|
||
// 验证失败,退出程序
|
||
return 0;
|
||
}
|
||
|
||
// 验证成功,启动程序
|
||
|
||
// 初始化mqtt
|
||
mqttInit();
|
||
|
||
// 开启系统服务
|
||
thread(StartService).detach();
|
||
|
||
// 开启推流服务器
|
||
thread Rtsp(OpenRTSP);
|
||
std::this_thread::sleep_for(chrono::seconds(2)); // 等待RTSP服务器启动
|
||
|
||
// 进行RTSP视频推流
|
||
thread video(VideoStream);
|
||
|
||
while (isRunning)
|
||
{
|
||
this_thread::sleep_for(1s); // 防止mpu沾满
|
||
}
|
||
|
||
// Processing.join();
|
||
video.join();
|
||
Rtsp.join();
|
||
|
||
return 0;
|
||
}
|
||
|
||
// 初始化MQTT
|
||
void mqttInit()
|
||
{
|
||
|
||
client.set_connected_handler([](const string &cause)
|
||
{ cout << "Connected Successed!\n"; });
|
||
client.set_message_callback(getMsgCallback);
|
||
|
||
// 连接服务器
|
||
client.connect()->wait();
|
||
client.subscribe(Topic, Qos)->wait();
|
||
}
|
||
|
||
// 接收消息回调
|
||
void getMsgCallback(mqtt::const_message_ptr msg)
|
||
{
|
||
// 立即拷贝负载,避免在回调里做大量工作
|
||
std::string payload = msg->to_string();
|
||
|
||
cout << "Recv:" << payload << endl;
|
||
|
||
std::thread([payload]()
|
||
{
|
||
try
|
||
{
|
||
string buffer = payload;
|
||
if (buffer.empty())
|
||
return;
|
||
// SET_DISTANCES: 解析 JSON 并更新 cameraPath 文件
|
||
if (buffer.find("SET_DISTANCES") != string::npos)
|
||
{ //设置报警距离
|
||
auto res = nlohmann::json::parse(buffer);
|
||
auto &danger_json = res["params"]["danger_distance"];
|
||
auto &warning_json = res["params"]["warning_distance"];
|
||
auto &safe_json = res["params"]["safe_distance"];
|
||
|
||
auto toDouble = [](const nlohmann::json &jv) -> double {
|
||
if (jv.is_number()) return jv.get<double>();
|
||
if (jv.is_string()) {
|
||
try { return std::stod(jv.get<string>()); }
|
||
catch (...) { return 0.0; }
|
||
}
|
||
return 0.0;
|
||
};
|
||
|
||
double danger = toDouble(danger_json);
|
||
double warn = toDouble(warning_json);
|
||
double safe = toDouble(safe_json);
|
||
|
||
ReadFile rf(cameraPath);
|
||
if (!rf.Open()) {
|
||
cerr << "文件打开失败: " << cameraPath << "\n";
|
||
} else {
|
||
auto lines = rf.ReadLines();
|
||
for (auto &line : lines) {
|
||
if (line.rfind("NEAR_THRESHOLD=", 0) == 0) line = format("NEAR_THRESHOLD={}", danger);
|
||
else if (line.rfind("MID_THRESHOLD=", 0) == 0) line = format("MID_THRESHOLD={}", warn);
|
||
else if (line.rfind("MAX_DISTANCE=", 0) == 0) line = format("MAX_DISTANCE={}", safe);
|
||
}
|
||
string out;
|
||
out.reserve(4096);
|
||
for (size_t i = 0; i < lines.size(); ++i) {
|
||
out += lines[i];
|
||
if (i + 1 < lines.size()) out += "\n";
|
||
}
|
||
rf.Close();
|
||
WriteFile wf(cameraPath);
|
||
wf.overwriteText(out);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// media: 更新 cameraPath 的 MEDIA_* 设置
|
||
if (buffer.find("media") != string::npos)
|
||
{//设置摄像头参数
|
||
CalculateInfo(media, buffer);
|
||
ReadFile rf(cameraPath);
|
||
if (!rf.Open()) {
|
||
cerr << "文件打开失败: " << cameraPath << "\n";
|
||
} else {
|
||
auto lines = rf.ReadLines();
|
||
for (auto &line : lines) {
|
||
if (line.rfind("MEDIA_MIRROR=", 0) == 0) line = format("MEDIA_MIRROR={}", media.mirror ? "true" : "false");
|
||
else if (line.rfind("MEDIA_FLIP=", 0) == 0) line = format("MEDIA_FLIP={}", media.flip ? "true" : "false");
|
||
else if (line.rfind("MEDIA_OCCLUSION=", 0) == 0) line = format("MEDIA_OCCLUSION={}", media.occlusion ? "true" : "false");
|
||
}
|
||
string out;
|
||
out.reserve(4096);
|
||
for (size_t i = 0; i < lines.size(); ++i) {
|
||
out += lines[i];
|
||
if (i + 1 < lines.size()) out += "\n";
|
||
}
|
||
rf.Close();
|
||
WriteFile wf(cameraPath);
|
||
wf.overwriteText(out);
|
||
}
|
||
return;
|
||
}
|
||
// algorithm: 更新 filepath 中的 outPutMode/inPutMode
|
||
if (buffer.find("algorithm") != string::npos)
|
||
{//设置报警输出模式
|
||
CalculateInfo(algor, buffer);
|
||
ReadFile rf2(filepath);
|
||
if (!rf2.Open()) {
|
||
cerr << "文件打开失败: " << filepath << "\n";
|
||
} else {
|
||
auto lines = rf2.ReadLines();
|
||
for (auto &line : lines) {
|
||
if (line.rfind("outPutMode:", 0) == 0) line = format("outPutMode:{}", algor.outPutMode ? "true" : "false");
|
||
else if (line.rfind("inPutMode:", 0) == 0) line = format("inPutMode:{}", algor.inPutMode ? "true" : "false");
|
||
}
|
||
string out;
|
||
out.reserve(4096);
|
||
for (size_t i = 0; i < lines.size(); ++i) {
|
||
out += lines[i];
|
||
if (i + 1 < lines.size()) out += "\n";
|
||
}
|
||
rf2.Close();
|
||
WriteFile wf2(filepath);
|
||
wf2.overwriteText(out);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
catch (const std::exception &e)
|
||
{
|
||
std::cerr << "处理 MQTT 消息异常: " << e.what() << std::endl;
|
||
}
|
||
catch (...)
|
||
{
|
||
std::cerr << "处理 MQTT 消息时发生未知错误\n";
|
||
} })
|
||
.detach();
|
||
}
|
||
|
||
// 开启服务:fastApi,pub
|
||
void StartService()
|
||
{
|
||
string commnd = "echo 'orangepi' | sudo -S ../../StartService/bin/start start";
|
||
system(commnd.c_str());
|
||
}
|
||
|
||
// 开启网络
|
||
void StartNet()
|
||
{
|
||
string commd = "../../GetNet/bin/setNet";
|
||
net_proc = bp::child("/bin/bash", bp::args = {"-c", commd});
|
||
}
|
||
|
||
// 进行设备验证
|
||
bool verification()
|
||
{
|
||
bool flag = false; // 是否通过验证
|
||
|
||
string ICCID = GetSimICCID();
|
||
// string ICCID = "89860324045100015440";
|
||
// 实时加密
|
||
string pass = MD5(ICCID);
|
||
|
||
// 获取ICCID
|
||
string request = format(R"({"cardNo":"{}"})", pass);
|
||
|
||
auto res = NetRequest::QuickPostJson(url, request);
|
||
|
||
if (res->status == 200)
|
||
{
|
||
// 线上认证
|
||
auto json = nlohmann::json::parse(res->body);
|
||
if (json["code"] == 200)
|
||
{ // 验证成功
|
||
flag = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 线下验证
|
||
ReadFile rf(filepath);
|
||
if (rf.Open() == false)
|
||
return false;
|
||
auto lines = rf.ReadLines();
|
||
|
||
string str;
|
||
|
||
for (auto &ii : lines)
|
||
{
|
||
if (ii.find("ServerPwd:") != string::npos)
|
||
{
|
||
str = ii.substr(sizeof("ServerPwd"));
|
||
break;
|
||
}
|
||
}
|
||
pass = format(R"("{}")", pass);
|
||
if (str == pass)
|
||
{ // 验证成功
|
||
flag = true;
|
||
}
|
||
|
||
rf.Close();
|
||
}
|
||
|
||
return flag;
|
||
}
|
||
|
||
// 退出信号
|
||
void Exit(int sig)
|
||
{
|
||
cout << "Exiting..." << endl;
|
||
isRunning = false;
|
||
// confirm = false;
|
||
// MyServer->stop();
|
||
|
||
cout << "System Quit" << endl;
|
||
|
||
// system("sudo killall create_ap");
|
||
// 杀死后台 RTSP 和 ffmpeg 进程
|
||
// if (rtsp_proc.running())
|
||
// rtsp_proc.terminate();
|
||
// if (video_proc.running())
|
||
// video_proc.terminate();
|
||
// if (net_proc.running())
|
||
// net_proc.terminate();
|
||
}
|
||
|
||
// 打开RSTP服务器
|
||
void OpenRTSP()
|
||
{
|
||
string commnd = "../RTSPServer/mediamtx ../RTSPServer/mediamtx.yml";
|
||
rtsp_proc = bp::child("/bin/bash", bp::args = {"-c", commnd});
|
||
}
|
||
|
||
// 视频推流
|
||
void VideoStream()
|
||
{
|
||
// 静音ffmpeg的统计输出,保留错误;避免污染日志
|
||
string commnd = "ffmpeg -nostats -hide_banner -loglevel error -f v4l2 -i /dev/video0 -c:v h264_rkmpp -rtsp_transport tcp -f rtsp rtsp://192.168.12.1:8554/stream 2>/dev/null";
|
||
video_proc = bp::child("/bin/bash", bp::args = {"-c", commnd});
|
||
}
|
||
|
||
// 获取SIM卡号
|
||
// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号
|
||
string GetSimICCID(const string &tty)
|
||
{
|
||
int retry = 0; // 重试次数
|
||
while (retry < 5) // 最多重试5次
|
||
{
|
||
int fd = open(tty.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); // 打开串口
|
||
if (fd < 0)
|
||
{
|
||
std::cerr << "无法打开串口: " << tty << std::endl;
|
||
return "";
|
||
}
|
||
|
||
// 配置串口参数
|
||
struct termios options;
|
||
tcgetattr(fd, &options);
|
||
cfsetispeed(&options, B115200);
|
||
cfsetospeed(&options, B115200);
|
||
options.c_cflag |= (CLOCAL | CREAD);
|
||
tcsetattr(fd, TCSANOW, &options);
|
||
|
||
// 发送 AT+CCID 指令
|
||
write(fd, "AT+CCID\r\n", 9);
|
||
|
||
std::string result;
|
||
char buf[256] = {0};
|
||
// 循环多次读取,拼接内容,防止数据分包
|
||
for (int i = 0; i < 10; ++i)
|
||
{
|
||
usleep(100000); // 每次等待100ms
|
||
int n = read(fd, buf, sizeof(buf) - 1);
|
||
if (n > 0)
|
||
result.append(buf, n);
|
||
}
|
||
close(fd); // 关闭串口
|
||
|
||
// 检查是否有ERROR,若有则重试
|
||
if (result.find("ERROR") != std::string::npos)
|
||
{
|
||
retry++;
|
||
usleep(200000); // 等待200ms再重试
|
||
continue;
|
||
}
|
||
|
||
// 用正则提取ICCID(+CCID: 后面的数字)
|
||
std::smatch m;
|
||
std::regex reg(R"(\+CCID:\s*([0-9]+))");
|
||
if (std::regex_search(result, m, reg))
|
||
return m[1];
|
||
|
||
// 兜底:直接找19~22位数字(兼容不同长度的ICCID)
|
||
std::regex reg2(R"((\d{19,22}))");
|
||
if (std::regex_search(result, m, reg2))
|
||
return m[1];
|
||
|
||
// 没有ERROR但也没读到ICCID,直接退出循环
|
||
break;
|
||
}
|
||
return ""; // 多次重试失败,返回空
|
||
}
|
||
|
||
// 解析接收的数据
|
||
template <class T>
|
||
void CalculateInfo(T &conf, const string &json)
|
||
{
|
||
try
|
||
{
|
||
/* code */
|
||
auto j = nlohmann::json::parse(json);
|
||
if (j.contains("params") && j["params"].contains("media"))
|
||
{
|
||
auto conf = j["params"]["media"];
|
||
if (conf.contains("mirror"))
|
||
media.mirror = conf["mirror"].get<bool>();
|
||
if (conf.contains("flip"))
|
||
media.flip = conf["mirror"].get<bool>();
|
||
if (conf.contains("occlusion"))
|
||
media.occlusion = conf["occlusion"].get<bool>();
|
||
}
|
||
else if (j.contains("params") && j["params"].contains("algorithm"))
|
||
{
|
||
auto conf = j["params"]["algorithm"];
|
||
if (conf.contains("alarm_output_mode"))
|
||
algor.outPutMode = (conf["alarm_output_mode"].get<string>().find("高电平") != string::npos) ? true : false;
|
||
|
||
if (conf.contains("trigger_input_mode"))
|
||
algor.inPutMode = (conf["trigger_input_mode"].get<string>().find("高电平") != string::npos) ? true : false;
|
||
}
|
||
}
|
||
catch (const std::exception &e)
|
||
{
|
||
std::cerr << e.what() << '\n';
|
||
}
|
||
}
|