修改ModbusTCPServer的退出; 修改轮眉的协议

This commit is contained in:
yiyi 2026-01-02 17:42:24 +08:00
parent 12a1a0e738
commit 5dda95edb9
28 changed files with 410 additions and 166 deletions

View File

@ -25,6 +25,7 @@
| 18,19 | 下点X | R | FLOAT32 | 轮毂下点X坐标 (mm) |
| 20,21 | 下点Y | R | FLOAT32 | 轮毂下点Y坐标 (mm) |
| 22,23 | 下点Z | R | FLOAT32 | 轮毂下点Z坐标 (mm) |
| 24,25 | 到地面高度 | R | FLOAT32 | 轮眉到地面高度 (mm) |
## FLOAT32 格式
@ -50,5 +51,5 @@
```
1. 写寄存器0 = 设备序号 (1-4)
2. 读寄存器1 等待状态变为 4 (完成)
3. 读地址2-23获取检测结果
3. 读地址2-25获取检测结果
```

View File

@ -79,6 +79,11 @@
<CURVE IsShow="0" />
<VALUE Stategy="0" TimeBngrWay="0" Coef_a="0" Coef_b="0" Coef_c="0" Coef_d="0" Coef_e="0" ResetStep="0" XInvl="1000" MaxRadom="0" MinRadom="0" MaxValue="0" MinValue="0" />
</DATA>
<DATA Name="到地面高度" BLOCK="2" PrtcTYPE="2" Unit="mm" ShowType="0" Addr="24" Gain="1" Size="2" BitOffset="0" BitNum="32" Range="--" Color="" ByteOder="0" WordOder="0" IntervalTime="0" IsShow="1" IsBRead="1" IsBWrite="0">
<TIMEOUT Time="2000" ResendCount="0" />
<CURVE IsShow="0" />
<VALUE Stategy="0" TimeBngrWay="0" Coef_a="0" Coef_b="0" Coef_c="0" Coef_d="0" Coef_e="0" ResetStep="0" XInvl="1000" MaxRadom="0" MinRadom="0" MaxValue="0" MinValue="0" />
</DATA>
</DATA_LIST>
</DEVICE>
</DEVICE_LIST>

View File

@ -19,11 +19,11 @@ WheelMeasurePresenter::WheelMeasurePresenter(QObject* parent)
{
// 创建配置接口
IVrWheelMeasureConfig::CreateInstance(&m_config);
LOG_INFO("ALGO_VERSION: %s \n", wd_wheelArchHeigthMeasureVersion());
}
WheelMeasurePresenter::~WheelMeasurePresenter()
{
// 释放配置接口
if (m_config) {
delete m_config;
m_config = nullptr;
@ -79,23 +79,27 @@ void WheelMeasurePresenter::OnCameraStatusChanged(int cameraIndex, bool isConnec
{
LOG_INFO("Camera[%d] status changed: %s\n", cameraIndex, isConnected ? "connected" : "disconnected");
if (m_statusUpdate) {
// 从配置中获取相机名称cameraIndex从1开始
QString cameraName;
int enabledIndex = 0;
for (const auto& cameraConfig : m_configResult.cameras) {
if (cameraConfig.enabled) {
enabledIndex++;
if (enabledIndex == cameraIndex) {
cameraName = QString::fromStdString(cameraConfig.name);
break;
}
if (!m_statusUpdate) return;
// 从配置中获取相机名称cameraIndex从1开始
QString cameraName;
int enabledIndex = 0;
for (const auto& cameraConfig : m_configResult.cameras) {
if (cameraConfig.enabled) {
enabledIndex++;
if (enabledIndex == cameraIndex) {
cameraName = QString::fromStdString(cameraConfig.name);
break;
}
}
if (cameraName.isEmpty()) {
cameraName = QString("Camera%1").arg(cameraIndex);
}
}
if (cameraName.isEmpty()) {
cameraName = QString("Camera%1").arg(cameraIndex);
}
// 切换到主线程更新UI
QMetaObject::invokeMethod(this, [this, cameraName, isConnected]() {
if (!m_statusUpdate) return;
if (isConnected) {
m_statusUpdate->OnCameraConnected(cameraName);
m_statusUpdate->OnDeviceStatusChanged(cameraName, static_cast<int>(DeviceStatus::Online));
@ -103,13 +107,12 @@ void WheelMeasurePresenter::OnCameraStatusChanged(int cameraIndex, bool isConnec
m_statusUpdate->OnCameraDisconnected(cameraName);
m_statusUpdate->OnDeviceStatusChanged(cameraName, static_cast<int>(DeviceStatus::Offline));
}
}
}, Qt::QueuedConnection);
}
void WheelMeasurePresenter::OnWorkStatusChanged(WorkStatus status)
{
// 写入Modbus工作状态 (地址1)
// 0=空闲, 1=初始化中, 2=就绪, 3=检测中, 4=完成, 5=错误
uint16_t statusValue = 0;
switch (status) {
case WorkStatus::InitIng: statusValue = 1; break;
@ -122,32 +125,45 @@ void WheelMeasurePresenter::OnWorkStatusChanged(WorkStatus status)
}
WriteModbusRegisters(1, &statusValue, 1);
// 直接使用公用的 WorkStatus不再需要转换
if (m_statusUpdate) {
m_statusUpdate->OnWorkStatusChanged(status);
}
// 切换到主线程更新UI
QMetaObject::invokeMethod(this, [this, status]() {
if (m_statusUpdate) {
m_statusUpdate->OnWorkStatusChanged(status);
}
}, Qt::QueuedConnection);
}
void WheelMeasurePresenter::OnCameraCountChanged(int count)
{
if (m_statusUpdate) {
// 使用配置中的相机名称,因为此时 m_vrEyeDeviceList 可能还未填充
QStringList cameraNames;
for (const auto& cameraConfig : m_configResult.cameras) {
if (cameraConfig.enabled) {
cameraNames.append(QString::fromStdString(cameraConfig.name));
}
if (!m_statusUpdate) return;
QStringList cameraNames;
for (const auto& cameraConfig : m_configResult.cameras) {
if (cameraConfig.enabled) {
cameraNames.append(QString::fromStdString(cameraConfig.name));
}
LOG_INFO("OnCameraCountChanged: count=%d, cameraNames=%d\n", count, cameraNames.size());
m_statusUpdate->OnNeedShowImageCount(cameraNames);
}
LOG_INFO("OnCameraCountChanged: count=%d, cameraNames=%d\n", count, cameraNames.size());
// 切换到主线程更新UI
QMetaObject::invokeMethod(this, [this, cameraNames]() {
if (m_statusUpdate) {
m_statusUpdate->OnNeedShowImageCount(cameraNames);
}
}, Qt::QueuedConnection);
}
void WheelMeasurePresenter::OnStatusUpdate(const std::string& statusMessage)
{
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(QString::fromStdString(statusMessage));
}
if (!m_statusUpdate) return;
QString msg = QString::fromStdString(statusMessage);
// 切换到主线程更新UI
QMetaObject::invokeMethod(this, [this, msg]() {
if (m_statusUpdate) {
m_statusUpdate->OnStatusUpdate(msg);
}
}, Qt::QueuedConnection);
}
bool WheelMeasurePresenter::initializeConfig(const QString& configPath)
@ -465,10 +481,15 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
}
groundCalibPara.planeHeight = calibParam->planeHeight;
LOG_INFO("Using calibrated plane parameters for camera %d\n", m_currentCameraIndex);
LOG_INFO(" planeHeight: %.3f, errorCompensation: %.2f\n", calibParam->planeHeight, calibParam->errorCompensation);
if(calibParam){
// 计算调平使用的地面高度(加上该相机的误差补偿)
double adjustedPlaneHeight = groundCalibPara.planeHeight + calibParam->errorCompensation;
LOG_INFO(" adjustedPlaneHeight (with compensation): %.3f\n", adjustedPlaneHeight);
for(size_t i = 0; i < scanLines.size(); i++){
wd_horizonCamera_lineDataR(scanLines[i], calibParam->planeCalib, groundCalibPara.planeHeight);
wd_horizonCamera_lineDataR(scanLines[i], calibParam->planeCalib, adjustedPlaneHeight);
}
}
} else {
@ -531,17 +552,18 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
wheelArchResult.wheelDownPos.x,
wheelArchResult.wheelDownPos.y,
wheelArchResult.wheelDownPos.z);
LOG_INFO("archToCenterHeigth: %.3f mm\n", wheelArchResult.archToCenterHeigth);
LOG_INFO("archToCenterHeigth: %.3f archToGroundHeigth: %.3f mm\n", wheelArchResult.archToCenterHeigth, wheelArchResult.archToGroundHeigth);
LOG_INFO("==================================================\n");
if (m_statusUpdate) {
QString statusMsg = QString("轮眉高度: %1 mm").arg(wheelArchResult.archToCenterHeigth);
QString statusMsg = QString("轮眉高度: %1 mm, 到地面高度: %2 mm").arg(wheelArchResult.archToCenterHeigth).arg(wheelArchResult.archToGroundHeigth);
m_statusUpdate->OnStatusUpdate(statusMsg);
}
// 填充测量结果数据
WheelMeasureData measureData;
measureData.archToCenterHeight = wheelArchResult.archToCenterHeigth;
measureData.archToGroundHeight = wheelArchResult.archToGroundHeigth;
measureData.wheelArchPosX = wheelArchResult.wheelArchPos.x;
measureData.wheelArchPosY = wheelArchResult.wheelArchPos.y;
measureData.wheelArchPosZ = wheelArchResult.wheelArchPos.z;
@ -581,7 +603,7 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
result.bImageValid = !result.image.isNull();
// ========== 写入检测结果到 Modbus (地址2-23) ==========
// ========== 写入检测结果到 Modbus (地址2-25) ==========
// 辅助lambda: float转两个uint16_t (大端模式)
auto floatToUint16 = [](float value, uint16_t& high, uint16_t& low) {
uint32_t bits;
@ -590,7 +612,7 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
low = static_cast<uint16_t>(bits & 0xFFFF);
};
uint16_t modbusData[22];
uint16_t modbusData[24];
memset(modbusData, 0, sizeof(modbusData));
// 地址2: 设备序号
@ -599,7 +621,7 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
modbusData[1] = (errCode == 0) ? 1 : 0;
if (errCode == 0) {
// 地址4-5: 轮眉高度
// 地址4-5: 轮眉高度(到中心)
floatToUint16(static_cast<float>(wheelArchResult.archToCenterHeigth), modbusData[2], modbusData[3]);
// 地址6-7: 轮眉X
floatToUint16(static_cast<float>(wheelArchResult.wheelArchPos.x), modbusData[4], modbusData[5]);
@ -619,10 +641,12 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
floatToUint16(static_cast<float>(wheelArchResult.wheelDownPos.y), modbusData[18], modbusData[19]);
// 地址22-23: 下点Z
floatToUint16(static_cast<float>(wheelArchResult.wheelDownPos.z), modbusData[20], modbusData[21]);
// 地址24-25: 到地面高度
floatToUint16(static_cast<float>(wheelArchResult.archToGroundHeigth), modbusData[22], modbusData[23]);
}
WriteModbusRegisters(2, modbusData, 22);
LOG_INFO("Modbus: 写入检测结果到地址2-23, 设备=%d, 有效=%d\n", m_currentCameraIndex, modbusData[1]);
WriteModbusRegisters(2, modbusData, 24);
LOG_INFO("Modbus: 写入检测结果到地址2-25, 设备=%d, 有效=%d\n", m_currentCameraIndex, modbusData[1]);
// 检测完成后清零"检测控制"地址0
uint16_t zero = 0;
@ -638,7 +662,6 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
// 如果正在进行顺序检测,继续检测下一个设备
if (m_sequentialDetecting) {
m_sequentialCurrentIndex++;
// 使用 QMetaObject::invokeMethod 延迟执行,确保当前检测完全结束
QMetaObject::invokeMethod(this, [this]() {
continueSequentialDetection();
}, Qt::QueuedConnection);

View File

@ -2,9 +2,9 @@
#define VERSION_H
#define WHEELMEASURE_VERSION_STRING "1.0.0"
#define WHEELMEASURE_BUILD_STRING "2"
#define WHEELMEASURE_FULL_VERSION_STRING "V1.0.0.2"
#define WHEELMEASURE_VERSION_STRING "1.0.1"
#define WHEELMEASURE_BUILD_STRING "3"
#define WHEELMEASURE_FULL_VERSION_STRING "V1.0.1.3"
// 获取版本信息的便捷函数
inline const char* GetWheelMeasureVersion() {

View File

@ -1,32 +1,8 @@
# 1.0.2
## build_3 2025-12-17
1. 测试不同的工件
2.
## build_2 2025-12-14
1. 完善协议
2. 显示结果精简和长度输出
## build_1 2025-12-11
1. 解决ARM上页面刷新崩溃问题
## build_0 2025-12-11
1. 更新算法
# 1.0.1 2025-11-08
## build_2
1. 修正开机相机重连【项目结构重构】
# 1.0.1 2025-12-31
## build_1
1. 更新算法,在应用日志中有算法版本: 1.1.0
2. 修复在没有配置文件中无法连接相机问题
2. 增加过滤参数
# 1.0.0 2025-11-02
## build_1
1. 修复相机打开点亮激光器
2. 修复相机调平
3. 相机参数配置失败提示指定的错误
## build_0
1. 初始版本
# 1.0.0 2025-12-27
- 初始版本

View File

@ -66,6 +66,12 @@ void DialogCameraLevel::setConfig(IVrWheelMeasureConfig* config, WheelMeasureCon
{
m_pConfig = config;
m_pConfigResult = configResult;
// 如果相机已经选择,重新加载当前相机的标定状态
// 修复:打开页面时配置可能在相机列表之后设置,导致初始加载失败
if (m_currentCameraIndex >= 0 && m_currentCameraIndex < static_cast<int>(m_cameraList.size())) {
checkAndDisplayCalibrationStatus(m_currentCameraIndex);
}
}
void DialogCameraLevel::initializeCameraCombo()
@ -182,9 +188,7 @@ bool DialogCameraLevel::performCameraLeveling()
// 获取选中的相机索引
int selectedIndex = ui->combo_camera->currentIndex();
// 1. 设置调平状态回调
setLevelingStatusCallback();
// 先检查索引有效性,再设置回调
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_cameraList.size())) {
LOG_ERROR("Invalid camera index: %d\n", selectedIndex);
return false;
@ -192,12 +196,16 @@ bool DialogCameraLevel::performCameraLeveling()
LOG_INFO("Performing camera leveling with camera %d (index %d)\n", selectedIndex + 1, selectedIndex);
// 1. 设置调平状态回调(在索引检查之后)
setLevelingStatusCallback();
// 2. 清空之前的扫描数据
clearScanDataCache();
// 3. 启动相机扫描地面数据
if (!startCameraScan(selectedIndex)) {
LOG_ERROR("Failed to start camera scan for leveling\n");
restorePresenterStatusCallback(); // 恢复回调
return false;
}
@ -229,6 +237,7 @@ bool DialogCameraLevel::performCameraLeveling()
if (!calculatePlaneCalibration(planeCalib, planeHeight, invRMatrix)) {
LOG_ERROR("Failed to calculate plane calibration\n");
restorePresenterStatusCallback(); // 恢复回调
return false;
}
@ -249,16 +258,22 @@ bool DialogCameraLevel::performCameraLeveling()
if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName)) {
LOG_ERROR("Failed to save leveling results\n");
restorePresenterStatusCallback(); // 恢复回调
return false;
}
clearScanDataCache();
// 9. 调平完成后恢复回调
restorePresenterStatusCallback();
LOG_INFO("Camera leveling completed successfully\n");
return true;
} catch (const std::exception& e) {
LOG_ERROR("Exception in performCameraLeveling: %s\n", e.what());
restorePresenterStatusCallback(); // 异常时也恢复回调
return false;
}
}
@ -471,12 +486,16 @@ bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHe
return false;
}
// 获取UI中的误差补偿值
double errorCompensation = ui->edit_error_compensation->text().toDouble();
// 创建或更新相机调平参数
WheelCameraPlaneCalibParam cameraParam;
cameraParam.cameraIndex = cameraIndex;
cameraParam.cameraName = cameraName.toStdString();
cameraParam.planeHeight = planeHeight;
cameraParam.isCalibrated = true;
cameraParam.errorCompensation = errorCompensation;
// 复制校准矩阵
for (int i = 0; i < 9; i++) {
@ -508,7 +527,7 @@ bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHe
LOG_INFO("Leveling results saved successfully for camera %d (%s)\n",
cameraIndex, cameraName.toUtf8().constData());
LOG_INFO("Plane height: %.3f\n", planeHeight);
LOG_INFO("Plane height: %.3f, Error compensation: %.2f\n", planeHeight, errorCompensation);
return true;
@ -536,12 +555,18 @@ bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString
}
planeHeight = param.planeHeight;
// 加载该相机的误差补偿值到UI
ui->edit_error_compensation->setText(QString::number(param.errorCompensation, 'f', 1));
LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n",
cameraIndex, cameraName.toUtf8().constData());
return true;
}
}
// 没有找到标定数据时,设置默认误差补偿值
ui->edit_error_compensation->setText(QString::number(-5.0, 'f', 1));
LOG_INFO("No calibration data found for camera %d (%s)\n",
cameraIndex, cameraName.toUtf8().constData());
return false;
@ -627,3 +652,54 @@ void DialogCameraLevel::restorePresenterStatusCallback()
LOG_INFO("Presenter status callback restored for all cameras\n");
}
void DialogCameraLevel::on_btn_save_compensation_clicked()
{
// 保存误差补偿值到当前相机的调平参数
if (!m_pConfig || !m_pConfigResult) {
LOG_ERROR("Config is null, cannot save error compensation\n");
return;
}
if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast<int>(m_cameraList.size())) {
LOG_WARNING("Invalid camera index: %d\n", m_currentCameraIndex);
return;
}
// 获取UI中的误差补偿值
double errorCompensation = ui->edit_error_compensation->text().toDouble();
int cameraIndex = m_currentCameraIndex + 1; // 转换为1-based索引
QString cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first);
LOG_INFO("Saving error compensation for camera %d (%s): %.2f\n",
cameraIndex, cameraName.toUtf8().constData(), errorCompensation);
// 查找或创建相机调平参数
bool found = false;
for (auto& param : m_pConfigResult->planeCalibParams) {
if (param.cameraIndex == cameraIndex) {
param.errorCompensation = errorCompensation;
found = true;
break;
}
}
// 如果没有找到现有记录,创建一个新的(仅包含误差补偿,其他值为默认)
if (!found) {
WheelCameraPlaneCalibParam newParam;
newParam.cameraIndex = cameraIndex;
newParam.cameraName = cameraName.toStdString();
newParam.errorCompensation = errorCompensation;
newParam.isCalibrated = false; // 尚未标定
m_pConfigResult->planeCalibParams.push_back(newParam);
}
// 保存配置到文件
QString configPath = PathManager::GetInstance().GetConfigFilePath();
bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult);
if (saveResult) {
LOG_INFO("Error compensation saved successfully for camera %d: %.2f\n", cameraIndex, errorCompensation);
} else {
LOG_ERROR("Failed to save error compensation\n");
}
}

View File

@ -48,6 +48,7 @@ private slots:
void on_btn_apply_clicked();
void on_btn_cancel_clicked();
void on_combo_camera_currentIndexChanged(int index);
void on_btn_save_compensation_clicked();
private:
Ui::DialogCameraLevel *ui;

View File

@ -180,6 +180,111 @@ background-color: rgb(47, 48, 52);</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_error_compensation">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);</string>
</property>
<property name="text">
<string>误差补偿:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_error_compensation">
<property name="minimumSize">
<size>
<width>100</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);
background-color: rgb(47, 48, 52);</string>
</property>
<property name="text">
<string>-5.0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_unit">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(221, 225, 233);</string>
</property>
<property name="text">
<string>mm</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_save_compensation">
<property name="minimumSize">
<size>
<width>60</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>35</height>
</size>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
color: rgb(221, 225, 233);
background-color: rgb(60, 63, 65);
border: 1px solid #3B3D47;
border-radius: 4px;
}
QPushButton:hover {
background-color: rgb(80, 83, 85);
}
QPushButton:pressed {
background-color: rgb(45, 48, 50);
}</string>
</property>
<property name="text">
<string>保存</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">

View File

@ -78,12 +78,10 @@ MainWindow::MainWindow(QWidget *parent)
connect(m_presenter, &WheelMeasurePresenter::configUpdated, this, &MainWindow::onConfigSaved);
// 连接设备点击信号到重新检测槽函数
connect(m_deviceStatusWidget, &DeviceStatusWidget::deviceClicked,
this, &MainWindow::onDeviceClicked);
connect(m_deviceStatusWidget, &DeviceStatusWidget::deviceClicked, this, &MainWindow::onDeviceClicked);
// 连接图像右键点击信号到保存数据槽函数
connect(m_gridView, &ImageGridWidget::tileRightClicked,
this, &MainWindow::onTileRightClicked);
connect(m_gridView, &ImageGridWidget::tileRightClicked, this, &MainWindow::onTileRightClicked);
// 设置版本信息显示
setupVersionDisplay();
@ -100,6 +98,11 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow()
{
// 先清除回调,防止后台线程继续调用
if (m_presenter) {
m_presenter->setStatusUpdate(nullptr);
}
if (m_gridView) {
delete m_gridView;
m_gridView = nullptr;
@ -141,6 +144,7 @@ MainWindow::~MainWindow()
}
delete ui;
LOG_DEBUG("~MainWindow finish \n");
}
void MainWindow::resizeEvent(QResizeEvent* event)
@ -353,9 +357,9 @@ void MainWindow::onDeviceClicked(const QString& deviceName)
void MainWindow::onConfigSaved()
{
if (m_presenter) {
m_presenter->Init();
}
// 配置已在 WheelMeasurePresenter::OnConfigChanged 中更新
// 算法参数和调平参数的修改不需要重新初始化相机
LOG_INFO("Config saved, configuration updated in memory\n");
}
void MainWindow::onTileRightClicked(int index, const QString& alias)

View File

@ -322,9 +322,9 @@ border: none;</string>
<property name="geometry">
<rect>
<x>1340</x>
<y>760</y>
<y>800</y>
<width>560</width>
<height>230</height>
<height>190</height>
</rect>
</property>
<property name="font">
@ -347,7 +347,7 @@ border: none;</string>
<x>1340</x>
<y>230</y>
<width>560</width>
<height>520</height>
<height>560</height>
</rect>
</property>
<property name="styleSheet">

View File

@ -22,29 +22,29 @@ void DeviceResultCard::setupUI()
" background-color: rgb(47, 48, 52); "
" border: 1px solid rgb(60, 62, 68); "
" border-radius: 8px; "
" padding: 10px; "
" padding: 6px; "
"} "
"DeviceResultCard:hover { "
" border: 1px solid rgb(80, 82, 88); "
"}"
);
setMinimumHeight(120);
setMaximumHeight(160);
setMinimumHeight(130);
setMaximumHeight(170);
// 主布局:水平布局(左侧名称 | 右侧数据)
QHBoxLayout* mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(12, 8, 12, 8);
mainLayout->setSpacing(15);
mainLayout->setContentsMargins(8, 4, 8, 4);
mainLayout->setSpacing(10);
// 左侧:设备名称和状态
QVBoxLayout* leftLayout = new QVBoxLayout();
leftLayout->setSpacing(4);
leftLayout->setSpacing(2);
m_nameLabel = new QLabel(m_deviceName, this);
m_nameLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 16px; background: transparent; }");
m_nameLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_nameLabel->setMinimumWidth(80);
m_nameLabel->setMinimumWidth(60);
m_statusLabel = new QLabel(QString::fromUtf8("等待检测"), this);
m_statusLabel->setStyleSheet("QLabel { color: rgb(150, 150, 150); font-size: 16px; background: transparent; }");
@ -65,21 +65,25 @@ void DeviceResultCard::setupUI()
// 右侧:纵向显示数据
QVBoxLayout* rightLayout = new QVBoxLayout();
rightLayout->setSpacing(2);
rightLayout->setSpacing(4);
// 拱高
m_heightLabel = new QLabel(QString::fromUtf8("拱高: -- mm"), this);
m_heightLabel->setStyleSheet("QLabel { color: rgb(100, 200, 100); font-size: 16px; background: transparent; }");
// 到地面高度
m_groundHeightLabel = new QLabel(QString::fromUtf8("到地面: -- mm"), this);
m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(100, 180, 220); font-size: 16px; background: transparent; }");
// 拱点位置
m_archPosLabel = new QLabel(QString::fromUtf8("拱点: X:-- Y:-- Z:--"), this);
m_archPosLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 14px; background: transparent; }");
// 上位置
// 上位置
m_upPosLabel = new QLabel(QString::fromUtf8("上点: X:-- Y:-- Z:--"), this);
m_upPosLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 14px; background: transparent; }");
// 下位置
// 下位置
m_downPosLabel = new QLabel(QString::fromUtf8("下点: X:-- Y:-- Z:--"), this);
m_downPosLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 14px; background: transparent; }");
@ -89,6 +93,7 @@ void DeviceResultCard::setupUI()
rightLayout->addStretch();
rightLayout->addWidget(m_heightLabel);
rightLayout->addWidget(m_groundHeightLabel);
rightLayout->addWidget(m_archPosLabel);
rightLayout->addWidget(m_upPosLabel);
rightLayout->addWidget(m_downPosLabel);
@ -105,6 +110,10 @@ void DeviceResultCard::updateResult(const WheelMeasureData& data, bool isValid)
m_heightLabel->setText(QString::fromUtf8("拱高: %1 mm").arg(data.archToCenterHeight, 0, 'f', 2));
m_heightLabel->setStyleSheet("QLabel { color: rgb(100, 200, 100); font-size: 16px; background: transparent; }");
// 更新到地面高度
m_groundHeightLabel->setText(QString::fromUtf8("到地面: %1 mm").arg(data.archToGroundHeight, 0, 'f', 2));
m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(100, 180, 220); font-size: 16px; background: transparent; }");
// 更新拱点位置
QString archPosText = QString::fromUtf8("拱点: X:%1 Y:%2 Z:%3")
.arg(data.wheelArchPosX, 0, 'f', 2)
@ -112,14 +121,14 @@ void DeviceResultCard::updateResult(const WheelMeasureData& data, bool isValid)
.arg(data.wheelArchPosZ, 0, 'f', 2);
m_archPosLabel->setText(archPosText);
// 更新上位置
// 更新上位置
QString upPosText = QString::fromUtf8("上点: X:%1 Y:%2 Z:%3")
.arg(data.wheelUpPosX, 0, 'f', 2)
.arg(data.wheelUpPosY, 0, 'f', 2)
.arg(data.wheelUpPosZ, 0, 'f', 2);
m_upPosLabel->setText(upPosText);
// 更新下位置
// 更新下位置
QString downPosText = QString::fromUtf8("下点: X:%1 Y:%2 Z:%3")
.arg(data.wheelDownPosX, 0, 'f', 2)
.arg(data.wheelDownPosY, 0, 'f', 2)
@ -140,6 +149,9 @@ void DeviceResultCard::updateResult(const WheelMeasureData& data, bool isValid)
m_heightLabel->setText(QString::fromUtf8("拱高: -- mm"));
m_heightLabel->setStyleSheet("QLabel { color: rgb(200, 100, 100); font-size: 16px; background: transparent; }");
m_groundHeightLabel->setText(QString::fromUtf8("到地面: -- mm"));
m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(200, 100, 100); font-size: 16px; background: transparent; }");
m_archPosLabel->setText(QString::fromUtf8("拱点: X:-- Y:-- Z:--"));
m_upPosLabel->setText(QString::fromUtf8("上点: X:-- Y:-- Z:--"));
m_downPosLabel->setText(QString::fromUtf8("下点: X:-- Y:-- Z:--"));
@ -156,6 +168,8 @@ void DeviceResultCard::clearResult()
{
m_heightLabel->setText(QString::fromUtf8("拱高: -- mm"));
m_heightLabel->setStyleSheet("QLabel { color: rgb(150, 150, 150); font-size: 16px; background: transparent; }");
m_groundHeightLabel->setText(QString::fromUtf8("到地面: -- mm"));
m_groundHeightLabel->setStyleSheet("QLabel { color: rgb(150, 150, 150); font-size: 16px; background: transparent; }");
m_archPosLabel->setText(QString::fromUtf8("拱点: X:-- Y:-- Z:--"));
m_upPosLabel->setText(QString::fromUtf8("上点: X:-- Y:-- Z:--"));
m_downPosLabel->setText(QString::fromUtf8("下点: X:-- Y:-- Z:--"));

View File

@ -31,9 +31,10 @@ private:
QString m_deviceName;
QLabel* m_nameLabel = nullptr;
QLabel* m_heightLabel = nullptr;
QLabel* m_groundHeightLabel = nullptr; // 到地面高度
QLabel* m_archPosLabel = nullptr; // 拱点位置
QLabel* m_upPosLabel = nullptr; // 上位置
QLabel* m_downPosLabel = nullptr; // 下位置
QLabel* m_upPosLabel = nullptr; // 上位置
QLabel* m_downPosLabel = nullptr; // 下位置
QLabel* m_timestampLabel = nullptr;
QLabel* m_statusLabel = nullptr;
};

View File

@ -60,6 +60,7 @@ struct WheelCameraPlaneCalibParam
double planeHeight = -1; // 地面高度
double invRMatrix[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; // 逆旋转矩阵(回到原坐标系)
bool isCalibrated = false; // 是否已标定
double errorCompensation = -5.0; // 误差补偿mm
};
/**
@ -154,6 +155,7 @@ struct WheelMeasureData
{
int id = 0; // 测量ID
double archToCenterHeight = 0.0; // 拱高到中心的高度
double archToGroundHeight = 0.0; // 拱高到地面的高度
double wheelArchPosX = 0.0; // 拱点X坐标
double wheelArchPosY = 0.0; // 拱点Y坐标
double wheelArchPosZ = 0.0; // 拱点Z坐标

View File

@ -84,6 +84,12 @@ WheelMeasureConfigResult VrWheelMeasureConfig::LoadConfig(const std::string& fil
calibParam.cameraName = xml.attributes().value("name").toString().toStdString();
calibParam.planeHeight = xml.attributes().value("planeHeight").toDouble();
calibParam.isCalibrated = xml.attributes().value("isCalibrated").toInt() != 0;
// 读取误差补偿参数,默认值为-5.0
if (xml.attributes().hasAttribute("errorCompensation")) {
calibParam.errorCompensation = xml.attributes().value("errorCompensation").toDouble();
} else {
calibParam.errorCompensation = -5.0;
}
// 读取planeCalib矩阵
QString planeCalibStr = xml.attributes().value("planeCalib").toString();
@ -321,6 +327,7 @@ bool VrWheelMeasureConfig::SaveConfig(const std::string& filePath, WheelMeasureC
xml.writeAttribute("name", QString::fromStdString(calibParam.cameraName));
xml.writeAttribute("planeHeight", QString::number(calibParam.planeHeight, 'f', 6));
xml.writeAttribute("isCalibrated", QString::number(calibParam.isCalibrated ? 1 : 0));
xml.writeAttribute("errorCompensation", QString::number(calibParam.errorCompensation, 'f', 2));
// 保存planeCalib矩阵
QStringList planeCalibList;

View File

@ -8,7 +8,7 @@
<!-- 相机调平参数 -->
<PlaneCalibParams>
<!-- 相机1的调平参数默认单位矩阵未校准状态 -->
<CameraCalib index="1" name="Camera1" planeHeight="554.0" isCalibrated="1"
<CameraCalib index="1" name="Camera1" planeHeight="554.0" isCalibrated="1" errorCompensation="-5.00"
planeCalib="1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0"
invRMatrix="1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0"/>
</PlaneCalibParams>

View File

@ -15,6 +15,7 @@ typedef struct
SVzNL3DPoint downLine[2];
SVzNL3DPoint centerLine[2];
double archToCenterHeigth;
double archToGroundHeigth;
}WD_wheelArchInfo;
//读版本号

View File

@ -509,6 +509,9 @@ protected:
// ModbusTCP服务器
IYModbusTCPServer* m_modbusServer = nullptr;
// 初始化线程
std::thread m_initThread;
private:
// 启动ModbusTCP服务在Init中调用
int StartModbusServer(int port = 502);

View File

@ -22,8 +22,10 @@ BasePresenter::BasePresenter(QObject *parent)
BasePresenter::~BasePresenter()
{
// 停止ModbusTCP服务器
StopModbusServer();
// 等待初始化线程完成
if (m_initThread.joinable()) {
m_initThread.join();
}
// 停止检测线程
StopAlgoDetectThread();
@ -31,8 +33,12 @@ BasePresenter::~BasePresenter()
// 停止重连定时器
StopCameraReconnectTimer();
// 停止ModbusTCP服务器
StopModbusServer();
// 清理相机设备
for (auto& camera : m_vrEyeDeviceList) {
for (size_t i = 0; i < m_vrEyeDeviceList.size(); ++i) {
auto& camera = m_vrEyeDeviceList[i];
if (camera.second) {
camera.second->CloseDevice();
delete camera.second;
@ -46,24 +52,30 @@ int BasePresenter::Init()
{
LOG_INFO("BasePresenter::Init()\n");
int nRet = SUCCESS;
// 在后台线程中执行初始化
m_initThread = std::thread([this]() {
int nRet = InitApp();
if (nRet != SUCCESS) {
LOG_ERROR("InitApp failed: %d\n", nRet);
return;
}
nRet = InitApp();
ERR_CODE_RETURN(nRet);
// 初始化算法参数
nRet = InitAlgoParams();
LOG_INFO("Algorithm parameters initialization result: %d\n", nRet);
// 初始化算法参数
nRet = InitAlgoParams();
LOG_INFO("Algorithm parameters initialization result : %d\n", nRet);
// 启动ModbusTCP服务
int modbusRet = StartModbusServer(5020);
if (modbusRet == SUCCESS) {
LOG_INFO("ModbusTCP server started on port 5020\n");
} else {
LOG_WARNING("Failed to start ModbusTCP server\n");
}
// 启动ModbusTCP服务
int modbusRet = StartModbusServer(5020);
if (modbusRet == SUCCESS) {
LOG_INFO("ModbusTCP server started on port 502(0)\n");
} else {
LOG_WARNING("Failed to start ModbusTCP server\n");
}
SetWorkStatus(WorkStatus::Ready);
});
return nRet;
return SUCCESS;
}
int BasePresenter::StartDetection(int cameraIndex, bool isAuto)
@ -462,9 +474,8 @@ void BasePresenter::StartAlgoDetectThread()
m_bAlgoDetectThreadRunning = true;
// 启动检测线程
// 启动检测线程不再detach使用joinable线程
m_algoDetectThread = std::thread(&BasePresenter::AlgoDetectThreadFunc, this);
m_algoDetectThread.detach(); // 分离线程,让其独立运行
LOG_INFO("[BasePresenter] 算法检测线程已启动\n");
}
@ -482,7 +493,10 @@ void BasePresenter::StopAlgoDetectThread()
// 唤醒可能在等待的线程
m_algoDetectCondition.notify_all();
// 注意:因为线程使用了 detach(),所以不需要 join()
// 等待线程退出
if (m_algoDetectThread.joinable()) {
m_algoDetectThread.join();
}
LOG_INFO("[BasePresenter] 算法检测线程已停止\n");
}
@ -628,29 +642,23 @@ void BasePresenter::StartCameraReconnectTimer()
// 使用QMetaObject::invokeMethod确保在正确的线程中操作定时器
QMetaObject::invokeMethod(this, [this]() {
if (!m_pCameraReconnectTimer) {
LOG_ERROR("[BasePresenter] m_pCameraReconnectTimer is nullptr!\n");
return;
}
if (m_pCameraReconnectTimer->isActive()) {
LOG_DEBUG("[BasePresenter] 相机重连定时器已在运行\n");
return;
}
m_pCameraReconnectTimer->start();
LOG_INFO("[BasePresenter] 启动相机重连定时器(间隔: %d ms\n", m_pCameraReconnectTimer->interval());
}, Qt::QueuedConnection);
}
void BasePresenter::StopCameraReconnectTimer()
{
// 使用QMetaObject::invokeMethod确保在正确的线程中操作定时器
QMetaObject::invokeMethod(this, [this]() {
if (m_pCameraReconnectTimer && m_pCameraReconnectTimer->isActive()) {
m_pCameraReconnectTimer->stop();
LOG_INFO("[BasePresenter] 停止相机重连定时器\n");
}
}, Qt::QueuedConnection);
// 直接停止定时器析构时需要立即停止不能用QueuedConnection
if (m_pCameraReconnectTimer) {
m_pCameraReconnectTimer->stop();
}
}
// ============ OnCameraReconnectTimer 实现 ============
@ -768,7 +776,6 @@ int BasePresenter::StartModbusServer(int port)
void BasePresenter::StopModbusServer()
{
if (m_modbusServer) {
LOG_INFO("[BasePresenter] 停止ModbusTCP服务器\n");
m_modbusServer->stop();
delete m_modbusServer;
m_modbusServer = nullptr;

View File

@ -427,7 +427,11 @@ void CVrEyeDevice::DebugThreadFunc()
// 固定的点云文件路径
// const std::string debugFilePath = "C:\\project\\QT\\GrabBag\\TestData\\workpiece\\new\\test\\9_LazerData_Hi229229.txt";
// const std::string debugFilePath = "C:\\project\\QT\\GrabBag\\TestData\\bag\\LaserLine1_grid.txt";
#ifdef _WIN32
const std::string debugFilePath = "C:\\project\\QT\\GrabBag\\TestData\\eye_test.txt";
#else
const std::string debugFilePath = "/home/linaro/eye_test.txt";
#endif
LaserDataLoader loader;
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>> laserLines;
@ -438,24 +442,24 @@ void CVrEyeDevice::DebugThreadFunc()
// 加载点云文件
int ret = loader.LoadLaserScanData(debugFilePath, laserLines, lineNum, scanSpeed, maxTimeStamp, clockPerSecond);
if (ret != SUCCESS) {
if (ret == SUCCESS) {
LOG_DEBUG("VR_EYE_DEBUG_MODE: Failed to load laser data from %s, error: %s\n",debugFilePath.c_str(), loader.GetLastError().c_str());
return;
}
// 循环读取并回调
auto iter = laserLines.begin();
auto iter = laserLines.begin();
while (iter != laserLines.end()) {
if (m_debugCallback) {
auto& dataPair = *iter;
m_debugCallback(dataPair.first, &dataPair.second, m_debugCallbackParam);
while (iter != laserLines.end()) {
if (m_debugCallback) {
auto& dataPair = *iter;
m_debugCallback(dataPair.first, &dataPair.second, m_debugCallbackParam);
}
iter++;
// 模拟采集间隔约30ms
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
iter++;
// 模拟采集间隔约30ms
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
if (m_debugStatusCallback) {
m_debugStatusCallback(keDeviceWorkStatus_Device_Swing_Finish, nullptr, 0, m_debugStatusCallbackParam);
m_debugStatusCallback(keDeviceWorkStatus_Device_Auto_Stop, nullptr, 0, m_debugStatusCallbackParam);

View File

@ -95,8 +95,10 @@ void ModbusTCPServer::stop()
return;
}
LOG_INFO("[ModbusTCPServer] stop()\n");
m_shouldStop.store(true);
// 关闭服务器socket以中断accept调用
if (m_serverSocket != -1) {
#ifdef _WIN32
@ -135,57 +137,69 @@ void ModbusTCPServer::stop()
void ModbusTCPServer::serverLoop()
{
fd_set readfds;
int maxfd = m_serverSocket;
while (!m_shouldStop.load()) {
// 检查服务器socket是否有效
int serverSock = m_serverSocket;
if (serverSock == -1) {
break; // socket已关闭退出循环
}
FD_ZERO(&readfds);
FD_SET(m_serverSocket, &readfds);
maxfd = m_serverSocket;
FD_SET(serverSock, &readfds);
int maxfd = serverSock;
// 添加所有客户端socket到监听集合
{
std::lock_guard<std::mutex> lock(m_clientsMutex);
for (const auto& pair : m_clients) {
int clientSocket = pair.first;
FD_SET(clientSocket, &readfds);
if (clientSocket > maxfd) {
maxfd = clientSocket;
if (clientSocket >= 0) {
FD_SET(clientSocket, &readfds);
if (clientSocket > maxfd) {
maxfd = clientSocket;
}
}
}
}
// 设置超时时间1秒避免阻塞太久
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int selectResult = select(maxfd + 1, &readfds, nullptr, nullptr, &timeout);
// 再次检查是否应该停止
if (m_shouldStop.load()) {
break;
}
if (selectResult == -1) {
if (!m_shouldStop.load()) {
LOG_WARNING("select error: %s\n", strerror(errno));
}
break;
}
if (selectResult == 0) {
// 超时,继续循环
continue;
}
// 检查服务器socket是否有新连接
if (FD_ISSET(m_serverSocket, &readfds)) {
if (serverSock >= 0 && FD_ISSET(serverSock, &readfds)) {
handleNewConnection();
}
// 检查客户端socket是否有数据
std::vector<int> socketsToRemove;
{
std::lock_guard<std::mutex> lock(m_clientsMutex);
for (const auto& pair : m_clients) {
int clientSocket = pair.first;
if (FD_ISSET(clientSocket, &readfds)) {
if (clientSocket >= 0 && FD_ISSET(clientSocket, &readfds)) {
try {
handleClientData(pair.second);
} catch (...) {
@ -195,7 +209,7 @@ void ModbusTCPServer::serverLoop()
}
}
}
// 删除有问题的客户端连接
for (int socket : socketsToRemove) {
removeClient(socket);