345 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file TCPServerMethods.cpp
* @brief DiscHolePosePresenter TCP/Modbus communication methods
*
* TCP 协议:
* - 请求: D1_X_Y_Z_A_B_C 表示 disc hole 检测
* - 请求: R1_X_Y_Z_A_B_C 表示 disc rack 检测
* - 响应: count_X_Y_Z_A_B_C
* (6 个浮点:目标点 XYZ + 姿态 A/B/C)
*
* Modbus 协议(默认端口 5020
* - 触发寄存器地址 0工作状态地址 1机器人位姿地址 2 (12 regs)
* - 结果起始地址 14 (12 regs = 6 floats)
*/
#include "DiscHolePosePresenter.h"
#include <QApplication>
#include "DetectionOutputConverter.h"
#include "VrLog.h"
#include <array>
#include <cstdio>
#include <cstring>
namespace {
constexpr uint16_t kModbusTriggerAddress = 0;
constexpr uint16_t kModbusWorkStatusAddress = 1;
constexpr uint16_t kModbusRobotPoseAddress = 2;
constexpr uint16_t kModbusRobotPoseRegisterCount = 12;
constexpr uint16_t kModbusResultStartAddress = 14;
// 结果寄存器12 = 6 floats * 2 regs目标点(6 floats)
constexpr uint16_t kModbusResultRegisterCount = 12;
constexpr uint16_t kDefaultModbusPort = 5020;
constexpr int kPoseOutputRxRyRz = 0;
constexpr int kPoseOutputRxRzRy = 1;
constexpr int kPoseOutputRyRxRz = 2;
constexpr int kPoseOutputRyRzRx = 3;
constexpr int kPoseOutputRzRxRy = 4;
constexpr int kPoseOutputRzRyRx = 5;
void FloatToRegisters(float value, uint16_t& high, uint16_t& low)
{
uint32_t raw = 0;
static_assert(sizeof(raw) == sizeof(value), "float size mismatch");
std::memcpy(&raw, &value, sizeof(value));
high = static_cast<uint16_t>((raw >> 16) & 0xFFFF);
low = static_cast<uint16_t>(raw & 0xFFFF);
}
void ReorderPoseAngles(double roll,
double pitch,
double yaw,
int poseOutputOrder,
double& angle1,
double& angle2,
double& angle3)
{
switch (poseOutputOrder) {
case kPoseOutputRxRzRy:
angle1 = roll;
angle2 = yaw;
angle3 = pitch;
break;
case kPoseOutputRyRxRz:
angle1 = pitch;
angle2 = roll;
angle3 = yaw;
break;
case kPoseOutputRyRzRx:
angle1 = pitch;
angle2 = yaw;
angle3 = roll;
break;
case kPoseOutputRzRxRy:
angle1 = yaw;
angle2 = roll;
angle3 = pitch;
break;
case kPoseOutputRzRyRx:
angle1 = yaw;
angle2 = pitch;
angle3 = roll;
break;
case kPoseOutputRxRyRz:
default:
angle1 = roll;
angle2 = pitch;
angle3 = yaw;
break;
}
}
template<typename PoseT>
void FillPoseRegisters(const PoseT& pose,
int poseOutputOrder,
std::array<uint16_t, kModbusResultRegisterCount>& registers)
{
double angle1 = 0.0;
double angle2 = 0.0;
double angle3 = 0.0;
ReorderPoseAngles(pose.roll, pose.pitch, pose.yaw, poseOutputOrder, angle1, angle2, angle3);
// 目标点6 floats
const float values[6] = {
static_cast<float>(pose.x),
static_cast<float>(pose.y),
static_cast<float>(pose.z),
static_cast<float>(angle1),
static_cast<float>(angle2),
static_cast<float>(angle3)
};
for (size_t i = 0; i < 6; ++i) {
FloatToRegisters(values[i], registers[i * 2], registers[i * 2 + 1]);
}
}
// 检测类型对应的中文标签
const char* DetectionTypeLabel(DetectionType type)
{
switch (type) {
case DETECTION_TYPE_DISC_HOLE:
return "盘孔";
case DETECTION_TYPE_DISC_RACK:
return "架子";
default:
return "未知";
}
}
} // namespace
// ============================================================================
// TCP 结果发送
// ============================================================================
void DiscHolePosePresenter::_SendDetectionResultToTCP(const DetectionResult& result, int cameraIndex)
{
if (!m_pTCPServer || !m_pTCPServer->IsConnected()) {
LOG_WARNING("TCP not connected, skip sending result\n");
return;
}
(void)cameraIndex;
int poseOutputOrder = 0;
if (m_pConfigManager) {
poseOutputOrder = m_pConfigManager->GetConfigResult().poseOutputOrder;
}
int count = static_cast<int>(result.positions.size());
QString resultText = QString::number(count);
// 每次只发送一个目标,取第一个
if (!result.positions.empty()) {
const auto& pos = result.positions.front();
double angle1 = 0.0;
double angle2 = 0.0;
double angle3 = 0.0;
ReorderPoseAngles(pos.roll, pos.pitch, pos.yaw, poseOutputOrder, angle1, angle2, angle3);
// 输出 6 个浮点:目标点(X/Y/Z + A/B/C)
// 使用 QString::arg 格式化
resultText += QString("_%1_%2_%3_%4_%5_%6")
.arg(pos.x, 0, 'f', 3)
.arg(pos.y, 0, 'f', 3)
.arg(pos.z, 0, 'f', 3)
.arg(angle1, 0, 'f', 3)
.arg(angle2, 0, 'f', 3)
.arg(angle3, 0, 'f', 3);
}
LOG_INFO("TCP result text (%s): %s\n",
DetectionTypeLabel(m_currentDetectionType),
resultText.toStdString().c_str());
m_pTCPServer->SendResult(resultText);
}
// ============================================================================
// Modbus 结果发布
// ============================================================================
void DiscHolePosePresenter::_PublishDetectionResultToModbus(const DetectionResult& result)
{
if (!IsModbusServerRunning()) {
return;
}
std::array<uint16_t, kModbusResultRegisterCount> registers{};
bool hasPose = false;
int poseOutputOrder = 0;
if (m_pConfigManager) {
poseOutputOrder = m_pConfigManager->GetConfigResult().poseOutputOrder;
}
if (!result.positions.empty()) {
FillPoseRegisters(result.positions.front(), poseOutputOrder, registers);
hasPose = true;
}
WriteModbusRegisters(kModbusResultStartAddress, registers.data(), kModbusResultRegisterCount);
LOG_DEBUG("Published Modbus pose registers (%s), hasPose=%d\n",
DetectionTypeLabel(m_currentDetectionType), hasPose ? 1 : 0);
_UpdateModbusWorkStatus(result.success ? 2 : 3);
}
void DiscHolePosePresenter::_ResetModbusResultRegisters()
{
if (!IsModbusServerRunning()) {
return;
}
std::array<uint16_t, kModbusResultRegisterCount> zeros{};
WriteModbusRegisters(kModbusResultStartAddress, zeros.data(), kModbusResultRegisterCount);
}
void DiscHolePosePresenter::_UpdateModbusWorkStatus(uint16_t statusValue)
{
if (!IsModbusServerRunning()) {
m_modbusWorkStatus = statusValue;
return;
}
m_modbusWorkStatus = statusValue;
WriteModbusRegisters(kModbusWorkStatusAddress, &statusValue, 1);
}
void DiscHolePosePresenter::_InitializeModbusRegisters()
{
if (m_modbusRegistersInitialized || !IsModbusServerRunning()) {
return;
}
const uint16_t zero = 0;
WriteModbusRegisters(kModbusTriggerAddress, &zero, 1);
std::array<uint16_t, kModbusRobotPoseRegisterCount> robotZeros{};
WriteModbusRegisters(kModbusRobotPoseAddress, robotZeros.data(), kModbusRobotPoseRegisterCount);
_ResetModbusResultRegisters();
_UpdateModbusWorkStatus(0);
m_modbusRegistersInitialized = true;
}
RobotPose6D DiscHolePosePresenter::_ReadRobotPoseFromModbus()
{
RobotPose6D pose;
return pose;
}
// ============================================================================
// TCP Server 生命周期
// ============================================================================
int DiscHolePosePresenter::InitTCPServer()
{
if (m_pTCPServer) {
LOG_WARNING("TCP server already initialized\n");
return 0;
}
m_pTCPServer = new DiscHolePoseTCPProtocol();
// 把 TCP 协议对象移到主线程,确保信号能通过事件循环正确投递
if (QApplication::instance()) {
m_pTCPServer->moveToThread(QApplication::instance()->thread());
}
// 连接 TCP 连接状态变化信号
// 注意:必须传入 this (DiscHolePosePresenter) 作为 context 对象,
// 因为 InitTCPServer() 在工作线程中调用,而 Presenter 在主线程创建,
// 不传 context 会导致 Qt 默认使用工作线程作为 receiver 线程,
// 信号在主线程 emit 后被 QueuedConnection 投递到工作线程(无事件循环),永远不执行。
QObject::connect(m_pTCPServer, &DiscHolePoseTCPProtocol::ConnectionChanged,
this, [this](bool connected) {
OnTCPConnectionChanged(connected);
});
// 连接检测触发信号
// TCP 命令D1_X_Y_Z_A_B_C 为 disc holeR1_X_Y_Z_A_B_C 为 disc rack
QObject::connect(m_pTCPServer, &DiscHolePoseTCPProtocol::DetectionTriggered,
this, [this](int cameraIndex, DetectionType detectionType, const RobotPose6D& robotPose) {
LOG_DEBUG("TCP triggered detection, cameraIndex: %d, type: %d (%s)\n",
cameraIndex, static_cast<int>(detectionType),
DetectionTypeLabel(detectionType));
TriggerDetection(cameraIndex, detectionType, robotPose);
});
uint16_t port = 7800;
if (m_pConfigManager) {
const ConfigResult configResult = m_pConfigManager->GetConfigResult();
port = configResult.tcpPort;
}
if (!m_pTCPServer->StartServer(port)) {
LOG_ERROR("Failed to start TCP server on port %d\n", port);
delete m_pTCPServer;
m_pTCPServer = nullptr;
return -1;
}
LOG_DEBUG("TCP server started on port %d (DiscHolePose protocol: D1/R1 commands)\n", port);
return 0;
}
void DiscHolePosePresenter::stopServer()
{
if (m_pTCPServer) {
m_pTCPServer->StopServer();
delete m_pTCPServer;
m_pTCPServer = nullptr;
m_bTCPConnected = false;
LOG_DEBUG("TCP server stopped\n");
}
}
void DiscHolePosePresenter::OnTCPConnectionChanged(bool connected)
{
m_bTCPConnected = connected;
LOG_DEBUG("TCP connection changed: %s\n", connected ? "connected" : "disconnected");
if (auto pStatus = GetStatusCallback<IYDiscHolePoseStatus>()) {
pStatus->OnRobotConnectionChanged(connected);
pStatus->OnStatusUpdate(connected ? "TCP 客户端已连接" : "TCP 客户端已断开");
}
CheckAndUpdateWorkStatus();
}
// ============================================================================
// 对外发送接口
// ============================================================================
void DiscHolePosePresenter::SendDetectionResultToClient(const DetectionResult& result)
{
_SendDetectionResultToTCP(result, result.cameraIndex);
}