// tcp_server.cpp - TCP 服务端(新版统一扫描协议) // // 消息以 '>' 结尾(不是换行)。每条命令是一个 JSON: // {"function":"count|check|config", // "scan":"start|stop", // "scanTime":N, // >0: 到时自动停止,无需再发 stop(单位:秒) // "box":{"l":..,"w":..,"h":..,"maxPerLayer":..}, // "task_id":".."} // 语义: // - function=count/check + scan=start:启动统一后台扫描;若扫描已在运行,则 // 强制重启(旧扫描结果丢弃);scanTime>0 时到点自动停止并返回结果。 // - function=count/check + scan=stop:立即停止当前扫描并返回结果。 // - function=config:重新加载配置文件。 // count/check 共用同一个 ScanSession,同一时刻只有一个采集线程访问雷达。 #include "tcp_server.h" #include "config.h" #include "json_utils.h" #include "lidar_utils.h" #include "count.h" #include "detect.h" #include "io_utils.h" #include #include #include #include #include #include #include #include #include #include namespace { // ==================== 会话状态(全局单雷达) ==================== struct SessionState { bool active = false; // 是否有扫描在运行 bool auto_stop = false; // 是否到时自动停止 std::string function; // "count" / "check" CountParams box; // count 的 box 参数 std::string task_id; SOCKET owner = INVALID_SOCKET; // 发起者(自动停止结果回发) std::chrono::steady_clock::time_point deadline; // 自动停止时刻 }; static ScanSession g_scan; // 统一采集会话 static pavo2::pavo2_driver* g_driver = nullptr; static SessionState g_state; static std::mutex g_life; // 会话生命周期锁 static std::mutex g_cv_mtx; // 自动停止等待锁 static std::condition_variable g_cv; static bool g_armed = false; // 是否有到点任务 static std::chrono::steady_clock::time_point g_deadline; // ==================== 发送 ==================== // 响应统一以 '>' 结尾 static void send_text(SOCKET s, const std::string& msg) { std::string out = msg; if (!out.empty() && out.back() != '>') out += '>'; send(s, out.c_str(), (int)out.size(), 0); } // ==================== 停止并取回结果 ==================== // need_auto=true 表示"自动到时"触发,只允许在 auto_stop 仍武装时抢到; // 否则(手动 stop)只要 active 即可。 static bool claim_stop_and_fetch(bool need_auto, SessionState* snap, std::vector* pts) { std::lock_guard lk(g_life); if (!g_state.active) return false; if (need_auto && !g_state.auto_stop) return false; // 已被手动 stop 抢先 *snap = g_state; g_state.active = false; g_state.auto_stop = false; { std::lock_guard ck(g_cv_mtx); g_armed = false; } g_cv.notify_all(); g_scan.stop(); // 等待采集线程退出(≤100ms轮询粒度) *pts = g_scan.snapshot(); g_scan.reset(); // 清空点集,为下一次 start 准备 return true; } // 仅停止当前扫描,不返回结果(用于 start 时的强制重启)。 // 调用方需已持有 g_life。 static bool discard_current_locked() { if (!g_state.active) return false; g_state.active = false; g_state.auto_stop = false; { std::lock_guard ck(g_cv_mtx); g_armed = false; } g_cv.notify_all(); g_scan.stop(); g_scan.reset(); return true; } // ==================== 结果计算与回包 ==================== static void finish_count(const SessionState& snap, const std::vector& pts, SOCKET target) { bool valid_box = snap.box.l > 0 && snap.box.w > 0 && snap.box.h > 0 && snap.box.maxPerLayer > 0; std::vector work = pts; std::string detail; std::vector top_img; int total = 0, tw = 0, th = 0, tlv = 0; double tarea = 0.0; if (!pts.empty() && valid_box) { z_gap_interpolate(work); // z空隙插值:只在停止后执行一次 total = calc_item_count(work, snap.box, &detail, &top_img, &tw, &th, &tlv, &tarea); } std::cout << "[TCP] count结束: task_id=" << snap.task_id << ", 点数=" << pts.size() << ", 结果=" << total << std::endl; std::ostringstream r; r << "{\"code\":200,\"message\":\"count done\",\"count\":" << total; if (!snap.task_id.empty()) r << ",\"task_id\":\"" << snap.task_id << "\""; r << "}"; send_text(target, r.str()); // 后台保存 PCD/图片/日志(不阻塞响应) if (!pts.empty() && valid_box) save_count_files_async(work, top_img, tw, th, snap.box, detail, total, tlv, tarea); } static void finish_check(const SessionState& snap, const std::vector& pts, SOCKET target) { // 输出目录 data/日期/时间_taskId(由 io_utils 统一创建) std::string out_dir = make_task_dir(snap.task_id); DetectResult res; detect_analyze_slices(pts, out_dir, &res); std::ostringstream r; r << "{\"code\":200,\"message\":\"detect done\""; if (res.valid) { if (res.start_fill_pct >= 0.0) r << ",\"start\":{\"fill_pct\":" << std::fixed << std::setprecision(2) << res.start_fill_pct << ",\"y_max\":" << std::setprecision(1) << res.start_y_max << ",\"y_median\":" << res.start_y_med << "}"; if (res.end_fill_pct >= 0.0) r << ",\"end\":{\"fill_pct\":" << std::fixed << std::setprecision(2) << res.end_fill_pct << ",\"y_max\":" << std::setprecision(1) << res.end_y_max << ",\"y_median\":" << res.end_y_med << "}"; } if (!snap.task_id.empty()) r << ",\"task_id\":\"" << snap.task_id << "\""; r << "}"; send_text(target, r.str()); std::cout << "[TCP] check结束: task_id=" << snap.task_id << ", 点数=" << pts.size() << (res.valid ? ", 检测有效" : ", 检测无效") << std::endl; } static void finish_and_reply(const SessionState& snap, const std::vector& pts, SOCKET target) { if (snap.function == "count") finish_count(snap, pts, target); else if (snap.function == "check") finish_check(snap, pts, target); } // ==================== 自动到时停止线程 ==================== static void auto_stop_loop() { for (;;) { std::unique_lock lk(g_cv_mtx); if (!g_armed) { g_cv.wait(lk); continue; } auto deadline = g_deadline; if (g_cv.wait_until(lk, deadline) != std::cv_status::timeout) continue; // 被通知:可能已取消/重设,回到顶部 g_armed = false; // 到点 lk.unlock(); SessionState snap; std::vector pts; if (claim_stop_and_fetch(true, &snap, &pts)) { std::cout << "[TCP] 扫描到时自动停止: function=" << snap.function << ", task_id=" << snap.task_id << std::endl; finish_and_reply(snap, pts, snap.owner); } } } // ==================== 命令处理 ==================== static void handle_scan_start(const std::string& msg, SOCKET client) { std::string fn; json_get_string(msg, "function", fn); std::string task_id; json_get_string(msg, "task_id", task_id); double scan_time = 0.0; json_get_number(msg, "scanTime", scan_time); CountParams box; json_get_string(msg, "task_id", box.task_id); json_get_number(msg, "l", box.l); json_get_number(msg, "w", box.w); json_get_number(msg, "h", box.h); double d = 0.0; if (json_get_number(msg, "maxPerLayer", d) || json_get_number(msg, "max", d)) box.maxPerLayer = (int)d; std::ostringstream ack; ack << "{\"code\":200,\"message\":\"scan start\",\"function\":\"" << fn << "\""; if (!task_id.empty()) ack << ",\"task_id\":\"" << task_id << "\""; if (scan_time > 0) ack << ",\"scanTime\":" << (int)scan_time; ack << "}"; bool started = false; std::string fail; { std::lock_guard lk(g_life); // 扫描运行中收到新的 start:强制重启(旧扫描结果丢弃) if (discard_current_locked()) std::cout << "[TCP] 扫描运行中被新命令强制重启: function=" << fn << ", task_id=" << task_id << std::endl; g_scan.reset(); if (!g_scan.start(*g_driver)) { fail = "scan start failed"; } else { SessionState ns; ns.active = true; ns.function = fn; ns.box = box; ns.task_id = task_id; ns.owner = client; if (scan_time > 0) { ns.auto_stop = true; ns.deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds((long)(scan_time * 1000.0)); } g_state = ns; if (ns.auto_stop) { std::lock_guard ck(g_cv_mtx); g_armed = true; g_deadline = ns.deadline; } g_cv.notify_all(); started = true; } } if (!started) { std::cerr << "[TCP] " << fail << std::endl; send_text(client, "{\"code\":400,\"message\":\"" + fail + "\"}"); return; } std::cout << "[TCP] 扫描开始: function=" << fn << ", task_id=" << task_id << (scan_time > 0 ? ", 到时自动停止" : ", 等待stop") << std::endl; send_text(client, ack.str()); } static void handle_scan_stop(const std::string& msg, SOCKET client) { std::string fn; json_get_string(msg, "function", fn); SessionState snap; std::vector pts; if (!claim_stop_and_fetch(false, &snap, &pts)) { // 未在扫描(从未 start / 已停止 / 自动到时已完成) std::ostringstream r; r << "{\"code\":200,\"message\":\"scan not running\""; if (fn == "count") r << ",\"count\":0"; r << "}"; send_text(client, r.str()); return; } std::cout << "[TCP] 收到stop: function=" << snap.function << ", task_id=" << snap.task_id << std::endl; finish_and_reply(snap, pts, client); } static void handle_config(const std::string& msg, SOCKET client) { (void)msg; std::cout << "[TCP] 收到config命令,重新读取 config/config ..." << std::endl; load_config(); std::cout << "[TCP] 配置已重载: 采集=" << Collect_Duration_S << "s, 速度=" << Speed_Z << "cm/s, 插值=" << Z_Interpolate_Mode << ", 保留=" << Log_Retain_Days << "天, 端口=" << Tcp_Port << std::endl; send_text(client, "{\"code\":200,\"message\":\"config reloaded\"}"); } static void dispatch(const std::string& raw, SOCKET client) { std::string msg = json_trim(raw); if (msg.empty()) return; std::string fn; if (!json_get_string(msg, "function", fn)) return; // 未知消息忽略 if (fn == "config") { handle_config(msg, client); return; } if (fn != "count" && fn != "check") return; // 未知功能忽略 std::string scan_cmd; json_get_string(msg, "scan", scan_cmd); if (scan_cmd == "stop") handle_scan_stop(msg, client); else if (scan_cmd == "start") handle_scan_start(msg, client); // 其余(无scan/未知scan值)忽略 } static void handle_client(SOCKET client) { char buf[4096]; std::string rbuf; while (true) { int n = recv(client, buf, sizeof(buf) - 1, 0); if (n <= 0) break; // 连接关闭或出错 buf[n] = '\0'; rbuf.append(buf, n); // 按 '>' 分帧处理(一条消息以 '>' 结尾) size_t pos; while ((pos = rbuf.find('>')) != std::string::npos) { std::string raw = rbuf.substr(0, pos); rbuf.erase(0, pos + 1); dispatch(raw, client); } } // 客户端断开:若它是某自动扫描的 owner,扫描仍会到时自动停止; // 未设置 scanTime 的扫描则持续到下一次 start 强制重启或 stop。 closesocket(client); } } // namespace void run_tcp_server(pavo2::pavo2_driver& driver) { g_driver = &driver; std::cout << "\n========== TCP服务端模式 ==========" << std::endl; std::cout << "[TCP] 监听端口: " << Tcp_Port << " | 消息以 '>' 结尾" << std::endl; SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0); if (listen_sock == INVALID_SOCKET) { std::cerr << "[错误] 创建监听socket失败!" << std::endl; return; } sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(Tcp_Port); if (bind(listen_sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { std::cerr << "[错误] 绑定端口 " << Tcp_Port << " 失败!" << std::endl; closesocket(listen_sock); return; } if (listen(listen_sock, 5) == SOCKET_ERROR) { std::cerr << "[错误] 监听失败!" << std::endl; closesocket(listen_sock); return; } // 自动到时停止线程(scanTime 模式) std::thread(auto_stop_loop).detach(); std::cout << "[TCP] 初始化完成,等待命令 " "{\"function\":\"count\",\"scan\":\"start\",...} ..." << std::endl; while (true) { SOCKET client = accept(listen_sock, nullptr, nullptr); if (client == INVALID_SOCKET) { std::cout << "[TCP] accept失败,重试..." << std::endl; continue; } std::thread(handle_client, client).detach(); } }