2026-07-03 19:21:24 +08:00

338 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 JiuruiWorkpiecePosePresenter TCP/Modbus communication methods
*
* TCP 协议(眼在手外):
* - 请求: D1 触发相机1检测无需机械臂位姿
* - 响应: count_X1_Y1_Z1_A1_B1_C1_... 返回全部 count 个目标
* (每个目标 6 个浮点XYZ + 姿态 A/B/C)
*
* Modbus 协议(默认端口 5020
* - 触发寄存器地址 0工作状态地址 1机器人位姿地址 2 (12 regs)
* - 结果起始地址 14 (12 regs = 6 floats: XYZ + 姿态 A/B/C)
*/
#include "JiuruiWorkpiecePosePresenter.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: XYZ + 姿态3分量)
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]);
}
}
} // namespace
// ============================================================================
// TCP 结果发送
// ============================================================================
void JiuruiWorkpiecePosePresenter::_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;
}
const int count = static_cast<int>(result.positions.size());
QString resultText = QString::number(count);
// 返回全部检测结果(每目标 6 个浮点XYZ + 姿态 A/B/C
for (int i = 0; i < count; ++i) {
const auto& pos = result.positions[i];
double angle1 = 0.0;
double angle2 = 0.0;
double angle3 = 0.0;
ReorderPoseAngles(pos.roll, pos.pitch, pos.yaw, poseOutputOrder, angle1, angle2, angle3);
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: count=%d, text=%s\n",
count, resultText.toStdString().c_str());
m_pTCPServer->SendResult(resultText);
}
// ============================================================================
// Modbus 结果发布
// ============================================================================
void JiuruiWorkpiecePosePresenter::_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, hasPose=%d\n", hasPose ? 1 : 0);
_UpdateModbusWorkStatus(result.success ? 2 : 3);
}
void JiuruiWorkpiecePosePresenter::_ResetModbusResultRegisters()
{
if (!IsModbusServerRunning()) {
return;
}
std::array<uint16_t, kModbusResultRegisterCount> zeros{};
WriteModbusRegisters(kModbusResultStartAddress, zeros.data(), kModbusResultRegisterCount);
}
void JiuruiWorkpiecePosePresenter::_UpdateModbusWorkStatus(uint16_t statusValue)
{
if (!IsModbusServerRunning()) {
m_modbusWorkStatus = statusValue;
return;
}
m_modbusWorkStatus = statusValue;
WriteModbusRegisters(kModbusWorkStatusAddress, &statusValue, 1);
}
void JiuruiWorkpiecePosePresenter::_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 JiuruiWorkpiecePosePresenter::_ReadRobotPoseFromModbus()
{
RobotPose6D pose;
auto regToFloat = [this](int offset) -> float {
uint32_t raw = (static_cast<uint32_t>(m_modbusRobotPoseRegs[offset]) << 16)
| static_cast<uint32_t>(m_modbusRobotPoseRegs[offset + 1]);
float val = 0;
std::memcpy(&val, &raw, sizeof(val));
return val;
};
pose.x = regToFloat(0);
pose.y = regToFloat(2);
pose.z = regToFloat(4);
pose.a = regToFloat(6);
pose.b = regToFloat(8);
pose.c = regToFloat(10);
return pose;
}
// ============================================================================
// TCP Server 生命周期
// ============================================================================
int JiuruiWorkpiecePosePresenter::InitTCPServer()
{
if (m_pTCPServer) {
LOG_WARNING("TCP server already initialized\n");
return 0;
}
m_pTCPServer = new JiuruiWorkpiecePoseTCPProtocol();
// 把 TCP 协议对象移到主线程,确保信号能通过事件循环正确投递
if (QApplication::instance()) {
m_pTCPServer->moveToThread(QApplication::instance()->thread());
}
// 连接 TCP 连接状态变化信号
// 注意:必须传入 this (JiuruiWorkpiecePosePresenter) 作为 context 对象,
// 因为 InitTCPServer() 在工作线程中调用,而 Presenter 在主线程创建,
// 不传 context 会导致 Qt 默认使用工作线程作为 receiver 线程,
// 信号在主线程 emit 后被 QueuedConnection 投递到工作线程(无事件循环),永远不执行。
QObject::connect(m_pTCPServer, &JiuruiWorkpiecePoseTCPProtocol::ConnectionChanged,
this, [this](bool connected) {
OnTCPConnectionChanged(connected);
});
// 连接检测触发信号
// 眼在手外模式TCP 命令 D1 为触发相机1检测
QObject::connect(m_pTCPServer, &JiuruiWorkpiecePoseTCPProtocol::DetectionTriggered,
this, [this](int cameraIndex, const RobotPose6D& robotPose) {
LOG_DEBUG("TCP triggered detection, cameraIndex: %d\n", cameraIndex);
TriggerDetection(cameraIndex, 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 (JiuruiWorkpiecePose protocol: D1/R1 commands)\n", port);
return 0;
}
void JiuruiWorkpiecePosePresenter::stopServer()
{
if (m_pTCPServer) {
m_pTCPServer->StopServer();
delete m_pTCPServer;
m_pTCPServer = nullptr;
m_bTCPConnected = false;
LOG_DEBUG("TCP server stopped\n");
}
}
void JiuruiWorkpiecePosePresenter::OnTCPConnectionChanged(bool connected)
{
m_bTCPConnected = connected;
LOG_DEBUG("TCP connection changed: %s\n", connected ? "connected" : "disconnected");
if (auto pStatus = GetStatusCallback<IYJiuruiWorkpiecePoseStatus>()) {
pStatus->OnRobotConnectionChanged(connected);
pStatus->OnStatusUpdate(connected ? "TCP 客户端已连接" : "TCP 客户端已断开");
}
CheckAndUpdateWorkStatus();
}
// ============================================================================
// 对外发送接口
// ============================================================================
void JiuruiWorkpiecePosePresenter::SendDetectionResultToClient(const DetectionResult& result)
{
_SendDetectionResultToTCP(result, result.cameraIndex);
}