430 lines
12 KiB
C++
430 lines
12 KiB
C++
/*
|
||
本程序用于 设备出厂获取 唯一标识符进行 wifi设置和修改
|
||
向云端发送访问请求(ICCID号),并显示请求次数
|
||
接收云端传传来的标识符,并保存至本地,根据表示符进行wifi设置
|
||
第二次启动时,自动检测是否已进行初始化,若是,直接从配置文件中获取唯一标识符
|
||
*/
|
||
#include <iostream>
|
||
#include <mutex>
|
||
#include <condition_variable>
|
||
#include <nlohmann/json.hpp> //用于解析JSON字符串
|
||
#include <chrono>
|
||
#include <boost/process.hpp>
|
||
#include "NetRequest.hpp"
|
||
#include "Netra.hpp"
|
||
#include <boost/process.hpp>
|
||
#include <signal.h>
|
||
|
||
using namespace std;
|
||
using namespace QCL;
|
||
|
||
using namespace ntq;
|
||
using namespace chrono_literals;
|
||
namespace bp = boost::process;
|
||
|
||
const string filePath = "/home/orangepi/RKApp/InitAuth/conf/.env";
|
||
// 请求唯一标识符接口路径
|
||
const string UUIDPost = "http://116.147.36.110:8095/device/requestQrcode";
|
||
|
||
const size_t MAXSIZE = 1024 * 1024;
|
||
|
||
// 保存ICCID
|
||
string ICCID = "";
|
||
string UUID; // 保存唯一标识
|
||
int PostCount = 0; // 请求次数
|
||
|
||
mutex fileMutex; // 文件锁
|
||
mutex logMutex; // 日志锁
|
||
atomic<pid_t> g_Device_pid{0};
|
||
|
||
// 检测是否已进行初始化
|
||
bool checkInit();
|
||
|
||
// 获取标识符并保存至配置文件中
|
||
bool GetSignID();
|
||
|
||
// 设置wifi热点:名称,密码
|
||
void setApNet(const string &name, const string &pwd);
|
||
|
||
// 读取配置文件,并获取表示符
|
||
void ReadUUID();
|
||
|
||
// 启动设备激活程序,等待用户激活
|
||
void startActiveWare();
|
||
|
||
// 退出信号处理
|
||
void forwardSigint(int);
|
||
|
||
// 获取SIM卡号
|
||
string GetSimICCID(const string &tty = "/dev/ttyUSB2");
|
||
|
||
// 清理日志
|
||
void clearLog();
|
||
|
||
int main()
|
||
{
|
||
// 自检
|
||
if (checkInit())
|
||
{ // 已进行初始化
|
||
ReadUUID();
|
||
setApNet(UUID, UUID);
|
||
// 启动激活程序
|
||
startActiveWare();
|
||
return 0;
|
||
}
|
||
else
|
||
{ // 未进行初始化
|
||
if (GetSignID())
|
||
{ // 获取标识符成功
|
||
setApNet(UUID, UUID);
|
||
startActiveWare();
|
||
return 0;
|
||
}
|
||
cerr << "获取标识符失败,请重试" << endl;
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// 清理日志
|
||
void clearLog()
|
||
{
|
||
lock_guard<mutex> lk(logMutex);
|
||
string logpath = "/home/orangepi/RKApp/Identification/src/nohup.out";
|
||
size_t length = filesystem::file_size(logpath);
|
||
if (length >= MAXSIZE)
|
||
{ // 清空日志
|
||
ofstream ofs(logpath, ios::trunc);
|
||
}
|
||
}
|
||
|
||
// 退出信号处理
|
||
void forwardSigint(int)
|
||
{
|
||
pid_t pgid = g_Device_pid.load();
|
||
if (pgid > 0)
|
||
{
|
||
kill(-pgid, SIGINT);
|
||
}
|
||
}
|
||
|
||
// 获取SIM卡号
|
||
// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号
|
||
string GetSimICCID(const string &tty)
|
||
{
|
||
int retry = 0;
|
||
while (retry < 5)
|
||
{
|
||
// 非阻塞打开,避免因 VMIN/VTIME 卡死
|
||
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);
|
||
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
||
options.c_oflag &= ~OPOST;
|
||
options.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||
options.c_cc[VMIN] = 0;
|
||
options.c_cc[VTIME] = 5; // 0.5s
|
||
tcsetattr(fd, TCSANOW, &options);
|
||
|
||
auto send_and_read = [&](const char *cmd) -> std::string
|
||
{
|
||
// 清空缓冲并发送
|
||
tcflush(fd, TCIOFLUSH);
|
||
write(fd, cmd, strlen(cmd));
|
||
std::string result;
|
||
char buf[256] = {0};
|
||
// 轮询读取,累计约2秒
|
||
for (int i = 0; i < 20; ++i)
|
||
{
|
||
int n = read(fd, buf, sizeof(buf));
|
||
if (n > 0)
|
||
result.append(buf, n);
|
||
usleep(100000);
|
||
}
|
||
return result;
|
||
};
|
||
|
||
// 先试 AT+QCCID,失败再试 AT+CCID
|
||
std::string result = send_and_read("AT+QCCID\r\n");
|
||
if (result.find("+QCCID") == std::string::npos)
|
||
{
|
||
std::string r2 = send_and_read("AT+CCID\r\n");
|
||
if (!r2.empty())
|
||
result += r2;
|
||
}
|
||
close(fd);
|
||
|
||
// 打印原始回应便于调试
|
||
std::string debug = result;
|
||
// 清理换行
|
||
debug.erase(std::remove_if(debug.begin(), debug.end(),
|
||
[](unsigned char c)
|
||
{ return c == '\r' || c == '\n'; }),
|
||
debug.end());
|
||
// std::cout << "ICCID原始回应: " << debug << std::endl;
|
||
|
||
// 错误重试
|
||
if (result.find("ERROR") != std::string::npos)
|
||
{
|
||
retry++;
|
||
usleep(200000);
|
||
continue;
|
||
}
|
||
|
||
// 解析(支持字母数字)
|
||
std::smatch m;
|
||
// +QCCID 或 +CCID 后取字母数字
|
||
std::regex reg(R"(\+(?:Q)?CCID:\s*([0-9A-Za-z]+))");
|
||
if (std::regex_search(debug, m, reg))
|
||
{
|
||
std::string iccid = m[1];
|
||
// 去掉尾部OK或非字母数字
|
||
while (!iccid.empty() && !std::isalnum(static_cast<unsigned char>(iccid.back())))
|
||
iccid.pop_back();
|
||
if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK")
|
||
iccid.erase(iccid.size() - 2);
|
||
return iccid;
|
||
}
|
||
|
||
// 兜底:19~22位的字母数字(如尾部含 D)
|
||
std::regex reg2(R"(([0-9A-Za-z]{19,22}))");
|
||
if (std::regex_search(debug, m, reg2))
|
||
{
|
||
std::string iccid = m[1];
|
||
while (!iccid.empty() && !std::isalnum(static_cast<unsigned char>(iccid.back())))
|
||
iccid.pop_back();
|
||
if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK")
|
||
iccid.erase(iccid.size() - 2);
|
||
return iccid;
|
||
}
|
||
|
||
// 进一步兜底:手工截取 +QCCID: / +CCID: 后的连续字母数字
|
||
auto parse_after = [&](const std::string &s, const std::string &key) -> std::string
|
||
{
|
||
size_t pos = s.find(key);
|
||
if (pos == std::string::npos)
|
||
return "";
|
||
pos += key.size();
|
||
while (pos < s.size() && std::isspace(static_cast<unsigned char>(s[pos])))
|
||
pos++;
|
||
size_t start = pos;
|
||
while (pos < s.size() && std::isalnum(static_cast<unsigned char>(s[pos])))
|
||
pos++;
|
||
std::string iccid = (pos > start) ? s.substr(start, pos - start) : "";
|
||
if (iccid.size() >= 2 && iccid.substr(iccid.size() - 2) == "OK")
|
||
iccid.erase(iccid.size() - 2);
|
||
return iccid;
|
||
};
|
||
{
|
||
std::string iccid = parse_after(debug, "+QCCID:");
|
||
if (iccid.empty())
|
||
iccid = parse_after(debug, "+CCID:");
|
||
if (!iccid.empty())
|
||
return iccid;
|
||
}
|
||
|
||
retry++;
|
||
usleep(200000);
|
||
}
|
||
return "";
|
||
}
|
||
|
||
// 检测是否已进行初始化
|
||
bool checkInit()
|
||
{
|
||
bool isInit = true;
|
||
// 上锁,防止竞争
|
||
lock_guard<mutex> lk(fileMutex);
|
||
// 读取配置文件
|
||
ReadFile rf(filePath);
|
||
if (rf.Open() == false)
|
||
{
|
||
cerr << "文件打开失败" << endl;
|
||
isInit = false;
|
||
}
|
||
|
||
auto lines = rf.ReadLines();
|
||
for (auto &line : lines)
|
||
{
|
||
if (line.find("UUID:null") != string::npos)
|
||
{ // 未初始化
|
||
isInit = false;
|
||
}
|
||
}
|
||
|
||
rf.Close();
|
||
return isInit;
|
||
}
|
||
|
||
// 获取标识符并保存至配置文件中
|
||
bool GetSignID()
|
||
{
|
||
// 获取成功状态
|
||
bool getState = true;
|
||
bool isRunning = true;
|
||
// ICCI获取
|
||
ICCID = GetSimICCID();
|
||
// cout << "ICCID = " << ICCID << endl;
|
||
if (ICCID.empty())
|
||
{
|
||
cerr << "获取ICCID失败,请检查SIM卡" << endl;
|
||
return false;
|
||
}
|
||
|
||
int postCount = 0;
|
||
int reTryTimes = 0;
|
||
string RequestBody = format(R"({"cardNo":"{}"})", ICCID);
|
||
// cout << "RequestBody = " << RequestBody << endl;
|
||
while (isRunning)
|
||
{
|
||
// 发送请求
|
||
// cout << "RequestBody = " << RequestBody << endl;
|
||
auto res = NetRequest::QuickPostJson(UUIDPost, RequestBody);
|
||
int code = res->status;
|
||
if (res->status == 200)
|
||
{ // 请求成功
|
||
++postCount;
|
||
auto json = nlohmann::json::parse(res->body);
|
||
// cout << "json = " << json << endl;
|
||
if (json["code"] == 601)
|
||
{
|
||
system("clear");
|
||
cout << "ICCID:" << ICCID << ",已发送请求次数 " << postCount << " 次" << endl;
|
||
}
|
||
else if (json["code"] == 200)
|
||
{
|
||
auto qrcode = json["data"]["qrcode"];
|
||
UUID = qrcode;
|
||
// 保存至配置文件中
|
||
{
|
||
lock_guard<mutex> lk(fileMutex);
|
||
ReadFile rf(filePath);
|
||
if (!rf.Open())
|
||
return false;
|
||
|
||
auto lines = rf.ReadLines();
|
||
for (auto &line : lines)
|
||
{
|
||
if (line.find("UUID:null") != string::npos)
|
||
{
|
||
line = format("UUID:{}", qrcode);
|
||
cout << line << endl;
|
||
}
|
||
}
|
||
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(filePath);
|
||
wf.overwriteText(out);
|
||
isRunning = false;
|
||
}
|
||
}
|
||
else if (json["code"] == 500)
|
||
{
|
||
cout << "申请失败,请联系管理员,ICCID = " << ICCID << endl;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
cerr << "请检查网络连接" << endl;
|
||
++reTryTimes;
|
||
if (reTryTimes > 5)
|
||
{ // 重试五次
|
||
getState = false;
|
||
isRunning = false;
|
||
}
|
||
}
|
||
// 间隔两秒
|
||
this_thread::sleep_for(2s);
|
||
}
|
||
return getState;
|
||
}
|
||
|
||
// 设置wifi热点:名称,密码
|
||
void setApNet(const string &name, const string &pwd)
|
||
{
|
||
// 清空nohup.out
|
||
clearLog();
|
||
// 确保无线接口启用
|
||
system("sudo rfkill unblock all");
|
||
system("sudo ip link set wlan0 up");
|
||
// 清理占用与启用接口
|
||
system("sudo pkill -f create_ap; sudo pkill -f dnsmasq");
|
||
system("sudo ip link set wlan0 up");
|
||
|
||
// 组装SSID和密码
|
||
std::string ssid = name.empty() ? UUID : name;
|
||
std::string pass = pwd.empty() ? UUID : pwd;
|
||
if (pass.size() < 8)
|
||
pass = "12345678";
|
||
|
||
// 上游接口优先默认路由
|
||
char buf[64]{0};
|
||
FILE *fp = popen("ip route show default | awk '{print $5}' | head -n1", "r");
|
||
std::string upstream = "enP4p65s0";
|
||
if (fp && fgets(buf, sizeof(buf), fp))
|
||
{
|
||
upstream = buf;
|
||
if (!upstream.empty() && upstream.back() == '\n')
|
||
upstream.pop_back();
|
||
}
|
||
if (fp)
|
||
pclose(fp);
|
||
string cmd = format("nohup sudo create_ap --no-virt wlan0 {} {} {} &", upstream,UUID, "12345678");
|
||
|
||
cout << "cmd = " << cmd << endl;
|
||
system(cmd.c_str());
|
||
}
|
||
|
||
// 读取配置文件,并获取标识符符
|
||
void ReadUUID()
|
||
{
|
||
lock_guard<mutex> lk(fileMutex);
|
||
ReadFile rf(filePath);
|
||
rf.Open();
|
||
auto lines = rf.ReadLines();
|
||
for (auto &line : lines)
|
||
{
|
||
if (line.find("UUID:\"") != string::npos)
|
||
{
|
||
size_t start = sizeof("UUID:\"") - 1;
|
||
size_t end = line.find_last_of("\"") - start;
|
||
UUID = line.substr(start, end);
|
||
}
|
||
}
|
||
rf.Close();
|
||
}
|
||
|
||
// 启动设备激活程序,等待用户激活
|
||
void startActiveWare()
|
||
{
|
||
string cmd = "/home/orangepi/RKApp/DeviceActivate/bin/init";
|
||
try
|
||
{
|
||
// 启动为前台子进程,父进程等待它退出
|
||
bp::child c(cmd);
|
||
g_Device_pid = c.id();
|
||
signal(SIGINT, forwardSigint);
|
||
c.wait(); // 等待子进程结束
|
||
}
|
||
catch (const std::exception &e)
|
||
{
|
||
cerr << e.what() << "\n";
|
||
}
|
||
} |