345 lines
11 KiB
C++
Raw Normal View History

/**
* @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);
}