Files
RkApp/softWareInit/src/main.cpp
2025-10-26 13:18:30 +08:00

478 lines
14 KiB
C++
Raw 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.启动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';
}
}