You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

181 lines
6.7 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// main.cpp - 程序入口(模块化重构版)
// 模块: config / json_utils / filter / io_utils / lidar_utils / count / detect / tcp_server
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <ctime>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#endif
#include "config.h"
#include "common.h"
#include "io_utils.h"
#include "lidar_utils.h"
#include "detect.h"
#include "tcp_server.h"
// ==================== 日志目录清理:启动时 + 每天 log.clean_time ====================
static std::atomic<bool> g_cleanup_stop{ false };
static std::mutex g_cleanup_mtx;
static std::condition_variable g_cleanup_cv;
static std::thread g_cleanup_thread;
// 启动清理线程:等下一个 01:00 执行一次清理后循环;等待可被 stop_daily_cleanup() 中断
static void start_daily_cleanup()
{
clean_expired_log_dirs(); // 启动清理一次
g_cleanup_stop = false;
g_cleanup_thread = std::thread([]() {
for (;;)
{
// 清理时刻来自配置(cfg.log.clean_hour/minute/sec),每轮读取:运行期发 config
// 命令重载后,下一轮即按新时刻执行
int hour = cfg.log.clean_hour, minute = cfg.log.clean_minute, sec = cfg.log.clean_sec;
time_t raw = std::time(nullptr);
struct tm* t = std::localtime(&raw);
std::tm next = *t;
next.tm_hour = hour;
next.tm_min = minute;
next.tm_sec = sec;
time_t next_t = std::mktime(&next);
if (next_t <= raw) next_t += 86400;
char buf[32];
std::snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, sec);
std::cout << "[清理] 下次定时清理: 每天 " << buf << " (等待 "
<< (next_t - raw) / 60 << " 分钟)" << std::endl;
// 可中断等待:wait_for 返回 true 表示 stop 标志已置位(程序退出)
std::unique_lock<std::mutex> lk(g_cleanup_mtx);
if (g_cleanup_cv.wait_for(lk, std::chrono::seconds(next_t - raw),
[] { return g_cleanup_stop.load(); }))
return; // 程序正在退出
lk.unlock();
clean_expired_log_dirs();
}
});
}
// 主程序退出前调用:唤醒并等待清理线程结束,
// 避免进程析构时后台线程仍在访问全局对象/标准输出导致崩溃(原 detach 版的崩退根因)
static void stop_daily_cleanup()
{
g_cleanup_stop = true;
g_cleanup_cv.notify_all();
if (g_cleanup_thread.joinable()) g_cleanup_thread.join();
}
// ==================== exe 模式:自动做一次截面检测 ====================
// 启动统一后台扫描,采 cfg.scan.collect_duration_s 秒后自动停止,做截面检测并保存到
// data/日期/时间_ 目录(无 task_id)。
static int run_exe_check(pavo2::pavo2_driver& driver)
{
std::cout << "\n[exe] 自动检测开始: 采集 " << cfg.scan.collect_duration_s
<< " 秒 ..." << std::endl;
ScanSession scan;
scan.reset();
if (!scan.start(driver))
{
std::cerr << "[错误] 启动扫描会话失败!" << std::endl;
return -1;
}
std::this_thread::sleep_for(std::chrono::seconds(cfg.scan.collect_duration_s));
scan.stop();
std::vector<CountPoint> pts = scan.snapshot();
scan.reset();
std::cout << "[exe] 采集结束, 点数=" << pts.size() << std::endl;
std::string out_dir = make_task_dir("");
DetectResult res;
detect_analyze_slices(pts, out_dir, &res);
std::cout << "[exe] 检测完成: ";
if (res.valid)
{
std::cout << "start白色占比=" << res.start_fill_pct
<< "%, end白色占比=" << res.end_fill_pct << "%";
}
else
{
std::cout << "无有效结果";
}
std::cout << ", 输出目录=" << out_dir << std::endl;
return 0;
}
// ==================== 主入口 ====================
int main()
{
#ifdef _WIN32
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
std::cerr << "[错误] WSAStartup 失败!" << std::endl;
return -1;
}
#endif
load_config(); // 默认读取 ./config/config (JSON)
reload_speed_profile(); // cfg.scan.z_speed_type=configuration 时加载 config/speed.txt
start_daily_cleanup();
std::cout << "==================== 配置文件内容 ====================" << std::endl;
std::cout << "雷达IP: " << cfg.radar.ip << " : " << cfg.radar.port << std::endl;
std::cout << "运行模式: " << cfg.server.mode
<< " (tcp=TCP服务端, http=HTTP服务端, 其他=自动检测)" << std::endl;
std::cout << "TCP/HTTP端口: " << cfg.server.port << std::endl;
std::cout << "采集时长: " << cfg.scan.collect_duration_s << " 秒" << std::endl;
std::cout << "Z轴模式: " << cfg.scan.z_speed_type
<< " (uniformSpeed=匀速, configuration=config/speed.txt变速)" << std::endl;
std::cout << "移动速度: " << cfg.scan.z_speed << " cm/s (0=时间模式, >0=距离模式)"
<< std::endl;
std::cout << "Z范围: " << cfg.detect.z_range_mm << " mm | Y范围: " << cfg.detect.y_range_mm
<< " mm | 雷达频率: " << cfg.radar.hz << " Hz" << std::endl;
std::cout << "z空隙插值模式: " << cfg.count.z_interpolate_mode << std::endl;
std::cout << "日志保留天数: " << cfg.log.retain_days << std::endl;
std::cout << "======================================================" << std::endl;
std::cout << "======================================================" << std::endl;
std::cout << " Pavo2 Lidar 雷达数据采集程序 (模块化)" << std::endl;
std::cout << "======================================================" << std::endl;
pavo2::pavo2_driver driver;
if (!connect_lidar(driver))
{
stop_daily_cleanup(); // 提前退出也要先停掉后台清理线程
#ifdef _WIN32
WSACleanup();
#endif
return -1;
}
std::cout << "\n[等待] 等待雷达数据稳定(2秒)..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
int rc = 0;
if (cfg.server.mode == "tcp")
run_tcp_server(driver);
else if (cfg.server.mode == "http")
run_http_server(driver);
else
rc = run_exe_check(driver);
stop_daily_cleanup(); // 通知后台清理线程退出并等待结束,防止崩退
std::cout << "\n[关闭] 正在断开雷达连接..." << std::endl;
driver.pavo2_close();
#ifdef _WIN32
WSACleanup();
#endif
std::cout << "[完成] 程序运行结束。" << std::endl;
return rc;
}