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.

424 lines
16 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.

// 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 <winsock2.h>
#include <ws2tcpip.h>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <mutex>
#include <sstream>
#include <thread>
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<CountPoint>* pts)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<CountPoint>& pts,
SOCKET target)
{
bool valid_box = snap.box.l > 0 && snap.box.w > 0 &&
snap.box.h > 0 && snap.box.maxPerLayer > 0;
std::vector<CountPoint> work = pts;
std::string detail;
std::vector<uint8_t> 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<CountPoint>& 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<CountPoint>& 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<std::mutex> 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<CountPoint> 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<std::mutex> 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<std::mutex> 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<CountPoint> 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();
}
}