LAPTOP-SNT8I5JK\Boounion
2025-06-26 838262ab61d580d7dd5eb3b181c61d8b4d3f54fe
Merge branch 'liuyang' into clh
已修改10个文件
575 ■■■■ 文件已修改
SourceCode/Bond/Servo/CCLinkPerformance/PerformanceMelsec.cpp 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCLinkPerformance/PerformanceMelsec.h 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqModeStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGraph1.cpp 246 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGraph1.h 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Common.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeDeviceBindDlg.cpp 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeDeviceBindDlg.h 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeManager.cpp 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/RecipeManager.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCLinkPerformance/PerformanceMelsec.cpp
@@ -9,7 +9,7 @@
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
static char* THIS_FILE = __FILE__;
#define new DEBUG_NEW
#endif
@@ -695,6 +695,100 @@
    return 0;
}
// 扩展读取位数据
long CPerformanceMelsec::ReadBitDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nBitCount, BitContainer& vecData) {
    long nRet = ValidateStationAndSize(station, nBitCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const long nWordCount = (nBitCount + 15) / 16;
    const long nByteSize = nWordCount * sizeof(short);
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nDevNo, nByteSize, vecRaw);
    if (nRet != 0) {
        return nRet;
    }
    vecData.clear();
    for (long i = 0; i < nWordCount; ++i) {
        short word = static_cast<unsigned char>(vecRaw[i * 2]) |
            (static_cast<unsigned char>(vecRaw[i * 2 + 1]) << 8);
        for (int j = 0; j < 16; ++j) {
            vecData.push_back((word & (1 << j)) != 0);
            if (vecData.size() >= static_cast<size_t>(nBitCount)) {
                return 0;
            }
        }
    }
    return 0;
}
// 扩展读取字数据
long CPerformanceMelsec::ReadWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nWordCount, WordContainer& vecData) {
    long nRet = ValidateStationAndSize(station, nWordCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const long nByteSize = nWordCount * sizeof(short);
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nDevNo, nByteSize, vecRaw);
    if (nRet != 0) {
        return nRet;
    }
    vecData.clear();
    for (long i = 0; i < nWordCount; ++i) {
        short value = static_cast<unsigned char>(vecRaw[i * 2]) |
            (static_cast<unsigned char>(vecRaw[i * 2 + 1]) << 8);
        vecData.push_back(value);
    }
    return 0;
}
// 扩展读取双字数据
long CPerformanceMelsec::ReadDWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nDWordCount, DWordContainer& vecData) {
    long nRet = ValidateStationAndSize(station, nDWordCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const long nByteSize = nDWordCount * sizeof(uint32_t);
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nDevNo, nByteSize, vecRaw);
    if (nRet != 0) {
        return nRet;
    }
    vecData.clear();
    for (long i = 0; i < nDWordCount; ++i) {
        uint32_t val = static_cast<unsigned char>(vecRaw[i * 4 + 0]) |
            (static_cast<unsigned char>(vecRaw[i * 4 + 1]) << 8) |
            (static_cast<unsigned char>(vecRaw[i * 4 + 2]) << 16) |
            (static_cast<unsigned char>(vecRaw[i * 4 + 3]) << 24);
        vecData.push_back(val);
    }
    return 0;
}
// 扩展写数据
long CPerformanceMelsec::WriteDataEx(const StationIdentifier& station, long nDevType, long nDevNo, const std::vector<char>& vecData) {
    // 验证站点参数和数据有效性
@@ -724,6 +818,82 @@
    return nRet;
}
// 扩展写位数据
long CPerformanceMelsec::WriteBitDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const BitContainer& vecData) {
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const size_t nWordCount = (vecData.size() + 15) / 16;
    std::vector<short> vecWordBuffer(nWordCount, 0);
    for (size_t i = 0; i < vecData.size(); ++i) {
        if (vecData[i]) {
            vecWordBuffer[i / 16] |= (1 << (i % 16));
        }
    }
    // 转换 short -> char
    std::vector<char> vecByteBuffer;
    vecByteBuffer.resize(nWordCount * sizeof(short));
    for (size_t i = 0; i < nWordCount; ++i) {
        vecByteBuffer[i * 2] = static_cast<char>(vecWordBuffer[i] & 0xFF);
        vecByteBuffer[i * 2 + 1] = static_cast<char>((vecWordBuffer[i] >> 8) & 0xFF);
    }
    return WriteDataEx(station, nDevType, nDevNo, vecByteBuffer);
}
// 扩展写字数据
long CPerformanceMelsec::WriteWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const WordContainer& vecData) {
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    std::vector<char> vecByteBuffer;
    vecByteBuffer.resize(vecData.size() * sizeof(short));
    for (size_t i = 0; i < vecData.size(); ++i) {
        vecByteBuffer[i * 2] = static_cast<char>(vecData[i] & 0xFF);
        vecByteBuffer[i * 2 + 1] = static_cast<char>((vecData[i] >> 8) & 0xFF);
    }
    return WriteDataEx(station, nDevType, nDevNo, vecByteBuffer);
}
// 扩展写双字数据
long CPerformanceMelsec::WriteDWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const DWordContainer& vecData) {
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    std::vector<char> vecByteBuffer;
    vecByteBuffer.resize(vecData.size() * sizeof(uint32_t));
    for (size_t i = 0; i < vecData.size(); ++i) {
        vecByteBuffer[i * 4] = static_cast<char>(vecData[i] & 0xFF);
        vecByteBuffer[i * 4 + 1] = static_cast<char>((vecData[i] >> 8) & 0xFF);
        vecByteBuffer[i * 4 + 2] = static_cast<char>((vecData[i] >> 16) & 0xFF);
        vecByteBuffer[i * 4 + 3] = static_cast<char>((vecData[i] >> 24) & 0xFF);
    }
    return WriteDataEx(station, nDevType, nDevNo, vecByteBuffer);
}
// 扩展软元件随机读取
long CPerformanceMelsec::ReadRandomDataEx(const StationIdentifier& station, const std::vector<SoftElement>& vecSoftElements, std::vector<char>& vecData) {
    if (vecSoftElements.empty()) {
SourceCode/Bond/Servo/CCLinkPerformance/PerformanceMelsec.h
@@ -388,7 +388,13 @@
    // 扩展读写数据
    long ReadDataEx(const StationIdentifier& station, long nDevType, long nDevNo, long nSize, std::vector<char>& vecData);
    long ReadBitDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nBitCount, BitContainer& vecData);
    long ReadWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nWordCount, WordContainer& vecData);
    long ReadDWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nDWordCount, DWordContainer& vecData);
    long WriteDataEx(const StationIdentifier& station, long nDevType, long nDevNo, const std::vector<char>& vecData);
    long WriteBitDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const BitContainer& vecData);
    long WriteWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const WordContainer& vecData);
    long WriteDWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const DWordContainer& vecData);
    // 扩展软元件随机读写(支持多个软元件)
    long ReadRandomDataEx(const StationIdentifier& station, const std::vector<SoftElement>& vecSoftElements, std::vector<char>& vecData);
SourceCode/Bond/Servo/CEqModeStep.cpp
@@ -32,7 +32,7 @@
        CReadStep::onReadData();
        DWordContainer dc;
        if (0 != m_pCclink->ReadDWordData(m_station, DeviceType::W, m_nModeDev, 1, dc)) {
        if (0 != m_pCclink->ReadDWordDataEx(m_station, DeviceType::W, m_nModeDev, 1, dc)) {
            return -2;
        }
        if (dc.size() < 1) {
SourceCode/Bond/Servo/CPageGraph1.cpp
@@ -7,7 +7,22 @@
#include "afxdialogex.h"
#include "Common.h"
const POINT g_arm1Offset = { -30, -45 }; // ARM1 从中心向左47, 向上33
const POINT g_arm2Offset = { 27, -45 };     // ARM2 从中心向右10, 向上33
const std::map<SERVO::ROBOT_POSITION, RobotPositionMapping> g_positionMap = {
    { SERVO::ROBOT_POSITION::Port1,     { SERVO::ROBOT_POSITION::Port1,     1.00f,   0.00f } },
    { SERVO::ROBOT_POSITION::Port2,     { SERVO::ROBOT_POSITION::Port2,     0.90f,   0.00f } },
    { SERVO::ROBOT_POSITION::Port3,     { SERVO::ROBOT_POSITION::Port3,     0.75f,   0.00f } },
    { SERVO::ROBOT_POSITION::Port4,     { SERVO::ROBOT_POSITION::Port4,     0.60f,   0.00f } },
    { SERVO::ROBOT_POSITION::Aligner,   { SERVO::ROBOT_POSITION::Aligner,   0.40f,   0.00f } },
    { SERVO::ROBOT_POSITION::Fliper,    { SERVO::ROBOT_POSITION::Fliper,    0.25f,     0.00f } },
    { SERVO::ROBOT_POSITION::Bonder1,   { SERVO::ROBOT_POSITION::Bonder1,   0.00f,   0.00f } },
    { SERVO::ROBOT_POSITION::Bonder2,   { SERVO::ROBOT_POSITION::Bonder2,   0.00f,   180.00f } },
    { SERVO::ROBOT_POSITION::Bake,      { SERVO::ROBOT_POSITION::Bake,      0.35f,   180.00f } },
    { SERVO::ROBOT_POSITION::Cooling,   { SERVO::ROBOT_POSITION::Cooling,   0.65f,   180.00f } },
    { SERVO::ROBOT_POSITION::Measurement,{SERVO::ROBOT_POSITION::Measurement,1.00f,  180.00f } },
};
// Image
#define IMAGE_ROBOT                2
@@ -25,6 +40,10 @@
#define INDICATE_VACUUM_BAKE    11
#define INDICATE_BAKE_COOLING    12
#define INDICATE_MEASUREMENT    13
// 定时器
#define TIMER_ID_DEVICE_STATUS      1   // 用于初始化设备状态
#define TIMER_ID_ROBOT_STATUS       2   // 用于周期刷新机器人位置/臂状态
// CPageGraph1 对话框
@@ -133,8 +152,8 @@
{
    CDialogEx::OnInitDialog();
    InitRxWindows();
    SetTimer(1, 3000, nullptr);
    SetTimer(TIMER_ID_DEVICE_STATUS, 3000, nullptr);
    SetTimer(TIMER_ID_ROBOT_STATUS, 1000, nullptr); // 每 1000ms 更新一次状态
    // 图示
    m_pGraph = CServoGraph::Hook(GetDlgItem(IDC_SERVO_GRAPH1)->GetSafeHwnd());
@@ -236,7 +255,12 @@
        newFrameColor2 = EQ_BOX_FRAME2;
        break;
    case OFFLINE:
        newBackgroundColor = RGB(222, 222, 222);
        newBackgroundColor = EQ_BOX_OFFLINE;
        newFrameColor1 = EQ_BOX_FRAME1;
        newFrameColor2 = EQ_BOX_FRAME2;
        break;
    case OCCUPIED:
        newBackgroundColor = EQ_BOX_OCCUPIED;
        newFrameColor1 = EQ_BOX_FRAME1;
        newFrameColor2 = EQ_BOX_FRAME2;
        break;
@@ -285,6 +309,8 @@
{
    CDialogEx::OnDestroy();
    KillTimer(TIMER_ID_ROBOT_STATUS);
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
    }
@@ -307,101 +333,97 @@
void CPageGraph1::UpdateRobotPosition(float percentage)
{
    // 限制百分比范围在 [0, 1] 之间
    if (percentage < 0.0f) percentage = 0.0f;
    if (percentage > 1.0f) percentage = 1.0f;
    // 根据百分比计算目标 X 坐标
    int startX = m_pGraph->GetImage(IMAGE_ROBOT)->x;
    auto* pImage = m_pGraph->GetImage(IMAGE_ROBOT);
    if (!pImage) return;
    // 获取当前角度(已通过 RotateRobot 设置)
    float angleDegrees = pImage->angle;
    float radians = angleDegrees * 3.1415926f / 180.0f;
    int startX = pImage->x;
    int endX = static_cast<int>(170 + percentage * (700 - 170));
    int y = 270;
    int cy = y + pImage->bmHeight / 2;
    int arm1Offset = 20;  // 从图片到ARM1的偏移
    int arm2Offset = 73;  // 从图片到ARM2的偏移
    // 计算移动所需的时间
    // 动画时间
    int distance = abs(endX - startX);
    int duration = static_cast<int>((distance / 100.0) * 1000);
    int duration = static_cast<int>((distance / 100.0f) * 1000);
    auto startTime = std::chrono::steady_clock::now();
    auto endTime = startTime + std::chrono::milliseconds(duration);
    // 开始移动,设置标记
    m_bIsRobotMoving = TRUE;
    // 开始平滑移动
    while (std::chrono::steady_clock::now() < endTime) {
        auto currentTime = std::chrono::steady_clock::now();
        float progress = std::chrono::duration<float, std::milli>(currentTime - startTime).count() / duration;
        progress = min(progress, 1.0f);
        // 根据进度计算当前位置
        int currentX = static_cast<int>(startX + progress * (endX - startX));
        m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, currentX, 270);
        m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, currentX + arm1Offset, 294);
        m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, currentX + arm2Offset, 294);
        int cx = currentX + pImage->bmWidth / 2;
        // 刷新界面
        // 旋转后的偏移
        int rotatedX1 = static_cast<int>(cos(radians) * g_arm1Offset.x - sin(radians) * g_arm1Offset.y);
        int rotatedY1 = static_cast<int>(sin(radians) * g_arm1Offset.x + cos(radians) * g_arm1Offset.y);
        int rotatedX2 = static_cast<int>(cos(radians) * g_arm2Offset.x - sin(radians) * g_arm2Offset.y);
        int rotatedY2 = static_cast<int>(sin(radians) * g_arm2Offset.x + cos(radians) * g_arm2Offset.y);
        // 应用所有元素的新坐标
        m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, currentX, y);
        m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, cx + rotatedX1, cy + rotatedY1);
        m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, cx + rotatedX2, cy + rotatedY2);
        Invalidate();
        UpdateWindow();
        // 控制帧率约为 60 FPS
        std::this_thread::sleep_for(std::chrono::milliseconds(16));
    }
    // 确保最后位置精确到目标位置
    m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, endX, 270);
    m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, endX + arm1Offset, 294);
    m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, endX + arm2Offset, 294);
    // 最终位置校正
    int cx = endX + pImage->bmWidth / 2;
    int rotatedX1 = static_cast<int>(cos(radians) * g_arm1Offset.x - sin(radians) * g_arm1Offset.y);
    int rotatedY1 = static_cast<int>(sin(radians) * g_arm1Offset.x + cos(radians) * g_arm1Offset.y);
    int rotatedX2 = static_cast<int>(cos(radians) * g_arm2Offset.x - sin(radians) * g_arm2Offset.y);
    int rotatedY2 = static_cast<int>(sin(radians) * g_arm2Offset.x + cos(radians) * g_arm2Offset.y);
    // 界面重绘
    m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, endX, y);
    m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, cx + rotatedX1, cy + rotatedY1);
    m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, cx + rotatedX2, cy + rotatedY2);
    Invalidate();
    // 动画结束,设置标记
    m_bIsRobotMoving = FALSE;
}
void CPageGraph1::RotateRobot(float angleInDegrees)
{
    // 将角度转换为弧度
    float angleInRadians = static_cast<float>(std::acos(-1)) / 180.0f * angleInDegrees;
    // 获取机器人图片的当前坐标和中心
    // 获取机器人图片
    auto* pImage = m_pGraph->GetImage(IMAGE_ROBOT);
    if (!pImage) return;
    // 更新 Rotate 图片的角度,确保角度保持在 [0, 360) 范围内
    m_pGraph->UpdateImageAngle(IMAGE_ROBOT, static_cast<float>(fmod(pImage->angle + angleInDegrees + 360, 360)));
    // 修正角度为 0~360
    float finalAngle = fmod(angleInDegrees + 360.0f, 360.0f);
    m_pGraph->UpdateImageAngle(IMAGE_ROBOT, finalAngle);
    int cx = pImage->x + pImage->bmWidth / 2;  // 图片中心 X
    int cy = pImage->y + pImage->bmHeight / 2; // 图片中心 Y
    // 计算中心点
    int cx = pImage->x + pImage->bmWidth / 2;
    int cy = pImage->y + pImage->bmHeight / 2;
    // 旋转指示框的坐标
    auto* pRobot1 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM1);
    auto* pRobot2 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM2);
    // 转换角度为弧度
    float radians = angleInDegrees * 3.1415926f / 180.0f;
    if (pRobot1 && pRobot2) {
        int newArmX1 = pImage->x + 20;
        int newArmY1 = 294;
    // 旋转 offset1
    int rotatedX1 = static_cast<int>(cos(radians) * g_arm1Offset.x - sin(radians) * g_arm1Offset.y);
    int rotatedY1 = static_cast<int>(sin(radians) * g_arm1Offset.x + cos(radians) * g_arm1Offset.y);
        int newArmX2 = pImage->x + 73;
        int newArmY2 = 294;
    // 旋转 offset2
    int rotatedX2 = static_cast<int>(cos(radians) * g_arm2Offset.x - sin(radians) * g_arm2Offset.y);
    int rotatedY2 = static_cast<int>(sin(radians) * g_arm2Offset.x + cos(radians) * g_arm2Offset.y);
        if (angleInDegrees != 0.0f) {
            // 计算指示框1的新坐标
            newArmX1 = static_cast<int>(cx + (pRobot1->x - cx) * cos(angleInRadians) - (pRobot1->y - cy) * sin(angleInRadians));
            newArmY1 = static_cast<int>(cy + (pRobot1->x - cx) * sin(angleInRadians) + (pRobot1->y - cy) * cos(angleInRadians));
    // 更新指示框
    m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, cx + rotatedX1, cy + rotatedY1);
    m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, cx + rotatedX2, cy + rotatedY2);
            // 计算指示框2的新坐标
            newArmX2 = static_cast<int>(cx + (pRobot2->x - cx) * cos(angleInRadians) - (pRobot2->y - cy) * sin(angleInRadians));
            newArmY2 = static_cast<int>(cy + (pRobot2->x - cx) * sin(angleInRadians) + (pRobot2->y - cy) * cos(angleInRadians));
        }
        // 更新指示框的位置
        m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, newArmX1, newArmY1);
        m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, newArmX2, newArmY2);
    }
    // 强制重绘界面
    Invalidate();
}
@@ -435,28 +457,89 @@
    }
}
void CPageGraph1::MoveRobotToPosition(SERVO::ROBOT_POSITION position)
{
    auto it = g_positionMap.find(position);
    if (it == g_positionMap.end()) {
        TRACE("Invalid robot position: %d\n", static_cast<int>(position));
        return;
    }
    const RobotPositionMapping& mapping = it->second;
    // 平台移动
    UpdateRobotPosition(mapping.percentage);
    // 旋转方向
    RotateRobot(mapping.angle);
    m_lastRobotPosition = position;
}
void CPageGraph1::OnGraphItemClicked(NMHDR* pNMHDR, LRESULT* pResult)
{
    BYSERVOGRAPH_NMHDR* pGraphNmhdr = reinterpret_cast<BYSERVOGRAPH_NMHDR*>(pNMHDR);
    // 移动到指定位置 (测试使用)
    if (pGraphNmhdr->dwData == INDICATE_LPORT1) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Port1);
    }
    else if (pGraphNmhdr->dwData == INDICATE_LPORT2) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Port2);
    }
    else if (pGraphNmhdr->dwData == INDICATE_LPORT3) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Port3);
    }
    else if (pGraphNmhdr->dwData == INDICATE_LPORT4) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Port4);
    }
    else if (pGraphNmhdr->dwData == INDICATE_ALIGNER) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Aligner);
    }
    else if (pGraphNmhdr->dwData == INDICATE_FLIPER) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Fliper);
    }
    else if (pGraphNmhdr->dwData == INDICATE_BONDER1) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Bonder1);
    }
    else if (pGraphNmhdr->dwData == INDICATE_BONDER2) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Bonder2);
    }
    else if (pGraphNmhdr->dwData == INDICATE_VACUUM_BAKE) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Bake);
    }
    else if (pGraphNmhdr->dwData == INDICATE_BAKE_COOLING) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Cooling);
    }
    else if (pGraphNmhdr->dwData == INDICATE_MEASUREMENT) {
        MoveRobotToPosition(SERVO::ROBOT_POSITION::Measurement);
    }
    CString s; s.Format(_T("OnGraphItemClicked %d"), pGraphNmhdr->dwData);
    SERVO::CEquipment* pEquipment = (SERVO::CEquipment*)m_pGraph->GetIndicateBoxData(pGraphNmhdr->dwData);
    if (pEquipment != nullptr) {
        theApp.m_model.notifyPtr(RX_CODE_SELECT_EQUIPMENT, pEquipment);
    }
    *pResult = 0;
}
void CPageGraph1::OnTimer(UINT_PTR nIDEvent)
{
    if (1 == nIDEvent) {
        KillTimer(1);
    if (TIMER_ID_DEVICE_STATUS == nIDEvent) {
        KillTimer(TIMER_ID_DEVICE_STATUS);
        // 更新状态
        {
            SERVO::CEquipment* pEquipment = (SERVO::CEFEM*)theApp.m_model.m_master.getEquipment(EQ_ID_EFEM);
            ASSERT(pEquipment);
            DeviceStatus status = pEquipment->isAlive() ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
            UpdateDeviceStatus(INDICATE_FLIPER, status);
            UpdateDeviceStatus(INDICATE_ALIGNER, status);
            UpdateDeviceStatus(INDICATE_LPORT1, status);
            UpdateDeviceStatus(INDICATE_LPORT2, status);
            UpdateDeviceStatus(INDICATE_LPORT3, status);
            UpdateDeviceStatus(INDICATE_LPORT4, status);
            UpdateDeviceStatus(INDICATE_ROBOT_ARM1, status);
            UpdateDeviceStatus(INDICATE_ROBOT_ARM2, status);
        }
@@ -474,6 +557,45 @@
            DeviceStatus status = pEquipment->isAlive() ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
            UpdateDeviceStatus(INDICATE_BONDER2, status);
        }
        {
            SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(EQ_ID_VACUUMBAKE);
            ASSERT(pEquipment);
            DeviceStatus status = pEquipment->isAlive() ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
            UpdateDeviceStatus(INDICATE_VACUUM_BAKE, status);
        }
        {
            SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(EQ_ID_BAKE_COOLING);
            ASSERT(pEquipment);
            DeviceStatus status = pEquipment->isAlive() ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
            UpdateDeviceStatus(INDICATE_BAKE_COOLING, status);
        }
        {
            SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(EQ_ID_MEASUREMENT);
            ASSERT(pEquipment);
            DeviceStatus status = pEquipment->isAlive() ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
            UpdateDeviceStatus(INDICATE_MEASUREMENT, status);
        }
    }
    else if (nIDEvent == TIMER_ID_ROBOT_STATUS) {
        SERVO::CEFEM* pEFEM = (SERVO::CEFEM*)theApp.m_model.m_master.getEquipment(EQ_ID_EFEM);
        if (!pEFEM || !pEFEM->isAlive()) {
            return;
        }
        // 如果设备在线,那么更新 ARM 状态
        SERVO::RMDATA& robotData = pEFEM->getRobotMonitoringData();
        DeviceStatus arm1Status = robotData.armState[0] ? DeviceStatus::OCCUPIED : DeviceStatus::ONLINE;
        DeviceStatus arm2Status = robotData.armState[1] ? DeviceStatus::OCCUPIED : DeviceStatus::ONLINE;
        UpdateDeviceStatus(INDICATE_ROBOT_ARM1, arm1Status);
        UpdateDeviceStatus(INDICATE_ROBOT_ARM2, arm2Status);
        // 位置信息状态显示
        if (robotData.position != m_lastRobotPosition) {
            MoveRobotToPosition(robotData.position);
        }
    }
    CDialogEx::OnTimer(nIDEvent);
SourceCode/Bond/Servo/CPageGraph1.h
@@ -1,12 +1,19 @@
#pragma once
#include "ServoGraph.h"
#include "ServoCommo.h"
enum DeviceStatus {
    ONLINE,       // 在线
    OFFLINE,      // 离线
    OCCUPIED      // 有片(占用)
};
struct RobotPositionMapping {
    SERVO::ROBOT_POSITION position;
    float percentage;
    float angle;
    int arm; // 0 表示 ARM1,1 表示 ARM2
};
// CPageGraph1 对话框
@@ -25,6 +32,7 @@
    void UpdateRobotPosition(float percentage);
    void RotateRobot(float angleInDegrees);
    void BindEquipmentToGraph();
    void MoveRobotToPosition(SERVO::ROBOT_POSITION position);
private:
    IObserver* m_pObserver;
@@ -32,6 +40,7 @@
    BOOL m_bIsRobotMoving;
    COLORREF m_crBkgnd;
    HBRUSH m_hbrBkgnd;
    SERVO::ROBOT_POSITION m_lastRobotPosition;
// 对话框数据
#ifdef AFX_DESIGN_TIME
SourceCode/Bond/Servo/Common.h
@@ -34,6 +34,7 @@
#define PAGE_GRPAH2_BACKGROUND_COLOR        RGB(255, 255, 255)
#define EQ_BOX_OFFLINE                        RGB(222, 222, 222)
#define EQ_BOX_ONLINE                        RGB(0, 176, 80)
#define EQ_BOX_OCCUPIED                     RGB(0, 204, 102)
#define EQ_BOX_FRAME1                        RGB(22, 22, 22)
#define EQ_BOX_FRAME2                        RGB(255, 127, 39)
#define CR_MSGBOX_BKGND                        RGB(7, 71, 166)
@@ -79,6 +80,49 @@
#define EQ_ID_ARM                101
#define EQ_ID_OPERATOR_REMOVE    102
/* Equipment Name */
#define EQ_NAME_LOADPORT1            "LoadPort1"
#define EQ_NAME_LOADPORT2            "LoadPort2"
#define EQ_NAME_LOADPORT3            "LoadPort3"
#define EQ_NAME_LOADPORT4            "LoadPort4"
#define EQ_NAME_ARM_TRAY1            "ArmTray1"
#define EQ_NAME_ARM_TRAY2            "ArmTray2"
#define EQ_NAME_ALIGNER                "Aligner"
#define EQ_NAME_FLIPER                "Fliper"
#define EQ_NAME_VACUUMBAKE            "VacuumBake"
#define EQ_NAME_BONDER1                "Bonder1"
#define EQ_NAME_BONDER2                "Bonder2"
#define EQ_NAME_BAKE_COOLING        "BakeCooling"
#define EQ_NAME_MEASUREMENT            "Measurement"
#define EQ_NAME_EFEM                "EFEM"
#define EQ_NAME_ARM                    "Arm"
#define EQ_NAME_OPERATOR_REMOVE        "OperatorRemove"
// 设备元信息结构体
struct DeviceMetaInfo {
    int nDeviceID;
    const char* strDeviceName;  // 指针,仅指向常量字符串
};
// 全局设备元信息列表
static const DeviceMetaInfo g_allDeviceMetaInfos[] = {
    {EQ_ID_LOADPORT1, EQ_NAME_LOADPORT1},
    {EQ_ID_LOADPORT2, EQ_NAME_LOADPORT2},
    {EQ_ID_LOADPORT3, EQ_NAME_LOADPORT3},
    {EQ_ID_LOADPORT4, EQ_NAME_LOADPORT4},
    {EQ_ID_ARM_TRAY1, EQ_NAME_ARM_TRAY1},
    {EQ_ID_ARM_TRAY2, EQ_NAME_ARM_TRAY2},
    {EQ_ID_ALIGNER,   EQ_NAME_ALIGNER},
    {EQ_ID_FLIPER,    EQ_NAME_FLIPER},
    {EQ_ID_VACUUMBAKE, EQ_NAME_VACUUMBAKE},
    {EQ_ID_Bonder1, EQ_NAME_BONDER1},
    {EQ_ID_Bonder2, EQ_NAME_BONDER2},
    {EQ_ID_BAKE_COOLING, EQ_NAME_BAKE_COOLING},
    {EQ_ID_MEASUREMENT, EQ_NAME_MEASUREMENT},
    {EQ_ID_EFEM, EQ_NAME_EFEM},
    {EQ_ID_ARM, EQ_NAME_ARM},
    {EQ_ID_OPERATOR_REMOVE, EQ_NAME_OPERATOR_REMOVE},
};
/* step name */
#define STEP_MODE                        _T("EQMode")
SourceCode/Bond/Servo/RecipeDeviceBindDlg.cpp
@@ -5,10 +5,22 @@
#include "Servo.h"
#include "afxdialogex.h"
#include "RecipeDeviceBindDlg.h"
#include "RecipeManager.h"
#include "Common.h"
#define IDC_EDIT_DEVICEID_BASE     3000
#define IDC_EDIT_DEVICENAME_BASE   3050
#define IDC_COMBO_RECIPEID_BASE    3100
// 绑定界面需要显示的设备
static const std::vector<DeviceMetaInfo> g_vecBindDevices = {
    { EQ_ID_VACUUMBAKE,      EQ_NAME_VACUUMBAKE },
    { EQ_ID_Bonder1,         EQ_NAME_BONDER1 },
    { EQ_ID_Bonder2,         EQ_NAME_BONDER2 },
    { EQ_ID_BAKE_COOLING,    EQ_NAME_BAKE_COOLING },
    { EQ_ID_MEASUREMENT,     EQ_NAME_MEASUREMENT },
    { EQ_ID_EFEM,            EQ_NAME_EFEM }
};
// CRecipeDeviceBindDlg 对话框
@@ -31,6 +43,8 @@
BEGIN_MESSAGE_MAP(CRecipeDeviceBindDlg, CDialogEx)
    ON_WM_CLOSE()
    ON_WM_SIZE()
END_MESSAGE_MAP()
@@ -50,24 +64,64 @@
    GetClientRect(&clientRect);
    int xStart = (clientRect.Width() - totalControlWidth) / 2;
    const int nRowCount = 8;
    const int nRowHeight = 30;
    const int yStart = 30; // 顶部起始高度
    for (int i = 0; i < nRowCount; ++i)
    {
    const int nRowCount = static_cast<int>(g_vecBindDevices.size());
    for (int i = 0; i < nRowCount; ++i) {
        int y = yStart + i * nRowHeight;
        const auto& meta = g_vecBindDevices[i];
        CEdit* pEditID = new CEdit();
        pEditID->Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(xStart, y, xStart + 100, y + 25), this, IDC_EDIT_DEVICEID_BASE + i);
        CString strID;
        strID.Format(_T("%d"), meta.nDeviceID);
        pEditID->SetWindowText(strID);
        pEditID->SetReadOnly(TRUE);     // 设备ID只读
        CEdit* pEditName = new CEdit();
        pEditName->Create(WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(xStart + 110, y, xStart + 210, y + 25), this, IDC_EDIT_DEVICENAME_BASE + i);
        pEditName->SetWindowText(CA2T(meta.strDeviceName));
        pEditName->SetReadOnly(TRUE);   // 设备名称只读
        CComboBox* pCombo = new CComboBox();
        pCombo->Create(WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, CRect(xStart + 220, y, xStart + 340, y + 300), this, IDC_COMBO_RECIPEID_BASE + i);
        // 添加选项到 ComboBox
        m_vecDevices.push_back({ pEditID, pEditName, pCombo });
    }
    return TRUE;  // return TRUE unless you set the focus to a control
    // 异常: OCX 属性页应返回 FALSE
}
void CRecipeDeviceBindDlg::OnClose()
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    CDialogEx::OnClose();
    // 清理控件
    for (auto& device : m_vecDevices) {
        if (device.editDeviceID) {
            device.editDeviceID->DestroyWindow();
            delete device.editDeviceID;
        }
        if (device.editDeviceName) {
            device.editDeviceName->DestroyWindow();
            delete device.editDeviceName;
        }
        if (device.comboRecipeID) {
            device.comboRecipeID->DestroyWindow();
            delete device.comboRecipeID;
        }
    }
    m_vecDevices.clear();
}
void CRecipeDeviceBindDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    // TODO: 在此处添加消息处理程序代码
}
SourceCode/Bond/Servo/RecipeDeviceBindDlg.h
@@ -20,13 +20,15 @@
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
    virtual BOOL OnInitDialog();
    afx_msg void OnClose();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    DECLARE_MESSAGE_MAP()
private:
    struct DeviceWidget {
        CEdit editDeviceID;
        CEdit editDeviceName;
        CComboBox comboRecipeID;
        CEdit* editDeviceID;
        CEdit* editDeviceName;
        CComboBox* comboRecipeID;
    };
    std::vector<DeviceWidget> m_vecDevices;
SourceCode/Bond/Servo/RecipeManager.cpp
@@ -238,6 +238,29 @@
    return recipes;
}
std::vector<RecipeInfo> RecipeManager::getRecipesByKeyword(const std::string& keyword) {
    std::vector<RecipeInfo> recipes;
    if (!m_pDB || keyword.empty()) {
        return recipes;
    }
    std::ostringstream query;
    query << "SELECT ppid, description, create_time FROM recipes "
        << "WHERE ppid LIKE '%" << keyword << "%' OR description LIKE '%" << keyword << "%';";
    auto rows = m_pDB->fetchResults(query.str());
    for (const auto& row : rows) {
        if (row.size() >= 3) {
            RecipeInfo info;
            info.strPPID = row[0];
            info.strDescription = row[1];
            info.strCreateTime = row[2];
            recipes.push_back(info);
        }
    }
    return recipes;
}
std::vector<std::string> RecipeManager::getAllPPID() const {
    std::vector<std::string> vecPPID;
SourceCode/Bond/Servo/RecipeManager.h
@@ -58,6 +58,9 @@
    // 查询所有配方
    std::vector<RecipeInfo> getAllRecipes();
    // 根据 PPID 或描述查询配方
    std::vector<RecipeInfo> getRecipesByKeyword(const std::string& keyword);
    // 获取所有 PPID
    std::vector<std::string> getAllPPID() const;