/* 本程序用于设备验证 验证通过执行以下功能: 1.启动RTSP推流 2.启动系统各项服务,用于和双目交互 3.启动mqtt,与App进行数据交互 */ #include #include #include #include #include #include #include #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 isRunning(true); // 全局运行标志 std::atomic 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 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(); if (jv.is_string()) { try { return std::stod(jv.get()); } 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 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(); if (conf.contains("flip")) media.flip = conf["mirror"].get(); if (conf.contains("occlusion")) media.occlusion = conf["occlusion"].get(); } 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().find("高电平") != string::npos) ? true : false; if (conf.contains("trigger_input_mode")) algor.inPutMode = (conf["trigger_input_mode"].get().find("高电平") != string::npos) ? true : false; } } catch (const std::exception &e) { std::cerr << e.what() << '\n'; } }