10 Commits

9 changed files with 1107 additions and 253 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,14 @@ std::vector<terminal_dev> terminal_devlist;
//台账锁 //台账锁
std::mutex ledgermtx; std::mutex ledgermtx;
///////////////////////////////////异步接口队列
std::mutex g_qvvr_async_mtx;
std::list<QvvrFileDownloadTask> g_qvvr_file_download_list;
std::list<QvvrJsonTask> g_qvvr_json_list;
///////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern int g_front_seg_num; extern int g_front_seg_num;
@@ -211,7 +219,7 @@ bool DownloadFileAPI_web(const std::string& strUrl, // 接口路径
////////////////////////////////////////////////////////////////////////////////////////////////////////上传文件接口 ////////////////////////////////////////////////////////////////////////////////////////////////////////上传文件接口
//处理文件上传响应 //处理文件上传响应
void handleUploadResponse(const std::string& response, std::string& wavepath, int type) { bool handleUploadResponse(const std::string& response, std::string& wavepath, int type) {
using nlohmann::json; //把 nlohmann::json 这个名字带到当前作用域 using nlohmann::json; //把 nlohmann::json 这个名字带到当前作用域
@@ -223,27 +231,34 @@ void handleUploadResponse(const std::string& response, std::string& wavepath, in
catch (const json::parse_error& e) { catch (const json::parse_error& e) {
std::cerr << "Error parsing response: " << e.what() << std::endl; std::cerr << "Error parsing response: " << e.what() << std::endl;
DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON, "文件上传接口响应异常,上传文件失败"); DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON, "文件上传接口响应异常,上传文件失败");
return; return false;
} }
// 提取字段 // 提取字段
if (!json_data.contains("code") || !json_data.contains("data")) { if (!json_data.contains("code") || !json_data.contains("data")) {
std::cerr << "Error: Missing expected fields in JSON response." << std::endl; std::cerr << "Error: Missing expected fields in JSON response." << std::endl;
DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON, "文件上传接口响应异常,上传文件失败"); DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON, "文件上传接口响应异常,上传文件失败");
return; return false;
} }
std::string code = json_data["code"].get<std::string>(); std::string code = json_data["code"].get<std::string>();
std::cout << "Response Code: " << code << std::endl; std::cout << "Response Code: " << code << std::endl;
std::string msg = json_data.value("msg", std::string{"not found"}); std::string msg = json_data.value("message",
json_data.value("msg", std::string{"not found"}));
std::cout << "Message: " << msg << std::endl; std::cout << "Message: " << msg << std::endl;
if (code != "A0000") {
std::cerr << "File upload failed, response code=" << code
<< ", message=" << msg << std::endl;
return false;
}
auto& data = json_data["data"]; auto& data = json_data["data"];
if (!data.contains("name") || !data.contains("fileName") || !data.contains("url")) { if (!data.contains("name") || !data.contains("fileName") || !data.contains("url")) {
std::cerr << "Error: Missing expected fields in JSON data object." << std::endl; std::cerr << "Error: Missing expected fields in JSON data object." << std::endl;
DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON, "文件上传接口响应异常,上传文件失败"); DIY_ERRORLOG_CODE("process",0,LOG_CODE_JSON, "文件上传接口响应异常,上传文件失败");
return; return false;
} }
std::string name = data["name"].get<std::string>(); std::string name = data["name"].get<std::string>();
@@ -280,20 +295,24 @@ void handleUploadResponse(const std::string& response, std::string& wavepath, in
std::cout << "wavepath: " << wavepath << std::endl; std::cout << "wavepath: " << wavepath << std::endl;
DIY_INFOLOG_CODE("process",0,LOG_CODE_FILE, "上传文件成功,远端文件名:%s", wavepath.c_str()); DIY_INFOLOG_CODE("process",0,LOG_CODE_FILE, "上传文件成功,远端文件名:%s", wavepath.c_str());
return true;
} }
//上传文件 //上传文件
void SendFileWeb(const std::string& strUrl, const std::string& localpath, const std::string& cloudpath, std::string& wavepath, int type) { bool SendFileWeb(const std::string& strUrl, const std::string& localpath, const std::string& cloudpath, std::string& wavepath, int type) {
wavepath.clear();
// 基本存在性检查 // 基本存在性检查
if (access(localpath.c_str(), F_OK) != 0) { if (access(localpath.c_str(), F_OK) != 0) {
std::cerr << "Local file does not exist: " << localpath << std::endl; std::cerr << "Local file does not exist: " << localpath << std::endl;
return; return false;
} }
// ★新增stat 打印大小,便于快速确认读源 // ★新增stat 打印大小,便于快速确认读源
struct stat st {}; struct stat st {};
if (stat(localpath.c_str(), &st) != 0) { if (stat(localpath.c_str(), &st) != 0) {
perror("stat"); perror("stat");
return false;
} else { } else {
std::cout << "[debug] upload file: " << localpath std::cout << "[debug] upload file: " << localpath
<< ", size=" << static_cast<long long>(st.st_size) << " bytes\n"; << ", size=" << static_cast<long long>(st.st_size) << " bytes\n";
@@ -351,6 +370,7 @@ void SendFileWeb(const std::string& strUrl, const std::string& localpath, const
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply_web); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, req_reply_web);
// 执行请求 // 执行请求
bool upload_ok = false;
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
const char* em = errbuf[0] ? errbuf : curl_easy_strerror(res); const char* em = errbuf[0] ? errbuf : curl_easy_strerror(res);
@@ -358,16 +378,18 @@ void SendFileWeb(const std::string& strUrl, const std::string& localpath, const
DIY_ERRORLOG_CODE("process",0,LOG_CODE_CONFIG, "通过文件接口上传文件 %s 失败",localpath.c_str()); DIY_ERRORLOG_CODE("process",0,LOG_CODE_CONFIG, "通过文件接口上传文件 %s 失败",localpath.c_str());
} else { } else {
std::cout << "http web success, response: " << resPost0 << std::endl; std::cout << "http web success, response: " << resPost0 << std::endl;
handleUploadResponse(resPost0, wavepath, type); // 处理响应 upload_ok = handleUploadResponse(resPost0, wavepath, type); // 处理响应
} }
// 清理 // 清理
curl_formfree(formpost); // 释放表单数据 curl_formfree(formpost); // 释放表单数据
//curl_slist_free_all(header_list); // 释放头部列表 //curl_slist_free_all(header_list); // 释放头部列表
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
return upload_ok;
} else { } else {
std::cerr << ">>> curl init failed" << std::endl; std::cerr << ">>> curl init failed" << std::endl;
} }
return false;
} }
//上传暂态文件 //上传暂态文件
@@ -1699,3 +1721,148 @@ bool get_monitor_name_by_monitor_id(const std::string& monitor_id,
out_monitor_name.clear(); out_monitor_name.clear();
return false; return false;
} }
/////////////////////////////////////////////////异步接口
// ★新增:代替直接调用 update_qvvr_file_download()
void enqueue_qvvr_file_download(const std::string& file_path,
const std::string& terminal_id)
{
std::lock_guard<std::mutex> lk(g_qvvr_async_mtx);
QvvrFileDownloadTask task;
task.file_path = file_path;
task.terminal_id = terminal_id;
g_qvvr_file_download_list.push_back(task);
std::cout << "[QVVR_ASYNC] enqueue file_download terminal="
<< terminal_id
<< " file=" << file_path
<< " size=" << g_qvvr_file_download_list.size()
<< std::endl;
}
// ★新增:代替直接调用 transfer_json_qvvr_data()
void enqueue_qvvr_json_update(const std::string& terminal_id,
int line_id,
double amplitude,
double persist_time,
long long trigger_time_ms,
int type,
int phase,
const std::string& wavepath)
{
std::lock_guard<std::mutex> lk(g_qvvr_async_mtx);
QvvrJsonTask task;
task.terminal_id = terminal_id;
task.line_id = line_id;
task.amplitude = amplitude;
task.persist_time = persist_time;
task.trigger_time_ms = trigger_time_ms;
task.type = type;
task.phase = phase;
task.wavepath = wavepath;
g_qvvr_json_list.push_back(task);
std::cout << "[QVVR_ASYNC] enqueue json terminal="
<< terminal_id
<< " line=" << line_id
<< " time=" << trigger_time_ms
<< " size=" << g_qvvr_json_list.size()
<< std::endl;
}
// ★新增FrontThread 中调用,消费异步接口任务
void process_qvvr_async_tasks()
{
// 每轮最多处理几个,避免一个接口卡住太久
const int MAX_FILE_TASK_PER_ROUND = 1;
const int MAX_JSON_TASK_PER_ROUND = 3;
// 1. 处理 update_qvvr_file_download 队列
for (int n = 0; n < MAX_FILE_TASK_PER_ROUND; ++n) {
QvvrFileDownloadTask task;
{
std::lock_guard<std::mutex> lk(g_qvvr_async_mtx);
if (g_qvvr_file_download_list.empty()) {
break;
}
task = g_qvvr_file_download_list.front();
g_qvvr_file_download_list.pop_front();
}
try {
std::cout << "[QVVR_ASYNC] call update_qvvr_file_download terminal="
<< task.terminal_id
<< " file=" << task.file_path
<< std::endl;
update_qvvr_file_download(task.file_path, task.terminal_id);
}
catch (const std::exception& e) {
std::cerr << "[QVVR_ASYNC][ERROR] update_qvvr_file_download exception: "
<< e.what()
<< " terminal=" << task.terminal_id
<< " file=" << task.file_path
<< std::endl;
}
catch (...) {
std::cerr << "[QVVR_ASYNC][ERROR] update_qvvr_file_download unknown exception"
<< " terminal=" << task.terminal_id
<< " file=" << task.file_path
<< std::endl;
}
}
// 2. 处理 transfer_json_qvvr_data 队列
for (int n = 0; n < MAX_JSON_TASK_PER_ROUND; ++n) {
QvvrJsonTask task;
{
std::lock_guard<std::mutex> lk(g_qvvr_async_mtx);
if (g_qvvr_json_list.empty()) {
break;
}
task = g_qvvr_json_list.front();
g_qvvr_json_list.pop_front();
}
try {
std::cout << "[QVVR_ASYNC] call transfer_json_qvvr_data terminal="
<< task.terminal_id
<< " line=" << task.line_id
<< " time=" << task.trigger_time_ms
<< " wavepath=" << task.wavepath
<< std::endl;
transfer_json_qvvr_data(task.terminal_id,
task.line_id,
task.amplitude,
task.persist_time,
task.trigger_time_ms,
task.type,
task.phase,
task.wavepath);
}
catch (const std::exception& e) {
std::cerr << "[QVVR_ASYNC][ERROR] transfer_json_qvvr_data exception: "
<< e.what()
<< " terminal=" << task.terminal_id
<< " line=" << task.line_id
<< std::endl;
}
catch (...) {
std::cerr << "[QVVR_ASYNC][ERROR] transfer_json_qvvr_data unknown exception"
<< " terminal=" << task.terminal_id
<< " line=" << task.line_id
<< std::endl;
}
}
}

View File

@@ -32,6 +32,44 @@ class Front;
#define RECALL_HIS_DATA_BASE_NODE_ID 600 #define RECALL_HIS_DATA_BASE_NODE_ID 600
#define RECALL_ALL_DATA_BASE_NODE_ID 700*/ #define RECALL_ALL_DATA_BASE_NODE_ID 700*/
///////////////////////////////////////////////////////////////////////////////////////////异步接口
struct QvvrFileDownloadTask {
std::string file_path;
std::string terminal_id;
};
struct QvvrJsonTask {
std::string terminal_id;
int line_id;
double amplitude;
double persist_time;
long long trigger_time_ms;
int type;
int phase;
std::string wavepath;
};
// ★声明
extern std::mutex g_qvvr_async_mtx;
extern std::list<QvvrFileDownloadTask> g_qvvr_file_download_list;
extern std::list<QvvrJsonTask> g_qvvr_json_list;
extern void process_qvvr_async_tasks();
extern void enqueue_qvvr_file_download(const std::string& file_path,
const std::string& terminal_id);
extern void enqueue_qvvr_json_update(const std::string& terminal_id,
int line_id,
double amplitude,
double persist_time,
long long trigger_time_ms,
int type,
int phase,
const std::string& wavepath);
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
//单条补招时间结构 //单条补招时间结构
@@ -68,6 +106,12 @@ enum class ActionResult {
}; };
// ====== ★修改:扩展 RecallFile支持“多目录 + 文件筛选 + 串行下载”的状态机 ====== // ====== ★修改:扩展 RecallFile支持“多目录 + 文件筛选 + 串行下载”的状态机 ======
enum class RecallFileType {
NONE = 0,
STEADY_FILE, // 稳态文件
VOLTAGE_FILE // 暂态直下文件
};
class RecallFile class RecallFile
{ {
public: public:
@@ -75,18 +119,35 @@ public:
int recall_status; // 补招状态 0-未补招 1-补招中 2-补招完成 3-补招失败 int recall_status; // 补招状态 0-未补招 1-补招中 2-补招完成 3-补招失败
std::string StartTime; // 数据补招起始时间yyyy-MM-dd HH:mm:ss std::string StartTime; // 数据补招起始时间yyyy-MM-dd HH:mm:ss
std::string EndTime; // 数据补招结束时间yyyy-MM-dd HH:mm:ss std::string EndTime; // 数据补招结束时间yyyy-MM-dd HH:mm:ss
// ===== 业务类型 =====
// STEADY稳态文件补招
// VOLTAGE暂态事件补招
std::string STEADY; // 补招历史统计数据标识 0-不补招1-补招 std::string STEADY; // 补招历史统计数据标识 0-不补招1-补招
std::string VOLTAGE; // 补招暂态事件标识 0-不补招1-补招 std::string VOLTAGE; // 补招暂态事件标识 0-不补招1-补招
// ===== 文件下载类型 =====
RecallFileType file_type = RecallFileType::NONE;
//暂态文件用 //暂态文件用
bool direct_mode = false; // 直下文件开关true 表示不按时间窗,仅按目标文件名 std::string target_filetimes; // 直下文件匹配时间点yyyyMMdd_HHmmss
std::string target_filetimes; // 直下文件匹配时间点yyyyMMdd_HHmmss仅 direct_mode=true 时有效
// ★新增:按“目录名 -> 文件名列表”的映射;由“其他线程”在目录请求成功后回填 // ★新增:按“目录名 -> 文件名列表”的映射;由“其他线程”在目录请求成功后回填
std::map<std::string, std::vector<tag_dir_info>> dir_files; std::map<std::string, std::vector<tag_dir_info>> dir_files;
// ★新增:候选目录(可扩展) std::vector<std::string> steady_dir_candidates{
std::vector<std::string> dir_candidates{ "/cf/pqdif", //580绝对真实路径
"/bd0/pqdif", //chemengyu提供包含bd0
"/bd0/pqdif/%DESC%/%DAY%",
"/bd0/pqdif/Line%SEQ%",
"/bd0/historyFile/%DESC%",
"/pqdif", // 默认版本 / 新疆 可能取到其他测点文件
"/pqdif/%DESC%/%DAY%", // 上海pqdif_dir_cfg 或描述目录 + 日期 不会取到其他测点文件
"/pqdif/Line%SEQ%", // 云南 不会取到其他测点文件
"/historyFile/%DESC%" // 广东 不会取到其他测点文件
};
std::vector<std::string> voltage_dir_candidates{
"/cf/COMTRADE", "/cf/COMTRADE",
"/bd0/COMTRADE", "/bd0/COMTRADE",
"/sd0/COMTRADE", "/sd0/COMTRADE",
@@ -102,6 +163,9 @@ public:
ActionResult list_result = ActionResult::PENDING; // 当前目录的列举结果 ActionResult list_result = ActionResult::PENDING; // 当前目录的列举结果
ActionResult download_result = ActionResult::PENDING; // 当前文件的下载结果 ActionResult download_result = ActionResult::PENDING; // 当前文件的下载结果
// 稳态文件用:一个 guid 下的多个补招时间段
std::vector<std::pair<long long, long long>> recall_ranges;
// ★新增:下载队列(已筛选出在时间窗内的文件,含完整路径) // ★新增:下载队列(已筛选出在时间窗内的文件,含完整路径)
std::list<std::string> download_queue; //一个时间可能对应多个文件 std::list<std::string> download_queue; //一个时间可能对应多个文件
std::string downloading_file; // 当前正在下载的文件(完整路径) std::string downloading_file; // 当前正在下载的文件(完整路径)
@@ -109,8 +173,22 @@ public:
std::unordered_set<std::string> required_files; // 本次应当下载成功的文件全集 std::unordered_set<std::string> required_files; // 本次应当下载成功的文件全集
std::unordered_set<std::string> file_success; // 已下载成功的文件集合 std::unordered_set<std::string> file_success; // 已下载成功的文件集合
// 是否稳态文件任务
bool is_steady_file() const {
return file_type == RecallFileType::STEADY_FILE;
}
// 是否暂态直下文件任务
bool is_voltage_file() const {
return file_type == RecallFileType::VOLTAGE_FILE;
}
const std::vector<std::string>& active_dirs() const {
return is_steady_file() ? steady_dir_candidates : voltage_dir_candidates;
}
// ★新增:一个便捷复位 // ★新增:一个便捷复位
void reset_runtime(bool keep_direct = false) void reset_runtime(bool keep_target_filetimes = false)
{ {
phase = RecallPhase::IDLE; phase = RecallPhase::IDLE;
cur_dir_index = 0; cur_dir_index = 0;
@@ -124,9 +202,10 @@ public:
required_files.clear(); required_files.clear();
file_success.clear(); file_success.clear();
// 注意file_type 不属于运行态,不能清除,因为它决定了本次补招的业务类型(稳态/暂态),而这个业务类型在整个补招过程中是固定的,不应当被运行态重置影响
// ★新增:按需保留直下文件开关和目标名 // ★新增:按需保留直下文件开关和目标名
if (!keep_direct) { if (!keep_target_filetimes) {
direct_mode = false;
target_filetimes.clear(); // ▲列表清空 target_filetimes.clear(); // ▲列表清空
} }
} }
@@ -718,7 +797,7 @@ bool save_internal_value(const std::string &dev_id, const std::vector<ushort> &f
bool save_set_value(const std::string &dev_id, unsigned char mp_index, const std::vector<float> &fabsf); bool save_set_value(const std::string &dev_id, unsigned char mp_index, const std::vector<float> &fabsf);
//发送文件 //发送文件
void SendFileWeb(const std::string& strUrl, const std::string& localpath, const std::string& cloudpath, std::string& wavepath,int Type); bool SendFileWeb(const std::string& strUrl, const std::string& localpath, const std::string& cloudpath, std::string& wavepath,int Type);
//状态翻转 //状态翻转
void connect_status_update(const std::string& id, int status); void connect_status_update(const std::string& id, int status);
@@ -1069,4 +1148,4 @@ bool runninginfo_cache_take(const std::string& dev_id, RunningInformation& out);
void versioninfo_cache_put(const std::string& dev_id, const DeviceVersionInfo& info); void versioninfo_cache_put(const std::string& dev_id, const DeviceVersionInfo& info);
bool versioninfo_cache_take(const std::string& dev_id, DeviceVersionInfo& out); bool versioninfo_cache_take(const std::string& dev_id, DeviceVersionInfo& out);
#endif #endif

View File

@@ -98,6 +98,8 @@ extern int TEST_PORT; //测试端口号
extern std::string FRONT_INST; extern std::string FRONT_INST;
extern bool PQD_FLAG;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 功能函数 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 功能函数
template<typename T, typename... Args> template<typename T, typename... Args>
@@ -130,6 +132,11 @@ bool parse_param(int argc, char* argv[]) {
try { try {
g_front_seg_index = std::stoi(val.substr(0, pos)); g_front_seg_index = std::stoi(val.substr(0, pos));
g_front_seg_num = std::stoi(val.substr(pos + 1)); g_front_seg_num = std::stoi(val.substr(pos + 1));
if (g_front_seg_index == 0) {
PQD_FLAG = true;
}
} catch (...) { } catch (...) {
std::cerr << "Invalid -s format." << std::endl; std::cerr << "Invalid -s format." << std::endl;
} }
@@ -144,6 +151,11 @@ bool parse_param(int argc, char* argv[]) {
try { try {
g_front_seg_index = std::stoi(val.substr(0, pos)); g_front_seg_index = std::stoi(val.substr(0, pos));
g_front_seg_num = std::stoi(val.substr(pos + 1)); g_front_seg_num = std::stoi(val.substr(pos + 1));
if (g_front_seg_index == 0) {
PQD_FLAG = true;
}
} catch (...) { } catch (...) {
std::cerr << "Invalid -s format." << std::endl; std::cerr << "Invalid -s format." << std::endl;
} }
@@ -223,13 +235,13 @@ std::string get_parent_directory() {
//解析模板文件 //解析模板文件
//Set_xml_nodeinfo(); //Set_xml_nodeinfo();
StartFrontThread(); //开启主线程
StartMQConsumerThread(); //开启消费者线程
StartMQProducerThread(); //开启生产者线程 StartMQProducerThread(); //开启生产者线程
StartTimerThread(); //开启定时线程 if(!PQD_FLAG){
StartFrontThread(); //开启主线程
StartMQConsumerThread(); //开启消费者线程
StartTimerThread(); //开启定时线程
}
//启动worker 根据启动标志启动 //启动worker 根据启动标志启动
if(G_TEST_FLAG){ if(G_TEST_FLAG){
@@ -461,6 +473,9 @@ void Front::FrontThread() {
check_recall_event(); // 处理补招事件从list中读取然后直接调用接口,每一条可能都不同测点,每个测点自己做好记录 check_recall_event(); // 处理补招事件从list中读取然后直接调用接口,每一条可能都不同测点,每个测点自己做好记录
check_recall_file(); //处理补招文件-稳态和暂态 check_recall_file(); //处理补招文件-稳态和暂态
// ★新增:异步处理 QVVR 接口任务
process_qvvr_async_tasks();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }

View File

@@ -758,12 +758,20 @@ void Worker::printLedgerinshell(const terminal_dev& dev, int fd) {
<< ", VOLTAGE=" << rf.VOLTAGE << ", VOLTAGE=" << rf.VOLTAGE
<< "\n"; << "\n";
// ★新增:直下模式与目标时间列表 // ★新增:文件补招类型与目标信息
os << "\r\x1B[K |-- direct_mode=" << (rf.direct_mode ? "true" : "false") os << "\r\x1B[K |-- file_type="
<< ", target_filetimes(" << rf.target_filetimes << ")\n"; << (rf.is_steady_file() ? "STEADY_FILE" :
{ rf.is_voltage_file() ? "VOLTAGE_FILE" : "NONE");
os << "\r\x1B[K |.. " << rf.target_filetimes << "\n";
if (rf.is_voltage_file()) {
os << ", target_filetimes=" << rf.target_filetimes;
} }
else if (rf.is_steady_file()) {
os << ", time_range=" << rf.StartTime
<< " ~ " << rf.EndTime;
}
os << "\n";
// ★新增:状态机运行态 // ★新增:状态机运行态
os << "\r\x1B[K |-- phase=" << phaseStr(rf.phase) os << "\r\x1B[K |-- phase=" << phaseStr(rf.phase)
@@ -773,15 +781,15 @@ void Worker::printLedgerinshell(const terminal_dev& dev, int fd) {
<< ", download_result=" << resultStr(rf.download_result) << "\n"; << ", download_result=" << resultStr(rf.download_result) << "\n";
// ★新增:候选目录 // ★新增:候选目录
os << "\r\x1B[K |-- dir_candidates(" << rf.dir_candidates.size() << ")\n"; os << "\r\x1B[K |-- active_dirs(" << rf.active_dirs().size() << ")\n";
{ {
size_t c = 0; size_t c = 0;
for (const auto& d : rf.dir_candidates) { for (const auto& d : rf.active_dirs()) {
if (c++ >= MAX_ITEMS) break; if (c++ >= MAX_ITEMS) break;
os << "\r\x1B[K |-- " << d << "\n"; os << "\r\x1B[K |-- " << d << "\n";
} }
if (rf.dir_candidates.size() > MAX_ITEMS) { if (rf.active_dirs().size() > MAX_ITEMS) {
os << "\r\x1B[K |.. (+" << (rf.dir_candidates.size() - MAX_ITEMS) << " more)\n"; os << "\r\x1B[K |.. (+" << (rf.active_dirs().size() - MAX_ITEMS) << " more)\n";
} }
} }

View File

@@ -38,7 +38,7 @@ AccessKey=rmqroot
SecretKey=001@#njcnmq SecretKey=001@#njcnmq
Topic_Test=lnk_Topic Topic_Test=lnk_Topic
Tag_Test=Test_Tag Tag_Test=914b94563ca7f272c90ee8580ed6adc6
Key_Test=Test_Keys Key_Test=Test_Keys
Testflag=1 Testflag=1
Testnum=0 Testnum=0
@@ -48,7 +48,7 @@ TestList=
consumer=Group_consumer consumer=Group_consumer
ConsumerIpport=192.168.1.103:9876 ConsumerIpport=192.168.1.103:9876
ConsumerTopicRT=ask_real_data_topic ConsumerTopicRT=ask_real_data_Topic
ConsumerTagRT=Test_Tag ConsumerTagRT=Test_Tag
ConsumerKeyRT=Test_Keys ConsumerKeyRT=Test_Keys
ConsumerAccessKey=rmqroot ConsumerAccessKey=rmqroot
@@ -57,7 +57,7 @@ ConsumerChannel=
ConsumerTopicUD=control_Topic ConsumerTopicUD=control_Topic
ConsumerTagUD=Test_Tag ConsumerTagUD=Test_Tag
ConsumerKeyUD=Test_Keys ConsumerKeyUD=Test_Keys
ConsumerTopicRC=recall_Topic ConsumerTopicRC=ask_recall_Topic
ConsumerTagRC=Test_Tag ConsumerTagRC=Test_Tag
ConsumerKeyRC=Test_Keys ConsumerKeyRC=Test_Keys
ConsumerTopicSET=process_Topic ConsumerTopicSET=process_Topic
@@ -75,6 +75,15 @@ CONNECTKey=Test_Keys
Heart_Beat_Topic=Heart_Beat_Topic Heart_Beat_Topic=Heart_Beat_Topic
Heart_Beat_Tag=Test_Tag Heart_Beat_Tag=Test_Tag
Heart_Beat_Key=Test_Key Heart_Beat_Key=Test_Key
Topic_Reply_Topic=Topic_Reply_Topic Topic_Reply_Topic=Reply_Topic
Topic_Reply_Tag=Test_Tag Topic_Reply_Tag=Test_Tag
Topic_Reply_Key=Test_Key Topic_Reply_Key=Test_Key
ConsumerTopicLOG=ask_log_Topic
ConsumerTagLOG=Test_Tag
ConsumerKeyLOG=Test_Keys
ConsumerTopicCLOUD=Cloud_Topic
ConsumerTagCLOUD=Cloud_Tag
ConsumerKeyCLOUD=Cloud_Keys
Cloud_Reply_Topic=Cloud_Reply_Topic
Cloud_Reply_Tag=Cloud_Reply_Tag
Cloud_Reply_Key=Cloud_Reply_Key

View File

@@ -350,11 +350,14 @@ void process_received_message(string mac, string id,const char* data, size_t len
<< ", 时间戳: " << record.triggerTimeMs << "ms" << std::endl; << ", 时间戳: " << record.triggerTimeMs << "ms" << std::endl;
//lnk20250805 事件上送先记录,录波文件上传结束后再更新文件 //lnk20250805 事件上送先记录,录波文件上传结束后再更新文件
append_qvvr_event(id,event.head.name, if(record.nType != 0){
append_qvvr_event(id,event.head.name,
record.nType,record.fPersisstime,record.fMagntitude,record.triggerTimeMs,record.phase); record.nType,record.fPersisstime,record.fMagntitude,record.triggerTimeMs,record.phase);
transfer_json_qvvr_data(id,event.head.name, /*transfer_json_qvvr_data(id,event.head.name,
record.fMagntitude,record.fPersisstime,record.triggerTimeMs,record.nType,record.phase, record.fMagntitude,record.fPersisstime,record.triggerTimeMs,record.nType,record.phase,
""); "");*/
enqueue_qvvr_json_update(id, event.head.name, record.fMagntitude, record.fPersisstime, record.triggerTimeMs, record.nType, record.phase, "");
}
//事件主动上送处理完成,不需要通知状态机 //事件主动上送处理完成,不需要通知状态机
} }
@@ -462,6 +465,9 @@ void process_received_message(string mac, string id,const char* data, size_t len
); );
std::cout << "Added download request for: " << filename << std::endl; std::cout << "Added download request for: " << filename << std::endl;
DIY_INFOLOG_CODE(id, 1, static_cast<int>(LogCode::LOG_CODE_DEV_ALARM),
"添加装置暂态波形文件下载请求: %s",
filename.c_str());
} }
//最后报文收发处理逻辑(如果当前装置空闲则尝试执行后续动作)(如果当前装置存在其他状态则直接退出,不要干扰装置后续执行) //最后报文收发处理逻辑(如果当前装置空闲则尝试执行后续动作)(如果当前装置存在其他状态则直接退出,不要干扰装置后续执行)
@@ -840,10 +846,20 @@ void process_received_message(string mac, string id,const char* data, size_t len
if (out_file) { if (out_file) {
out_file.write(reinterpret_cast<const char*>(file_data.data()), out_file.write(reinterpret_cast<const char*>(file_data.data()),
file_data.size()); file_data.size());
std::cout << "File saved: " << file_path << std::endl; // SendFileWebAuto reopens this path. Close first so data buffered by
// ofstream (especially for small files) is visible to stat/libcurl.
out_file.close();
if (!out_file) {
std::cerr << "Failed to write/close file: " << file_path << std::endl;
ClientManager::instance().change_device_state(id, DeviceState::IDLE);
return;
}
std::cout << "File saved: " << file_path
<< ", size=" << file_data.size() << " bytes" << std::endl;
//lnk20250805文件保存成功调用录波文件上送接口 //lnk20250805文件保存成功调用录波文件上送接口
update_qvvr_file_download(file_path, id); //update_qvvr_file_download(file_path, id);
enqueue_qvvr_file_download(file_path, id);
} }
else { else {
std::cerr << "Failed to save file: " << file_path std::cerr << "Failed to save file: " << file_path
@@ -860,6 +876,9 @@ void process_received_message(string mac, string id,const char* data, size_t len
} }
else { else {
// 装置答非所问异常 // 装置答非所问异常
DIY_INFOLOG_CODE(id, 1, static_cast<int>(LogCode::LOG_CODE_DEV_ALARM),
"装置答非所问异常,接收波形文件数据错误,错误码:%d",
static_cast<int>(udata[8]));
//on_device_response_minimal(static_cast<int>(ResponseCode::INTERNAL_ERROR), id, 0, static_cast<int>(DeviceState::READING_EVENTFILE)); //on_device_response_minimal(static_cast<int>(ResponseCode::INTERNAL_ERROR), id, 0, static_cast<int>(DeviceState::READING_EVENTFILE));
// 接收波形文件数据错误,调整为空闲状态,处理下一项工作。 // 接收波形文件数据错误,调整为空闲状态,处理下一项工作。
ClientManager::instance().change_device_state(id, DeviceState::IDLE); ClientManager::instance().change_device_state(id, DeviceState::IDLE);
@@ -1161,7 +1180,18 @@ void process_received_message(string mac, string id,const char* data, size_t len
if (out_file) { if (out_file) {
out_file.write(reinterpret_cast<const char*>(file_data.data()), out_file.write(reinterpret_cast<const char*>(file_data.data()),
file_data.size()); file_data.size());
std::cout << "File saved: " << file_path << std::endl; // The uploader reopens this path. Close first so buffered data is
// visible to stat/libcurl; otherwise small files can upload as 0 bytes.
out_file.close();
if (!out_file) {
std::cerr << "Failed to write/close file: " << file_path << std::endl;
on_device_response_minimal(static_cast<int>(ResponseCode::INTERNAL_ERROR),
id, 0, static_cast<int>(DeviceState::READING_FILEDATA));
ClientManager::instance().change_device_state(id, DeviceState::IDLE);
return;
}
std::cout << "File saved: " << file_path
<< ", size=" << file_data.size() << " bytes" << std::endl;
//调试用 //调试用
// 若是 .cfg先查看并打印内容限长 // 若是 .cfg先查看并打印内容限长
@@ -1199,8 +1229,7 @@ void process_received_message(string mac, string id,const char* data, size_t len
//使用接口上送文件lnk20250826 //使用接口上送文件lnk20250826
std::string filename; std::string filename;
if(SendFileWebAuto(id, file_path, file_path, filename)){//如果是补招文件的下载则不会在这边上送 if(SendFileWebAuto(id, file_path, file_path, filename)){//如果是补招文件的下载则不会在这边上送
std::cout << "File upload success: " << filename << std::endl;
//通知文件上传 //通知文件上传
on_device_response_minimal(static_cast<int>(ResponseCode::OK), id, 0, static_cast<int>(DeviceState::READING_FILEDATA)); on_device_response_minimal(static_cast<int>(ResponseCode::OK), id, 0, static_cast<int>(DeviceState::READING_FILEDATA));
} }
@@ -2449,12 +2478,15 @@ void process_received_message(string mac, string id,const char* data, size_t len
<< ", 时间戳: " << record.triggerTimeMs << "ms" << std::endl; << ", 时间戳: " << record.triggerTimeMs << "ms" << std::endl;
//记录补招上来的暂态事件 //记录补招上来的暂态事件
append_qvvr_event(id,event.head.name, if(record.nType != 0){
record.nType,record.fPersisstime,record.fMagntitude,record.triggerTimeMs,record.phase); append_qvvr_event(id,event.head.name,
record.nType,record.fPersisstime,record.fMagntitude,record.triggerTimeMs,record.phase);
//直接发走暂态事件 //直接发走暂态事件
transfer_json_qvvr_data(id,event.head.name, /*transfer_json_qvvr_data(id,event.head.name,
record.fMagntitude,record.fPersisstime,record.triggerTimeMs,record.nType,record.phase,""); record.fMagntitude,record.fPersisstime,record.triggerTimeMs,record.nType,record.phase,"");*/
enqueue_qvvr_json_update(id, event.head.name, record.fMagntitude, record.fPersisstime, record.triggerTimeMs, record.nType, record.phase, "");
}
//通知状态机补招暂态事件成功 //通知状态机补招暂态事件成功
on_device_response_minimal(static_cast<int>(ResponseCode::OK), id, 0, static_cast<int>(DeviceState::READING_EVENTLOG)); on_device_response_minimal(static_cast<int>(ResponseCode::OK), id, 0, static_cast<int>(DeviceState::READING_EVENTLOG));

View File

@@ -29,6 +29,17 @@
#include "pqdif/include/pqdif_lg.h" #include "pqdif/include/pqdif_lg.h"
#include "pqdif_semantic_ids.h" #include "pqdif_semantic_ids.h"
#include "cloudfront/code/log4.h" //lnk20260526
extern void enqueue_stat_pq(const std::string& max_base64Str,
const std::string& min_base64Str,
const std::string& avg_base64Str,
const std::string& cp95_base64Str,
time_t data_time,
const std::string& mac,
short cid);
extern std::string extract_filename1(const std::string& path);
namespace fs = std::experimental::filesystem; namespace fs = std::experimental::filesystem;
namespace { namespace {
@@ -42,7 +53,7 @@ namespace {
// 而是按通道逐步聚合成时间桶并直接组装 Base64避免单文件中间对象占用过大内存。 // 而是按通道逐步聚合成时间桶并直接组装 Base64避免单文件中间对象占用过大内存。
constexpr size_t kPqdifLargeFileStreamingPointThreshold = 800000; constexpr size_t kPqdifLargeFileStreamingPointThreshold = 800000;
const char* kPqdRootDir = "download"; const char* kPqdRootDir = "download_pqdif";
const char* kDoneRootDir = "download_done"; const char* kDoneRootDir = "download_done";
const char* kFailRootDir = "download_fail"; const char* kFailRootDir = "download_fail";
@@ -8249,6 +8260,96 @@ void ClearReadyPqdifStatBase64Queue()
g_pqdif_stat_base64_ready_queue.clear(); g_pqdif_stat_base64_ready_queue.clear();
} }
static bool GetBase64ByKind(const PqdifStatBase64TimePointPacket& tp, //从序列中获取指定 kind 的 Base64 内容
StatValueKind kind,
std::string& out)
{
for (const auto& r : tp.records) {
if (r.value_kind == kind) {
out = r.base64_payload;
return !out.empty();
}
}
return false;
}
static bool extract_monitor_seq_from_local_pqdif_path(const std::string& path,
short& point_name)
{
point_name = 0;
std::cout << "[extract_monitor_seq] begin path="
<< path << std::endl;
// 取纯文件名,例如:
// download/192.168.1.10/M1_xxx.pqd
// -> M1_xxx.pqd
std::string fname = extract_filename1(path);
std::cout << "[extract_monitor_seq] filename="
<< fname << std::endl;
if (fname.size() < 3) {
std::cout << "[extract_monitor_seq] filename too short"
<< std::endl;
return false;
}
if (fname[0] != 'M' && fname[0] != 'm') {
std::cout << "[extract_monitor_seq] filename not start with M/m"
<< std::endl;
return false;
}
size_t pos = fname.find('_');
std::cout << "[extract_monitor_seq] underscore pos="
<< pos << std::endl;
if (pos == std::string::npos || pos <= 1) {
std::cout << "[extract_monitor_seq] invalid underscore position"
<< std::endl;
return false;
}
// M1_xxx -> 1
std::string seq_str = fname.substr(1, pos - 1);
std::cout << "[extract_monitor_seq] seq_str="
<< seq_str << std::endl;
for (char c : seq_str) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
std::cout << "[extract_monitor_seq] non-digit char="
<< c << std::endl;
return false;
}
}
try {
point_name = static_cast<short>(std::stoi(seq_str));
std::cout << "[extract_monitor_seq] success point_name="
<< point_name << std::endl;
return point_name > 0;
}
catch (const std::exception& e) {
std::cout << "[extract_monitor_seq] exception="
<< e.what() << std::endl;
point_name = 0;
return false;
}
catch (...) {
std::cout << "[extract_monitor_seq] unknown exception"
<< std::endl;
point_name = 0;
return false;
}
}
void RunPqdifScanLoop() void RunPqdifScanLoop()
{ {
std::cout << "[PQDIF] scan loop started, root=" << kPqdRootDir std::cout << "[PQDIF] scan loop started, root=" << kPqdRootDir
@@ -8282,6 +8383,54 @@ void RunPqdifScanLoop()
if (PopReadyPqdifStatBase64FileBatch(batch)) { if (PopReadyPqdifStatBase64FileBatch(batch)) {
// batch 就是一个 PQDIF 文件完整的 Base64 组装结果 // batch 就是一个 PQDIF 文件完整的 Base64 组装结果
// 在此处处理上送逻辑 // 在此处处理上送逻辑
const std::string& mac = batch.mac;
short point_name = 0;
if (!extract_monitor_seq_from_local_pqdif_path(batch.pqdif_file_path, point_name)) {
std::cout << "[PQDIF_UPLOAD] failed to extract monitor seq from file="
<< batch.pqdif_file_path << std::endl;
continue;
}
for (const auto& tp : batch.time_points) {
std::string max_base64;
std::string min_base64;
std::string avg_base64;
std::string p95_base64;
bool has_max = GetBase64ByKind(tp, StatValueKind::Max, max_base64);
bool has_min = GetBase64ByKind(tp, StatValueKind::Min, min_base64);
bool has_avg = GetBase64ByKind(tp, StatValueKind::Avg, avg_base64);
bool has_p95 = GetBase64ByKind(tp, StatValueKind::P95, p95_base64);
if (!has_max || !has_min || !has_avg || !has_p95) {
std::cout << "[PQDIF_UPLOAD] skip incomplete time point, file="
<< batch.pqdif_file_path
<< " time=" << tp.timestamp_text
<< " has_max=" << has_max
<< " has_min=" << has_min
<< " has_avg=" << has_avg
<< " has_p95=" << has_p95
<< std::endl;
continue;
}
enqueue_stat_pq(max_base64,
min_base64,
avg_base64,
p95_base64,
tp.timestamp,
mac,
point_name);
std::cout << "[PQDIF_UPLOAD] enqueue_stat_pq ok, file="
<< batch.pqdif_file_path
<< " time=" << tp.timestamp_text
<< " mac=" << mac
<< " point=" << point_name
<< std::endl;
}
} }
} }
catch (const std::exception& ex) catch (const std::exception& ex)