a056810ab4c5fa63777f6fdddfeb190bbd9a6f54..c62dbec7328a8b44e6ec61758e7b8463f2e502dd
2025-09-12 LAPTOP-SNT8I5JK\Boounion
Merge branch 'liuyang'
c62dbe 对比 | 目录
2025-09-12 LAPTOP-SNT8I5JK\Boounion
Merge branch 'clh' of http://soft.boounion.cn/r/~chenluhua/Bond2 into clh
e2641f 对比 | 目录
2025-09-12 LAPTOP-SNT8I5JK\Boounion
1.PageGlassList,修改字段名,简化显示内容;
3ad367 对比 | 目录
2025-09-12 LAPTOP-SNT8I5JK\Boounion
1.PageGlassList,修改字段名,简化显示内容;
dcd55d 对比 | 目录
2025-09-12 LAPTOP-SNT8I5JK\Boounion
1.PanelDataReport事件上抛,以便上层检测到AOI 检测NG后停机; 2.CessetteSn种子数设定,保存到配置,以便程序关闭重开后...
3a9d54 对比 | 目录
2025-09-12 mrDarker
1. SG精度检文件字符集全部修改成UTF-8
49cab6 对比 | 目录
2025-09-12 mrDarker
1. 修复SG精度检编辑框修改覆盖问题(界面频繁刷新导致)
8d55ed 对比 | 目录
2025-09-12 mrDarker
Merge branch 'clh' into liuyang
0fb528 对比 | 目录
2025-09-12 mrDarker
1. 优化报警的查询功能 2. 插入设备表数据和单元表数据 3. 修复报警查询失败的问题
112601 对比 | 目录
2025-09-11 mrDarker
1. 把数值放大1000倍 2. 发送PLC是双字
1310b3 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.查询时多取一行,使最后一行总是能配对上
27dcc9 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.修复数据库类联G1和Buddy的问题;
29f900 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.修复Buddy玻璃不保存数据库的问题; 2.工艺参数,将豆号修改为换行符号弹出消息框显示;
56c798 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.Glass的状态切换; 2.重新组织机器中Glass的序列化和反序列化;
71766e 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.修复一处Path转文字出错的问题;
da9868 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.超长内容弹出消息框显示; 2.修复WIP增加或减少,报表没有对应动作的问题;
b9f5d0 对比 | 目录
2025-09-11 mrDarker
Merge branch 'clh' into liuyang
d64036 对比 | 目录
2025-09-11 mrDarker
1. SG精度检写入日志文件 2. SG精度检结果可以是负数 3. 添加读取产品ID功能
874993 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.更新工艺参数时,buddy也要一并更新;
7decda 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.修复buddy释放后未置空造成退出程序时发生异常的问题;
dbd827 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.修改微调WIP子项数据的背景为更浅的绿色; 2.已绑定的Glass路径跟随主Glass
6b96e3 对比 | 目录
2025-09-11 LAPTOP-SNT8I5JK\Boounion
1.可折叠ListCtrl应用到GlassList页,优化,模拟数据测试等;
eaf39c 对比 | 目录
2025-09-10 LAPTOP-SNT8I5JK\Boounion
1.将CListCtrlEx功能移到CExpandableListCtrl。
296cea 对比 | 目录
2025-09-10 LAPTOP-SNT8I5JK\Boounion
1.处理行的颜色问题;
4f6bc5 对比 | 目录
2025-09-10 LAPTOP-SNT8I5JK\Boounion
1.GlassLog页,增加一个隐藏列;
7876ef 对比 | 目录
2025-09-10 LAPTOP-SNT8I5JK\Boounion
1.一些输出日志信息,更新等级; 2.CPath增加Slot信息; 3.WIP数据显示到列表控件; 4.调整Glass的Path信息文本输出;
0a0b06 对比 | 目录
2025-09-09 LAPTOP-SNT8I5JK\Boounion
1.Glass数据库保存,查询,分页,关键字,时间段等功能;
c119b8 对比 | 目录
2025-09-09 LAPTOP-SNT8I5JK\Boounion
1.修改Bonder的SVData数据解释,其中一项压力有符号,增加一个加热剩余时间; 2.SVData解释偏移错误修复
d6fabe 对比 | 目录
2025-09-09 LAPTOP-SNT8I5JK\Boounion
1.VCR事件更新GlassID
4d64ab 对比 | 目录
2025-09-09 LAPTOP-SNT8I5JK\Boounion
1.Glass增加对应数据库字段的接口;
be5a67 对比 | 目录
2025-09-09 LAPTOP-SNT8I5JK\Boounion
1.VCR转移到Aligner
ee4945 对比 | 目录
已添加8个文件
已删除2个文件
已修改70个文件
278059 ■■■■■ 文件已修改
Document/EO2860AVA-101工艺参数(1).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/EO2860AVA-101工艺参数(2).xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/EO2860AVA-101工艺参数.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
Document/Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.cpp 136 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.h 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.cpp 2380 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.h 412 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/Logger.cpp 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/Logger.h 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp 421 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.h 275 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj.user 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/framework.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/pch.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/pch.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/resource.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/targetver.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmManager.cpp 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmManager.h 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CAligner.cpp 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBakeCooling.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CBonder.cpp 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEFEM.cpp 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqAlarmStep.cpp 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqJobEventStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqPortChangeStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqProcessStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqReadIntStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqReadStep.cpp 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqStatusStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEqVcrEventStep.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.cpp 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipment.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CEquipmentPage3.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CExpandableListCtrl.cpp 460 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CExpandableListCtrl.h 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CFliper.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.cpp 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.h 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CJobDataS.cpp 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CJobDataS.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.h 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMeasurement.cpp 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageCassetteCtrlCmd.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.cpp 1712 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.h 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPath.cpp 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPath.h 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTask.cpp 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CSVData.cpp 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CServoUtilsTool.cpp 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CServoUtilsTool.h 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Configuration.cpp 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Configuration.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/DevicePropertyDlg.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassJson.cpp 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassLogDb.cpp 526 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassLogDb.h 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ListCtrlEx.cpp 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageAlarm.cpp 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.cpp 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.h 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/sqlite3.c 255811 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/sqlite3.h 13357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/sqlite3ext.h 719 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Document/EO2860AVA-101¹¤ÒÕ²ÎÊý(1).xlsx
Binary files differ
Document/EO2860AVA-101¹¤ÒÕ²ÎÊý(2).xlsx
Binary files differ
Document/EO2860AVA-101¹¤ÒÕ²ÎÊý.xlsx
Binary files differ
Document/Panel Bonder°ËÁãÁªºÏ SecsTest CheckList_v3.0.xlsx
Binary files differ
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.cpp
@@ -6,99 +6,99 @@
CCCLinkIEControl::~CCCLinkIEControl() = default;
int CCCLinkIEControl::SetBoardModeEx(CCLinkIEControlMode mode) {
    return SetBoardMode(static_cast<short>(mode));
    return SetBoardMode(static_cast<short>(mode));
}
CCLinkIEControlMode CCCLinkIEControl::GetBoardModeEx() {
    short nMode = 0;
    const int nResult = GetBoardMode(nMode);
    if (nResult != 0) {
        return CCLinkIEControlMode::UNKNOWN;
    }
    short nMode = 0;
    const int nResult = GetBoardMode(nMode);
    if (nResult != 0) {
        return CCLinkIEControlMode::UNKNOWN;
    }
    return ConvertToCCLinkIEControlMode(nMode);
    return ConvertToCCLinkIEControlMode(nMode);
}
int CCCLinkIEControl::GetBoardStatusEx(BoardStatus& status) {
    const int nResult = GetBoardStatus(status);
    if (nResult != 0) {
        return nResult;
    }
    const int nResult = GetBoardStatus(status);
    if (nResult != 0) {
        return nResult;
    }
    return ValidateBoardStatus(status);
    return ValidateBoardStatus(status);
}
int CCCLinkIEControl::ReadLedStatus(LedStatus& outLedStatus) {
    std::vector<short> vecLedBuffer;
    const int nRet = ReadBoardLed(vecLedBuffer);
    if (nRet != 0) {
        return nRet;
    }
    std::vector<short> vecLedBuffer;
    const int nRet = ReadBoardLed(vecLedBuffer);
    if (nRet != 0) {
        return nRet;
    }
    if (vecLedBuffer.empty()) {
        UpdateLastError(ERROR_CODE_INVALID_DATA);
        return ERROR_CODE_INVALID_DATA;
    }
    if (vecLedBuffer.empty()) {
        UpdateLastError(ERROR_CODE_INVALID_DATA);
        return ERROR_CODE_INVALID_DATA;
    }
    // è§£æžå„位状态
    const short nBuffer = vecLedBuffer[0];
    outLedStatus.bExtPw = (nBuffer & (1 << 15)) != 0;
    outLedStatus.bRd = (nBuffer & (1 << 6)) != 0;
    outLedStatus.bDLnk = (nBuffer & (1 << 5)) != 0;
    outLedStatus.bPrm = (nBuffer & (1 << 4)) != 0;
    outLedStatus.bErr = (nBuffer & (1 << 3)) != 0;
    outLedStatus.bSd = (nBuffer & (1 << 2)) != 0;
    outLedStatus.bMode = (nBuffer & (1 << 1)) != 0;
    outLedStatus.bRun = (nBuffer & (1 << 0)) != 0;
    // è§£æžå„位状态
    const short nBuffer = vecLedBuffer[0];
    outLedStatus.bExtPw = (nBuffer & (1 << 15)) != 0;
    outLedStatus.bRd = (nBuffer & (1 << 6)) != 0;
    outLedStatus.bDLnk = (nBuffer & (1 << 5)) != 0;
    outLedStatus.bPrm = (nBuffer & (1 << 4)) != 0;
    outLedStatus.bErr = (nBuffer & (1 << 3)) != 0;
    outLedStatus.bSd = (nBuffer & (1 << 2)) != 0;
    outLedStatus.bMode = (nBuffer & (1 << 1)) != 0;
    outLedStatus.bRun = (nBuffer & (1 << 0)) != 0;
    return 0;
    return 0;
}
CCLinkIEControlMode CCCLinkIEControl::ConvertToCCLinkIEControlMode(const short nMode) {
    switch (static_cast<CCLinkIEControlMode>(nMode)) {
        case CCLinkIEControlMode::ONLINE: return CCLinkIEControlMode::ONLINE;                           // åœ¨çº¿
        case CCLinkIEControlMode::OFFLINE: return CCLinkIEControlMode::OFFLINE;                         // ç¦»çº¿
        case CCLinkIEControlMode::INTER_STATION_TEST: return CCLinkIEControlMode::INTER_STATION_TEST;   // ç«™é—´æµ‹è¯•
        case CCLinkIEControlMode::LINE_TEST: return CCLinkIEControlMode::LINE_TEST;                     // çº¿è·¯æµ‹è¯•
        case CCLinkIEControlMode::LOOPBACK_TEST: return CCLinkIEControlMode::LOOPBACK_TEST;             // è‡ªå›žé€æµ‹è¯•
        case CCLinkIEControlMode::HW_TEST: return CCLinkIEControlMode::HW_TEST;                         // H/W测试
        case CCLinkIEControlMode::BUS_IF_TEST: return CCLinkIEControlMode::BUS_IF_TEST;                 // æ€»çº¿I/F测试
        default: return CCLinkIEControlMode::UNKNOWN;
    }
    switch (static_cast<CCLinkIEControlMode>(nMode)) {
        case CCLinkIEControlMode::ONLINE: return CCLinkIEControlMode::ONLINE;                           // åœ¨çº¿
        case CCLinkIEControlMode::OFFLINE: return CCLinkIEControlMode::OFFLINE;                         // ç¦»çº¿
        case CCLinkIEControlMode::INTER_STATION_TEST: return CCLinkIEControlMode::INTER_STATION_TEST;   // ç«™é—´æµ‹è¯•
        case CCLinkIEControlMode::LINE_TEST: return CCLinkIEControlMode::LINE_TEST;                     // çº¿è·¯æµ‹è¯•
        case CCLinkIEControlMode::LOOPBACK_TEST: return CCLinkIEControlMode::LOOPBACK_TEST;             // è‡ªå›žé€æµ‹è¯•
        case CCLinkIEControlMode::HW_TEST: return CCLinkIEControlMode::HW_TEST;                         // H/W测试
        case CCLinkIEControlMode::BUS_IF_TEST: return CCLinkIEControlMode::BUS_IF_TEST;                 // æ€»çº¿I/F测试
        default: return CCLinkIEControlMode::UNKNOWN;
    }
}
int CCCLinkIEControl::ValidateBoardStatus(const BoardStatus& status) {
    if (status.nStationValue < 1 || status.nStationValue > 120) {
        return ERROR_CODE_STATION_OUT_OF_RANGE; // ç«™å·è¶…出范围
    }
    if (status.nGroupValue < 0 || status.nGroupValue > 32) {
        return ERROR_CODE_GROUP_OUT_OF_RANGE;   // ç»„超出范围
    }
    if (status.nNetworkValue < 1 || status.nNetworkValue > 239) {
        return ERROR_CODE_NETWORK_OUT_OF_RANGE; // ç½‘络号超出范围
    }
    return 0; // æ ¡éªŒé€šè¿‡
    if (status.nStationValue < 1 || status.nStationValue > 120) {
        return ERROR_CODE_STATION_OUT_OF_RANGE; // ç«™å·è¶…出范围
    }
    if (status.nGroupValue < 0 || status.nGroupValue > 32) {
        return ERROR_CODE_GROUP_OUT_OF_RANGE;   // ç»„超出范围
    }
    if (status.nNetworkValue < 1 || status.nNetworkValue > 239) {
        return ERROR_CODE_NETWORK_OUT_OF_RANGE; // ç½‘络号超出范围
    }
    return 0; // æ ¡éªŒé€šè¿‡
}
int CCCLinkIEControl::ReadDataEx(const StationIdentifier& station, DeviceType enDevType, long devNo, long size, void* pData)
{
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, static_cast<short>(size));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, static_cast<short>(size));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, devNo, &size, pData);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, devNo, &size, pData);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
    }
    return nRet;
    return nRet;
}
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.h
@@ -4,66 +4,66 @@
#include "PerformanceMelsec.h"
enum class CCLinkIEControlMode : short {
    UNKNOWN = 0x0194,               // Î´Öª
    ONLINE = 0x0000,                // åœ¨çº¿
    OFFLINE = 0x0002,               // ç¦»çº¿
    INTER_STATION_TEST = 0x0005,    // ç«™é—´æµ‹è¯•
    LINE_TEST = 0x0006,             // çº¿è·¯æµ‹è¯•
    LOOPBACK_TEST = 0x0007,         // è‡ªå›žé€æµ‹è¯•
    HW_TEST = 0x0009,               // H/W测试
    BUS_IF_TEST = 0x000E            // æ€»çº¿I/F测试
    UNKNOWN = 0x0194,               // æœªçŸ¥
    ONLINE = 0x0000,                // åœ¨çº¿
    OFFLINE = 0x0002,               // ç¦»çº¿
    INTER_STATION_TEST = 0x0005,    // ç«™é—´æµ‹è¯•
    LINE_TEST = 0x0006,             // çº¿è·¯æµ‹è¯•
    LOOPBACK_TEST = 0x0007,         // è‡ªå›žé€æµ‹è¯•
    HW_TEST = 0x0009,               // H/W测试
    BUS_IF_TEST = 0x000E            // æ€»çº¿I/F测试
};
class CCCLinkIEControl final : public CPerformanceMelsec {
public:
    CCCLinkIEControl();
    ~CCCLinkIEControl() override;
    CCCLinkIEControl();
    ~CCCLinkIEControl() override;
    struct LedStatus {
        bool bExtPw; // å¤–部电源状态 (b15)
        bool bRd;    // æ•°æ®æŽ¥æ”¶çŠ¶æ€ (b6)
        bool bDLnk;  // æ•°æ®é“¾æŽ¥çŠ¶æ€ (b5)
        bool bPrm;   // ç®¡ç†åŠŸèƒ½çŠ¶æ€ (b4)
        bool bErr;   // é”™è¯¯çŠ¶æ€ (b3)
        bool bSd;    // æ•°æ®å‘送状态 (b2)
        bool bMode;  // åŠ¨ä½œæ¨¡å¼ (b1)
        bool bRun;   // è¿è¡ŒçŠ¶æ€ (b0)
    struct LedStatus {
        bool bExtPw; // å¤–部电源状态 (b15)
        bool bRd;    // æ•°æ®æŽ¥æ”¶çŠ¶æ€ (b6)
        bool bDLnk;  // æ•°æ®é“¾æŽ¥çŠ¶æ€ (b5)
        bool bPrm;   // ç®¡ç†åŠŸèƒ½çŠ¶æ€ (b4)
        bool bErr;   // é”™è¯¯çŠ¶æ€ (b3)
        bool bSd;    // æ•°æ®å‘送状态 (b2)
        bool bMode;  // åŠ¨ä½œæ¨¡å¼ (b1)
        bool bRun;   // è¿è¡ŒçŠ¶æ€ (b0)
        // è½¬æ¢ä¸ºå­—符串,用于调试
        std::string ToString() const {
            std::ostringstream oss;
            oss << "CC-Link IE Control Network LED Status: {"
                << "\n  Ext Power: " << (bExtPw ? "ON" : "OFF")
                << "\n  Receive Data: " << (bRd ? "Receiving" : "Not Receiving")
                << "\n  Data Link: " << (bDLnk ? "Linked" : "Not Linked")
                << "\n  Management: " << (bPrm ? "Managing" : "Not Managing")
                << "\n  Error: " << (bErr ? "Error Detected" : "No Error")
                << "\n  Send Data: " << (bSd ? "Sending" : "Not Sending")
                << "\n  Mode: " << (bMode ? "Executing" : "Not Executing")
                << "\n  Run: " << (bRun ? "Running" : "Stopped")
                << "\n}";
            return oss.str();
        }
    };
        // è½¬æ¢ä¸ºå­—符串,用于调试
        std::string ToString() const {
            std::ostringstream oss;
            oss << "CC-Link IE Control Network LED Status: {"
                << "\n  Ext Power: " << (bExtPw ? "ON" : "OFF")
                << "\n  Receive Data: " << (bRd ? "Receiving" : "Not Receiving")
                << "\n  Data Link: " << (bDLnk ? "Linked" : "Not Linked")
                << "\n  Management: " << (bPrm ? "Managing" : "Not Managing")
                << "\n  Error: " << (bErr ? "Error Detected" : "No Error")
                << "\n  Send Data: " << (bSd ? "Sending" : "Not Sending")
                << "\n  Mode: " << (bMode ? "Executing" : "Not Executing")
                << "\n  Run: " << (bRun ? "Running" : "Stopped")
                << "\n}";
            return oss.str();
        }
    };
    // è¯»å–目标站点CPU类型
    // short ReadCPUCodeEx(const StationIdentifier& station, short& nCPUCode);
    // è¯»å–目标站点CPU类型
    // short ReadCPUCodeEx(const StationIdentifier& station, short& nCPUCode);
    // æ¿æ¨¡å¼èŽ·å–/设置
    int SetBoardModeEx(CCLinkIEControlMode mode);
    CCLinkIEControlMode GetBoardModeEx();
    // æ¿æ¨¡å¼èŽ·å–/设置
    int SetBoardModeEx(CCLinkIEControlMode mode);
    CCLinkIEControlMode GetBoardModeEx();
    // èŽ·å–æ¿çŠ¶æ€
    int GetBoardStatusEx(BoardStatus& status);
    // èŽ·å–æ¿çŠ¶æ€
    int GetBoardStatusEx(BoardStatus& status);
    // è¯»å–LED状态
    int ReadLedStatus(LedStatus& outLedStatus);
    // è¯»å–LED状态
    int ReadLedStatus(LedStatus& outLedStatus);
    int ReadDataEx(const StationIdentifier& station, DeviceType enDevType, long devNo, long size, void* pData);
    int ReadDataEx(const StationIdentifier& station, DeviceType enDevType, long devNo, long size, void* pData);
private:
    static CCLinkIEControlMode ConvertToCCLinkIEControlMode(short nMode);
    static int ValidateBoardStatus(const BoardStatus& status);
    static CCLinkIEControlMode ConvertToCCLinkIEControlMode(short nMode);
    static int ValidateBoardStatus(const BoardStatus& status);
};
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.cpp
@@ -23,1572 +23,1572 @@
#define LOG_DEBUG(msg)
#endif
// åˆå§‹åŒ–静态成员变量
// åˆå§‹åŒ–静态成员变量
std::unordered_map<int, std::string> CPerformanceMelsec::m_mapError = {
    // æ¿å—SDK错误码
    {0, "No error, communication successful."},
    {1, "Driver not started. The driver is not running."},
    {2, "Timeout error (board response error). Request not completed within timeout."},
    {66, "Already OPEN error. The specified channel is OPEN."},
    {68, "Path error. The specified path is invalid."},
    {69, "Unsupported function execution error."},
    {70, "Station number error. The specified station number is invalid."},
    {71, "No received data error (during RECV function)."},
    {77, "Memory allocation error / insufficient memory resources."},
    {85, "SEND/RECV channel number error."},
    {100, "Board H/W resource busy."},
    {101, "Routing exception."},
    {102, "Board driver I/F error: Failed to send request data to the board driver."},
    {103, "Board driver I/F error: Failed to receive response data from the board driver."},
    {130, "Initial software component No. Error."},
    {131, "Capacity error."},
    {133, "Parameter error."},
    {16385, "Specified target station number does not exist."},
    {16386, "Received a request that the target station cannot process."},
    {16418, "Failed to create the event history file."},
    {16420, "Failed to access the event history file."},
    {16421, "Another board driver is using the event history file."},
    {16432, "The specified soft component type does not exist."},
    {16433, "Soft component specification error: Out of range or invalid start I/O or block number."},
    {16512, "Request data exception: Invalid data or unsupported module."},
    {16685, "File association error: Failed to create the event history file."},
    {16837, "File association error: Event history file does not exist."},
    {18944, "Link association error: Network does not exist, unsupported CPU, or incorrect network No./station number."},
    {-1, "Invalid path. The specified function is not supported for this path."},
    {-2, "Start component No. error. The specified component is out of range."},
    {-3, "Capacity error. The capacity exceeds the component range."},
    {-6, "Component type error. The specified type during write is invalid."},
    {-8, "Channel No. error. The channel specified is invalid."},
    {-12, "Target path error. The specified path points to an invalid target."},
    {-13, "Write protection area error. The specified range is protected."},
    {-16, "Target path conflict. The path conflicts with write protection settings."},
    {-17, "Device not found or target not responding."},
    {-18, "Invalid target. The device does not support the operation."},
    {-19, "Invalid path operation. An unsupported path operation was executed."},
    {-31, "DLL library call failed or path not initialized."},
    {-32, "Resource timeout error. Communication timed out or exceeded resource limits."},
    {-33, "Communication timeout error. The target is not responding or timed out."},
    {-34, "Unsupported communication target error. The specified network No. or station No. points to an unsupported model."},
    {-35, "Registry access error."},
    {-36, "Registry access error."},
    {-37, "Communication initialization error. The settings for initializing the communication path are invalid."},
    {-42, "Key information error. Authentication failed."},
    {-43, "Marking event error. TC waiting event write was executed on the CPU."},
    {-61, "Marking event error. TC waiting event write was executed on the CPU."},
    {-62, "Event waiting timeout. The specified external event waiting timed out."},
    {-63, "Timeout value is out of range."},
    {-64, "Timeout value is out of range."},
    {-65, "Event waiting timeout. The specified external event waiting timed out."},
    {-66, "Timeout-induced resource shortage."},
    {-67, "Irrelevant file access execution error."},
    {-69, "Operation executed, but the module does not support the function."},
    {-70, "The target event processing module returned a rejection."},
    {-71, "The remote station did not return data correctly."},
    {-72, "Pointer error. The specified pointer value is invalid."},
    {-73, "Specified address error."},
    {-2174, "Buffer data queue exception occurred. Read/write exception to device."},
    {-7656, "Buffer data queue exception. Read/write exception to the device."},
    {-7672, "Buffer data queue exception. Read/write exception to the device."},
    {-11683, "Buffer data transfer error."},
    {-11717, "Network No. error."},
    {-11746, "Station No. error."},
    {-12128, "Buffer data send/response error."},
    {-18560, "Module mode setting error."},
    {-18572, "Communication method error."},
    {-25056, "Processor error."},
    {-26334, "Duplicate program call or illegal CPU operation."},
    {-26336, "Routing request error to a station without routing function support."},
    {-27902, "Event register timeout error."},
    {-28079, "Communication No. read error."},
    {-28080, "Communication No. incorrect error."},
    {-28136, "Unsupported function in fast mode error."},
    {-28139, "Link disconnection error."},
    {-28140, "Incorrect mode setting error."},
    {-28141, "System reboot error."},
    {-28142, "Mode error."},
    {-28143, "Hardware self-diagnosis error."},
    {-28144, "Hardware self-diagnosis error."},
    {-28150, "Data reception interruption at remote station error."},
    {-28151, "Data reception interruption at remote station error."},
    {-28153, "Data reception interruption at remote station error."},
    {-28154, "Abnormal data reception error."},
    {-28158, "Driver WDT error."},
    {-28160, "Hardware resource error."},
    {-28622, "Dedicated instruction channel in-use error."},
    {-28634, "Hardware self-diagnosis error."},
    {-28636, "Hardware self-diagnosis error."},
    // æ¿å—SDK错误码
    {0, "No error, communication successful."},
    {1, "Driver not started. The driver is not running."},
    {2, "Timeout error (board response error). Request not completed within timeout."},
    {66, "Already OPEN error. The specified channel is OPEN."},
    {68, "Path error. The specified path is invalid."},
    {69, "Unsupported function execution error."},
    {70, "Station number error. The specified station number is invalid."},
    {71, "No received data error (during RECV function)."},
    {77, "Memory allocation error / insufficient memory resources."},
    {85, "SEND/RECV channel number error."},
    {100, "Board H/W resource busy."},
    {101, "Routing exception."},
    {102, "Board driver I/F error: Failed to send request data to the board driver."},
    {103, "Board driver I/F error: Failed to receive response data from the board driver."},
    {130, "Initial software component No. Error."},
    {131, "Capacity error."},
    {133, "Parameter error."},
    {16385, "Specified target station number does not exist."},
    {16386, "Received a request that the target station cannot process."},
    {16418, "Failed to create the event history file."},
    {16420, "Failed to access the event history file."},
    {16421, "Another board driver is using the event history file."},
    {16432, "The specified soft component type does not exist."},
    {16433, "Soft component specification error: Out of range or invalid start I/O or block number."},
    {16512, "Request data exception: Invalid data or unsupported module."},
    {16685, "File association error: Failed to create the event history file."},
    {16837, "File association error: Event history file does not exist."},
    {18944, "Link association error: Network does not exist, unsupported CPU, or incorrect network No./station number."},
    {-1, "Invalid path. The specified function is not supported for this path."},
    {-2, "Start component No. error. The specified component is out of range."},
    {-3, "Capacity error. The capacity exceeds the component range."},
    {-6, "Component type error. The specified type during write is invalid."},
    {-8, "Channel No. error. The channel specified is invalid."},
    {-12, "Target path error. The specified path points to an invalid target."},
    {-13, "Write protection area error. The specified range is protected."},
    {-16, "Target path conflict. The path conflicts with write protection settings."},
    {-17, "Device not found or target not responding."},
    {-18, "Invalid target. The device does not support the operation."},
    {-19, "Invalid path operation. An unsupported path operation was executed."},
    {-31, "DLL library call failed or path not initialized."},
    {-32, "Resource timeout error. Communication timed out or exceeded resource limits."},
    {-33, "Communication timeout error. The target is not responding or timed out."},
    {-34, "Unsupported communication target error. The specified network No. or station No. points to an unsupported model."},
    {-35, "Registry access error."},
    {-36, "Registry access error."},
    {-37, "Communication initialization error. The settings for initializing the communication path are invalid."},
    {-42, "Key information error. Authentication failed."},
    {-43, "Marking event error. TC waiting event write was executed on the CPU."},
    {-61, "Marking event error. TC waiting event write was executed on the CPU."},
    {-62, "Event waiting timeout. The specified external event waiting timed out."},
    {-63, "Timeout value is out of range."},
    {-64, "Timeout value is out of range."},
    {-65, "Event waiting timeout. The specified external event waiting timed out."},
    {-66, "Timeout-induced resource shortage."},
    {-67, "Irrelevant file access execution error."},
    {-69, "Operation executed, but the module does not support the function."},
    {-70, "The target event processing module returned a rejection."},
    {-71, "The remote station did not return data correctly."},
    {-72, "Pointer error. The specified pointer value is invalid."},
    {-73, "Specified address error."},
    {-2174, "Buffer data queue exception occurred. Read/write exception to device."},
    {-7656, "Buffer data queue exception. Read/write exception to the device."},
    {-7672, "Buffer data queue exception. Read/write exception to the device."},
    {-11683, "Buffer data transfer error."},
    {-11717, "Network No. error."},
    {-11746, "Station No. error."},
    {-12128, "Buffer data send/response error."},
    {-18560, "Module mode setting error."},
    {-18572, "Communication method error."},
    {-25056, "Processor error."},
    {-26334, "Duplicate program call or illegal CPU operation."},
    {-26336, "Routing request error to a station without routing function support."},
    {-27902, "Event register timeout error."},
    {-28079, "Communication No. read error."},
    {-28080, "Communication No. incorrect error."},
    {-28136, "Unsupported function in fast mode error."},
    {-28139, "Link disconnection error."},
    {-28140, "Incorrect mode setting error."},
    {-28141, "System reboot error."},
    {-28142, "Mode error."},
    {-28143, "Hardware self-diagnosis error."},
    {-28144, "Hardware self-diagnosis error."},
    {-28150, "Data reception interruption at remote station error."},
    {-28151, "Data reception interruption at remote station error."},
    {-28153, "Data reception interruption at remote station error."},
    {-28154, "Abnormal data reception error."},
    {-28158, "Driver WDT error."},
    {-28160, "Hardware resource error."},
    {-28622, "Dedicated instruction channel in-use error."},
    {-28634, "Hardware self-diagnosis error."},
    {-28636, "Hardware self-diagnosis error."},
    // è‡ªå®šä¹‰é”™è¯¯ç 
    {ERROR_CODE_UNKNOWN, "Error: Unknown error code."},
    {ERROR_CODE_NOT_CONNECTED, "Error: Not connected to the device."},
    {ERROR_CODE_INVALID_PARAM, "Error: Invalid parameter."},
    {ERROR_CODE_INVALID_DATA, "Error: Invalid data provided."},
    {ERROR_CODE_STATION_OUT_OF_RANGE, "Error: Station number is out of range."},
    {ERROR_CODE_GROUP_OUT_OF_RANGE, "Error: Group number is out of range."},
    {ERROR_CODE_NETWORK_OUT_OF_RANGE, "Error: Network number is out of range."}
    // è‡ªå®šä¹‰é”™è¯¯ç 
    {ERROR_CODE_UNKNOWN, "Error: Unknown error code."},
    {ERROR_CODE_NOT_CONNECTED, "Error: Not connected to the device."},
    {ERROR_CODE_INVALID_PARAM, "Error: Invalid parameter."},
    {ERROR_CODE_INVALID_DATA, "Error: Invalid data provided."},
    {ERROR_CODE_STATION_OUT_OF_RANGE, "Error: Station number is out of range."},
    {ERROR_CODE_GROUP_OUT_OF_RANGE, "Error: Group number is out of range."},
    {ERROR_CODE_NETWORK_OUT_OF_RANGE, "Error: Network number is out of range."}
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CPerformanceMelsec::CPerformanceMelsec(const BoardType enBoardType) {
    m_nPath = 0;
    m_enBoardType = enBoardType;
    m_bConnected.store(false);
    m_nPath = 0;
    m_enBoardType = enBoardType;
    m_bConnected.store(false);
}
// æžæž„函数
// æžæž„函数
CPerformanceMelsec::~CPerformanceMelsec() {
    Disconnect();
    Disconnect();
}
// èŽ·å–æœ€è¿‘çš„é”™è¯¯ä¿¡æ¯
// èŽ·å–æœ€è¿‘çš„é”™è¯¯ä¿¡æ¯
std::string CPerformanceMelsec::GetLastError() const {
    return m_strLastError;
    return m_strLastError;
}
// ä¿å­˜é”™è¯¯ä¿¡æ¯
// ä¿å­˜é”™è¯¯ä¿¡æ¯
bool CPerformanceMelsec::SaveErrorInfoToFile(const std::string& filename) {
    // æ‰“开文件
    std::ofstream file(filename);
    if (!file.is_open()) {
        std::cerr << "Failed to open file for saving: " << filename << std::endl;
        return false;
    }
    // æ‰“开文件
    std::ofstream file(filename);
    if (!file.is_open()) {
        std::cerr << "Failed to open file for saving: " << filename << std::endl;
        return false;
    }
    // éåŽ†é™æ€æˆå‘˜å˜é‡ m_mapError å¹¶å°†æ¯ä¸ªé”™è¯¯ä¿¡æ¯å†™å…¥æ–‡ä»¶
    for (const auto& entry : m_mapError) {
        const int nCode = entry.first;
        const std::string& strMessage = entry.second;
        file << nCode << "|" << strMessage << "\n";
    }
    file.close();
    // éåŽ†é™æ€æˆå‘˜å˜é‡ m_mapError å¹¶å°†æ¯ä¸ªé”™è¯¯ä¿¡æ¯å†™å…¥æ–‡ä»¶
    for (const auto& entry : m_mapError) {
        const int nCode = entry.first;
        const std::string& strMessage = entry.second;
        file << nCode << "|" << strMessage << "\n";
    }
    file.close();
    return true;
    return true;
}
// åŠ è½½é”™è¯¯ä¿¡æ¯
// åŠ è½½é”™è¯¯ä¿¡æ¯
bool CPerformanceMelsec::LoadErrorInfoFromFile(const std::string& filename) {
    std::ifstream inFile(filename);
    if (!inFile.is_open()) {
        std::cerr << "Failed to open file for loading: " << filename << std::endl;
        return false;
    }
    std::ifstream inFile(filename);
    if (!inFile.is_open()) {
        std::cerr << "Failed to open file for loading: " << filename << std::endl;
        return false;
    }
    m_mapError.clear();
    std::string line;
    while (std::getline(inFile, line)) {
        std::istringstream iss(line);
        int nCode = 0;
        std::string strToken;
        std::string strMessage;
    m_mapError.clear();
    std::string line;
    while (std::getline(inFile, line)) {
        std::istringstream iss(line);
        int nCode = 0;
        std::string strToken;
        std::string strMessage;
        // ä½¿ç”¨åˆ†éš”符 "|" è§£æžæ¯ä¸€è¡Œ
        if (std::getline(iss, strToken, '|')) {
            nCode = std::stoi(strToken);
        }
        // ä½¿ç”¨åˆ†éš”符 "|" è§£æžæ¯ä¸€è¡Œ
        if (std::getline(iss, strToken, '|')) {
            nCode = std::stoi(strToken);
        }
        if (std::getline(iss, strToken)) {
            strMessage = strToken;
        }
        if (std::getline(iss, strToken)) {
            strMessage = strToken;
        }
        if (!strMessage.empty()) {
            m_mapError[nCode] = strMessage;
        }
    }
        if (!strMessage.empty()) {
            m_mapError[nCode] = strMessage;
        }
    }
    return true;
    return true;
}
// è¿žæŽ¥åˆ°PLC
// è¿žæŽ¥åˆ°PLC
int CPerformanceMelsec::Connect(const short nChannel, const short nMode) {
    std::lock_guard<std::mutex> lock(m_mtx);
    std::lock_guard<std::mutex> lock(m_mtx);
    if (m_bConnected.load()) {
        return 0;
    }
    if (m_bConnected.load()) {
        return 0;
    }
    const BoardType enBoardType = FindBoardTypeByChannel(nChannel);
    if (enBoardType == BoardType::UNKNOWN || enBoardType != m_enBoardType) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    const BoardType enBoardType = FindBoardTypeByChannel(nChannel);
    if (enBoardType == BoardType::UNKNOWN || enBoardType != m_enBoardType) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    // è¿žæŽ¥PLC,显式类型转换以匹配 mdOpen çš„签名
    const short nRet = mdOpen(nChannel, nMode, &m_nPath);
    if (nRet == 0) {
        m_bConnected.store(true);
        m_enBoardType = enBoardType;
    }
    else {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    // è¿žæŽ¥PLC,显式类型转换以匹配 mdOpen çš„签名
    const short nRet = mdOpen(nChannel, nMode, &m_nPath);
    if (nRet == 0) {
        m_bConnected.store(true);
        m_enBoardType = enBoardType;
    }
    else {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// æ–­å¼€è¿žæŽ¥
// æ–­å¼€è¿žæŽ¥
int CPerformanceMelsec::Disconnect() {
    std::lock_guard<std::mutex> lock(m_mtx);
    std::lock_guard<std::mutex> lock(m_mtx);
    short nRet = 0;
    if (m_bConnected.load()) {
        nRet = mdClose(m_nPath);
        m_bConnected.store(false);
        m_nPath = 0;
    }
    short nRet = 0;
    if (m_bConnected.load()) {
        nRet = mdClose(m_nPath);
        m_bConnected.store(false);
        m_nPath = 0;
    }
    UpdateLastError(nRet);
    LOG_DEBUG("Close connect.");
    UpdateLastError(nRet);
    LOG_DEBUG("Close connect.");
    return nRet;
    return nRet;
}
// å¯ç¼–程控制器软元件信息表的初始化
// å¯ç¼–程控制器软元件信息表的初始化
int CPerformanceMelsec::InitializeController() {
    std::lock_guard<std::mutex> lock(m_mtx);
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    const short nRet = mdInit(m_nPath);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    const short nRet = mdInit(m_nPath);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// èŽ·å–ç‰ˆæœ¬ä¿¡æ¯
// èŽ·å–ç‰ˆæœ¬ä¿¡æ¯
int CPerformanceMelsec::GetBoardVersion(BoardVersion& version) {
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    // èŽ·å–ç‰ˆæœ¬ä¿¡æ¯
    short buf[32] = { 0 };
    const short nRet = mdBdVerRead(m_nPath, buf);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
        return nRet;
    }
    // èŽ·å–ç‰ˆæœ¬ä¿¡æ¯
    short buf[32] = { 0 };
    const short nRet = mdBdVerRead(m_nPath, buf);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
        return nRet;
    }
    // å¡«å……版本信息到结构体
    version.fixedValue[0] = static_cast<char>(buf[0] & 0xFF);
    version.fixedValue[1] = static_cast<char>((buf[0] >> 8) & 0xFF);
    // å¡«å……版本信息到结构体
    version.fixedValue[0] = static_cast<char>(buf[0] & 0xFF);
    version.fixedValue[1] = static_cast<char>((buf[0] >> 8) & 0xFF);
    version.checksum[0] = static_cast<char>(buf[1] & 0xFF);
    version.checksum[1] = static_cast<char>((buf[1] >> 8) & 0xFF);
    version.checksum[0] = static_cast<char>(buf[1] & 0xFF);
    version.checksum[1] = static_cast<char>((buf[1] >> 8) & 0xFF);
    version.swVersion[0] = static_cast<char>(buf[2] & 0xFF);
    version.swVersion[1] = static_cast<char>((buf[2] >> 8) & 0xFF);
    version.swVersion[0] = static_cast<char>(buf[2] & 0xFF);
    version.swVersion[1] = static_cast<char>((buf[2] >> 8) & 0xFF);
    std::memcpy(version.date, &buf[3], 6);
    std::memcpy(version.date, &buf[3], 6);
    version.reserved = static_cast<uint32_t>(buf[6]) | (static_cast<uint32_t>(buf[7]) << 16);
    version.reserved = static_cast<uint32_t>(buf[6]) | (static_cast<uint32_t>(buf[7]) << 16);
    std::memcpy(version.swModel, &buf[8], 16);
    std::memcpy(version.hwModel, &buf[16], 16);
    std::memcpy(version.swModel, &buf[8], 16);
    std::memcpy(version.hwModel, &buf[16], 16);
    version.twoPortMemory[0] = static_cast<char>(buf[18] & 0xFF);
    version.twoPortMemory[1] = static_cast<char>((buf[18] >> 8) & 0xFF);
    version.twoPortMemory[0] = static_cast<char>(buf[18] & 0xFF);
    version.twoPortMemory[1] = static_cast<char>((buf[18] >> 8) & 0xFF);
    version.twoPortAttribute[0] = static_cast<char>(buf[19] & 0xFF);
    version.twoPortAttribute[1] = static_cast<char>((buf[19] >> 8) & 0xFF);
    version.twoPortAttribute[0] = static_cast<char>(buf[19] & 0xFF);
    version.twoPortAttribute[1] = static_cast<char>((buf[19] >> 8) & 0xFF);
    version.availableBias[0] = static_cast<char>(buf[20] & 0xFF);
    version.availableBias[1] = static_cast<char>((buf[20] >> 8) & 0xFF);
    version.availableBias[0] = static_cast<char>(buf[20] & 0xFF);
    version.availableBias[1] = static_cast<char>((buf[20] >> 8) & 0xFF);
    std::memcpy(version.moduleType, &buf[21], 10);
    std::memcpy(version.moduleType, &buf[21], 10);
    return nRet;
    return nRet;
}
// è¯»å–目标站点CPU类型
// è¯»å–目标站点CPU类型
int CPerformanceMelsec::ReadCPUCode(const StationIdentifier& station, short& nCPUCode) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        nCPUCode = 0;
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdTypeRead(m_nPath, CombineStation(station), &nCPUCode);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        nCPUCode = 0;
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdTypeRead(m_nPath, CombineStation(station), &nCPUCode);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// æ¿æ¨¡å¼è®¾ç½®
// æ¿æ¨¡å¼è®¾ç½®
int CPerformanceMelsec::SetBoardMode(const short nMode) {
    // æ£€æŸ¥æ˜¯å¦å·²ç»è¿žæŽ¥
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    // æ£€æŸ¥æ˜¯å¦å·²ç»è¿žæŽ¥
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    short nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdBdModSet(m_nPath, nMode);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    short nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdBdModSet(m_nPath, nMode);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// èŽ·å–æ¿æ¨¡å¼
// èŽ·å–æ¿æ¨¡å¼
int CPerformanceMelsec::GetBoardMode(short& nMode) {
    // æ£€æŸ¥æ˜¯å¦å·²ç»è¿žæŽ¥
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    // æ£€æŸ¥æ˜¯å¦å·²ç»è¿žæŽ¥
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    short nRet = 0;
    {
        nMode = 0;
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdBdModRead(m_nPath, &nMode);
    }
    short nRet = 0;
    {
        nMode = 0;
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdBdModRead(m_nPath, &nMode);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_DEBUG("Raw Mode: " << nMode);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_DEBUG("Raw Mode: " << nMode);
        LOG_ERROR(m_strLastError);
    }
    return 0;
    return 0;
}
// æ¿å¤ä½
// æ¿å¤ä½
int CPerformanceMelsec::BoardReset() {
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    const short nRet = mdBdRst(m_nPath);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    const short nRet = mdBdRst(m_nPath);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// æ¿LED读取
// æ¿LED读取
int CPerformanceMelsec::ReadBoardLed(std::vector<short>& vecLedBuffer) {
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    // æ¸…空 LED ç¼“冲区
    vecLedBuffer.clear();
    vecLedBuffer.resize(16, 0);
    // æ¸…空 LED ç¼“冲区
    vecLedBuffer.clear();
    vecLedBuffer.resize(16, 0);
    // è°ƒç”¨ SDK å‡½æ•°è¯»å– LED æ•°æ®
    const short nRet = mdBdLedRead(m_nPath, vecLedBuffer.data());
    if (nRet != 0) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        LOG_ERROR("Error reading board LED, ErrorCode: " << nRet);
        LOG_ERROR(m_strLastError);
    }
    // è°ƒç”¨ SDK å‡½æ•°è¯»å– LED æ•°æ®
    const short nRet = mdBdLedRead(m_nPath, vecLedBuffer.data());
    if (nRet != 0) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        LOG_ERROR("Error reading board LED, ErrorCode: " << nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// èŽ·å–æ¿çŠ¶æ€
// èŽ·å–æ¿çŠ¶æ€
int CPerformanceMelsec::GetBoardStatus(BoardStatus& status) {
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    short buf[6] = { 0 };
    const short nRet = mdBdSwRead(m_nPath, buf);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    short buf[6] = { 0 };
    const short nRet = mdBdSwRead(m_nPath, buf);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    // å°† buf æ˜ å°„到结构体
    status = BoardStatus::fromBuffer(buf);
    return 0;
    // å°† buf æ˜ å°„到结构体
    status = BoardStatus::fromBuffer(buf);
    return 0;
}
// é€šç”¨è¯»æ•°æ®
// é€šç”¨è¯»æ•°æ®
int CPerformanceMelsec::ReadData(const StationIdentifier& station, const long nDevType, const long nDevNo, long nSize, std::vector<short>& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, static_cast<short>(nSize));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, static_cast<short>(nSize));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // åˆå§‹åŒ–读取缓冲区
    vecData.clear();
    vecData.resize(nSize, 0);
    // åˆå§‹åŒ–读取缓冲区
    vecData.clear();
    vecData.resize(nSize, 0);
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        short* pData = vecData.data();
        nSize *= sizeof(short);
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, (long)(unsigned short)nDevNo, &nSize, pData);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        short* pData = vecData.data();
        nSize *= sizeof(short);
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, (long)(unsigned short)nDevNo, &nSize, pData);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        vecData.clear(); // å¦‚果读取失败,清空缓冲区
    }
    if (nRet != 0) {
        vecData.clear(); // å¦‚果读取失败,清空缓冲区
    }
    return nRet;
    return nRet;
}
// è¯»å–位数据
// è¯»å–位数据
int CPerformanceMelsec::ReadBitData(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo, const short nBitCount, BitContainer& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, nBitCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, nBitCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        nRet = -2;
        UpdateLastError(nRet);
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        nRet = -2;
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>((static_cast<int>(nBitCount) + 15) / 16);  // è®¡ç®—需要读取的字数量(向上取整)
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>((static_cast<int>(nBitCount) + 15) / 16);  // è®¡ç®—需要读取的字数量(向上取整)
    std::vector<short> vecTempBuffer(nSize, 0);
    nRet = ReadData(station, nDevType, nDevNo, nSize, vecTempBuffer);
    std::vector<short> vecTempBuffer(nSize, 0);
    nRet = ReadData(station, nDevType, nDevNo, nSize, vecTempBuffer);
    if (nRet == 0) {
        vecData.clear();
    if (nRet == 0) {
        vecData.clear();
        // å°†å­—数据解析为位数据
        for (short nIdx = 0; nIdx < nSize; ++nIdx) {
            const short nCurrentValue = vecTempBuffer[nIdx];
            // éåŽ†å½“å‰ short ä¸­çš„æ¯ä¸€ä½
            for (int bitIdx = 0; bitIdx < 16; ++bitIdx) {
                bool bBit = (nCurrentValue & (1 << bitIdx)) != 0;
                vecData.push_back(bBit);
                if (vecData.size() >= nBitCount) {
                    return nRet;  // å¦‚果已经读取完所需的位数,提前退出
                }
            }
        }
    }
        // å°†å­—数据解析为位数据
        for (short nIdx = 0; nIdx < nSize; ++nIdx) {
            const short nCurrentValue = vecTempBuffer[nIdx];
            // éåŽ†å½“å‰ short ä¸­çš„æ¯ä¸€ä½
            for (int bitIdx = 0; bitIdx < 16; ++bitIdx) {
                bool bBit = (nCurrentValue & (1 << bitIdx)) != 0;
                vecData.push_back(bBit);
                if (vecData.size() >= nBitCount) {
                    return nRet;  // å¦‚果已经读取完所需的位数,提前退出
                }
            }
        }
    }
    return nRet;
    return nRet;
}
// è¯»å–字数据
// è¯»å–字数据
int CPerformanceMelsec::ReadWordData(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo, const short nWordCount, WordContainer& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, nWordCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, nWordCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    std::vector<short> vecTempBuffer(nWordCount, 0);
    nRet = ReadData(station, nDevType, nDevNo, nWordCount, vecTempBuffer);
    const short nDevType = CalculateDeviceType(station, enDevType);
    std::vector<short> vecTempBuffer(nWordCount, 0);
    nRet = ReadData(station, nDevType, nDevNo, nWordCount, vecTempBuffer);
    if (nRet == 0) {
        vecData.clear();
        vecData.assign(vecTempBuffer.begin(), vecTempBuffer.end());
    }
    if (nRet == 0) {
        vecData.clear();
        vecData.assign(vecTempBuffer.begin(), vecTempBuffer.end());
    }
    return nRet;
    return nRet;
}
// è¯»å–双字数据
// è¯»å–双字数据
int CPerformanceMelsec::ReadDWordData(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo, const short nDWordCount, DWordContainer& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, nDWordCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndSize(station, nDWordCount);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const auto nSize = static_cast<short>(nDWordCount * 2); // æ¯ä¸ªåŒå­—占两个字(每个双字占 4 å­—节)
    const short nDevType = CalculateDeviceType(station, enDevType);
    std::vector<short> vecTempBuffer(nSize, 0);
    nRet = ReadData(station, nDevType, nDevNo, nSize, vecTempBuffer);
    const auto nSize = static_cast<short>(nDWordCount * 2); // æ¯ä¸ªåŒå­—占两个字(每个双字占 4 å­—节)
    const short nDevType = CalculateDeviceType(station, enDevType);
    std::vector<short> vecTempBuffer(nSize, 0);
    nRet = ReadData(station, nDevType, nDevNo, nSize, vecTempBuffer);
    if (nRet == 0) {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertShortToUint32(vecTempBuffer, vecData);
    }
    if (nRet == 0) {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertShortToUint32(vecTempBuffer, vecData);
    }
    return nRet;
    return nRet;
}
// é€šç”¨å†™æ•°æ®
// é€šç”¨å†™æ•°æ®
int CPerformanceMelsec::WriteData(const StationIdentifier& station, const long nDevType, const long nDevNo, long nSize, short* pData) {
    // éªŒè¯ç«™ç‚¹å‚æ•°
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚æ•°
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // æ•°æ®æœ‰æ•ˆæ€§
    if (nSize < 0 || pData == nullptr) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    // æ•°æ®æœ‰æ•ˆæ€§
    if (nSize < 0 || pData == nullptr) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        nSize *= sizeof(short);
        nRet = mdSendEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, pData);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        nSize *= sizeof(short);
        nRet = mdSendEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, pData);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// å†™ä½æ•°æ®
// å†™ä½æ•°æ®
int CPerformanceMelsec::WriteBitData(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo, const BitContainer& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        nRet = -2;
        UpdateLastError(nRet);
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        nRet = -2;
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>((static_cast<int>(vecData.size()) + 15) / 16);  // è®¡ç®—需要写入的字数量(向上取整)
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>((static_cast<int>(vecData.size()) + 15) / 16);  // è®¡ç®—需要写入的字数量(向上取整)
    // å‡†å¤‡ä¸´æ—¶ç¼“冲区来存储转换后的 16 ä½æ•°æ®
    std::vector<short> vecTempBuffer(nSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        // å°†ä½æ•°æ®æŒ‰å­—打包到临时缓冲区
        for (int i = 0; i < vecData.size(); ++i) {
            if (vecData[i]) {
                // ä½¿ç”¨ & 0xFFFF ä¿è¯ä¸ä¼šè¶…过 16 ä½ï¼Œé˜²æ­¢æº¢å‡º
                vecTempBuffer[i / 16] |= static_cast<short>((1 << (i % 16)) & 0xFFFF);
            }
        }
    }
    // å‡†å¤‡ä¸´æ—¶ç¼“冲区来存储转换后的 16 ä½æ•°æ®
    std::vector<short> vecTempBuffer(nSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        // å°†ä½æ•°æ®æŒ‰å­—打包到临时缓冲区
        for (int i = 0; i < vecData.size(); ++i) {
            if (vecData[i]) {
                // ä½¿ç”¨ & 0xFFFF ä¿è¯ä¸ä¼šè¶…过 16 ä½ï¼Œé˜²æ­¢æº¢å‡º
                vecTempBuffer[i / 16] |= static_cast<short>((1 << (i % 16)) & 0xFFFF);
            }
        }
    }
    return WriteData(station, nDevType, nDevNo, nSize, vecTempBuffer.data());
    return WriteData(station, nDevType, nDevNo, nSize, vecTempBuffer.data());
}
// å†™å­—数据
// å†™å­—数据
int CPerformanceMelsec::WriteWordData(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo, const WordContainer& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    const int nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    const int nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // è®¡ç®—需要写入的字节数(每个字占 2 å­—节)
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>(vecData.size());
    const auto pData = const_cast<short*>(reinterpret_cast<const short*>(vecData.data()));
    // è®¡ç®—需要写入的字节数(每个字占 2 å­—节)
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>(vecData.size());
    const auto pData = const_cast<short*>(reinterpret_cast<const short*>(vecData.data()));
    return WriteData(station, nDevType, nDevNo, nSize, pData);
    return WriteData(station, nDevType, nDevNo, nSize, pData);
}
// å†™åŒå­—数据
// å†™åŒå­—数据
int CPerformanceMelsec::WriteDWordData(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo, const DWordContainer& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    const int nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    const int nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // è®¡ç®—需要写入的字节数(每个双字占 4 å­—节)
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>(vecData.size() * sizeof(short));
    std::vector<short> vecBuffer(nSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertUint32ToShort(vecData, vecBuffer);
    }
    // è®¡ç®—需要写入的字节数(每个双字占 4 å­—节)
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>(vecData.size() * sizeof(short));
    std::vector<short> vecBuffer(nSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertUint32ToShort(vecData, vecBuffer);
    }
    return WriteData(station, nDevType, nDevNo, nSize, vecBuffer.data());
    return WriteData(station, nDevType, nDevNo, nSize, vecBuffer.data());
}
// æ‰©å±•读数据
// æ‰©å±•读数据
long CPerformanceMelsec::ReadDataEx(const StationIdentifier& station, long nDevType, long nDevNo, long nSize, std::vector<char>& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和读取大小是否有效
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和读取大小是否有效
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    if (nSize < 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    if (nSize < 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    nSize = nSize % 2 != 0 ? nSize + 1 : nSize;
    std::vector<short> vecBuffer(nSize / 2, 0);
    nSize = nSize % 2 != 0 ? nSize + 1 : nSize;
    std::vector<short> vecBuffer(nSize / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, vecBuffer.data());
    }
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, vecBuffer.data());
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        vecData.resize(nSize);
        ConvertShortToChar(vecBuffer, vecData);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        vecData.resize(nSize);
        ConvertShortToChar(vecBuffer, vecData);
    }
    return 0;
    return 0;
}
// æ‰©å±•读取位数据
// æ‰©å±•读取位数据
long CPerformanceMelsec::ReadBitDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nBitCount, BitContainer& vecData) {
    long nRet = ValidateStationAndSize(station, static_cast<short>(nBitCount));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    long nRet = ValidateStationAndSize(station, static_cast<short>(nBitCount));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const short nDevType = CalculateDeviceType(station, enDevType);
    // === è‡ªåŠ¨å¯¹é½åˆ°èµ·å§‹å­— ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + nBitCount;
    long nWordCount = (nTotalBits + 15) / 16;
    long nByteSize = nWordCount * sizeof(short);
    // === è‡ªåŠ¨å¯¹é½åˆ°èµ·å§‹å­— ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + nBitCount;
    long nWordCount = (nTotalBits + 15) / 16;
    long nByteSize = nWordCount * sizeof(short);
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, nByteSize, vecRaw);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, nByteSize, vecRaw);
    if (nRet != 0) {
        UpdateLastError(nRet);
        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);
    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) {
            long bitIndex = i * 16 + j;
            if (bitIndex >= nBitOffset && vecData.size() < static_cast<size_t>(nBitCount)) {
                vecData.push_back((word & (1 << j)) != 0);
            }
        }
    }
        for (int j = 0; j < 16; ++j) {
            long bitIndex = i * 16 + j;
            if (bitIndex >= nBitOffset && vecData.size() < static_cast<size_t>(nBitCount)) {
                vecData.push_back((word & (1 << j)) != 0);
            }
        }
    }
    return 0;
    return 0;
}
// æ‰©å±•读取字数据
// æ‰©å±•读取字数据
long CPerformanceMelsec::ReadWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nWordCount, WordContainer& vecData) {
    long nRet = ValidateStationAndSize(station, static_cast<short>(nWordCount));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    long nRet = ValidateStationAndSize(station, static_cast<short>(nWordCount));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const long nByteSize = nWordCount * sizeof(short);
    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;
    }
    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);
    }
    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;
    return 0;
}
// æ‰©å±•读取双字数据
// æ‰©å±•读取双字数据
long CPerformanceMelsec::ReadDWordDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, long nDWordCount, DWordContainer& vecData) {
    long nRet = ValidateStationAndSize(station, static_cast<short>(nDWordCount));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    long nRet = ValidateStationAndSize(station, static_cast<short>(nDWordCount));
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const long nByteSize = nDWordCount * sizeof(uint32_t);
    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;
    }
    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);
    }
    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;
    return 0;
}
// æ‰©å±•写数据
// æ‰©å±•写数据
long CPerformanceMelsec::WriteDataEx(const StationIdentifier& station, long nDevType, long nDevNo, const std::vector<char>& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // å°† vecData è½¬æ¢ä¸º short ç±»åž‹çš„缓冲区
    long nSize = static_cast<long>(vecData.size());
    nSize = nSize % 2 != 0 ? nSize + 1 : nSize;
    std::vector<short> vecBuffer(nSize / 2, 0);
    // å°† vecData è½¬æ¢ä¸º short ç±»åž‹çš„缓冲区
    long nSize = static_cast<long>(vecData.size());
    nSize = nSize % 2 != 0 ? nSize + 1 : nSize;
    std::vector<short> vecBuffer(nSize / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdSendEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, vecBuffer.data());
    }
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdSendEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, vecBuffer.data());
    }
    // é”™è¯¯å¤„理和日志记录
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    // é”™è¯¯å¤„理和日志记录
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    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;
    }
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const short nDevType = CalculateDeviceType(station, enDevType);
    // === 1. è‡ªåŠ¨å¯¹é½èµ·å§‹åœ°å€ ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + static_cast<long>(vecData.size());
    size_t nWordCount = (nTotalBits + 15) / 16;
    // === 1. è‡ªåŠ¨å¯¹é½èµ·å§‹åœ°å€ ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + static_cast<long>(vecData.size());
    size_t nWordCount = (nTotalBits + 15) / 16;
    // === 2. å…ˆè¯»å–原始值以支持非对齐覆盖 ===
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, static_cast<long>(nWordCount * sizeof(short)), vecRaw);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // === 2. å…ˆè¯»å–原始值以支持非对齐覆盖 ===
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, static_cast<long>(nWordCount * sizeof(short)), vecRaw);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // === 3. åˆå¹¶æ–°æ•°æ® ===
    std::vector<short> vecWordBuffer(nWordCount, 0);
    for (size_t i = 0; i < nWordCount; ++i) {
        vecWordBuffer[i] = static_cast<unsigned char>(vecRaw[i * 2]) | (static_cast<unsigned char>(vecRaw[i * 2 + 1]) << 8);
    }
    // === 3. åˆå¹¶æ–°æ•°æ® ===
    std::vector<short> vecWordBuffer(nWordCount, 0);
    for (size_t i = 0; i < nWordCount; ++i) {
        vecWordBuffer[i] = static_cast<unsigned char>(vecRaw[i * 2]) | (static_cast<unsigned char>(vecRaw[i * 2 + 1]) << 8);
    }
    for (size_t i = 0; i < vecData.size(); ++i) {
        size_t bitIndex = nBitOffset + i;
        size_t wordIdx = bitIndex / 16;
        size_t bitPos = bitIndex % 16;
        if (vecData[i]) {
            vecWordBuffer[wordIdx] |= (1 << bitPos);
        }
        else {
            vecWordBuffer[wordIdx] &= ~(1 << bitPos);
        }
    }
    for (size_t i = 0; i < vecData.size(); ++i) {
        size_t bitIndex = nBitOffset + i;
        size_t wordIdx = bitIndex / 16;
        size_t bitPos = bitIndex % 16;
        if (vecData[i]) {
            vecWordBuffer[wordIdx] |= (1 << bitPos);
        }
        else {
            vecWordBuffer[wordIdx] &= ~(1 << bitPos);
        }
    }
    // === 4. è½¬ä¸ºå­—节流写入 ===
    std::vector<char> vecByteBuffer(nWordCount * 2);
    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);
    }
    // === 4. è½¬ä¸ºå­—节流写入 ===
    std::vector<char> vecByteBuffer(nWordCount * 2);
    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, nWordAlignedStartBit, vecByteBuffer);
    return WriteDataEx(station, nDevType, nWordAlignedStartBit, 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;
    }
    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));
    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);
    }
    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);
    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;
    }
    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));
    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);
    }
    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);
    return WriteDataEx(station, nDevType, nDevNo, vecByteBuffer);
}
// æ‰©å±•软元件随机读取
// æ‰©å±•软元件随机读取
long CPerformanceMelsec::ReadRandomDataEx(const StationIdentifier& station, const std::vector<SoftElement>& vecSoftElements, std::vector<char>& vecData) {
    if (vecSoftElements.empty()) {
        UpdateLastError(ERROR_INVALID_PARAMETER);
        LOG_ERROR("Invalid parameters: soft elements are empty.");
        return ERROR_INVALID_PARAMETER;
    }
    if (vecSoftElements.empty()) {
        UpdateLastError(ERROR_INVALID_PARAMETER);
        LOG_ERROR("Invalid parameters: soft elements are empty.");
        return ERROR_INVALID_PARAMETER;
    }
    // å‡†å¤‡ dev æ•°æ®
    std::vector<short> devBuffer(vecSoftElements.size() * 3 + 1, 0); // æ¯ä¸ªè½¯å…ƒä»¶éœ€è¦ 3 ä¸ª short,外加一个计数器
    devBuffer[0] = static_cast<short>(vecSoftElements.size());                 // ç¬¬ä¸€ä¸ªå…ƒç´ æ˜¯è½¯å…ƒä»¶æ•°é‡
    for (size_t i = 0; i < vecSoftElements.size(); ++i) {
        const SoftElement& element = vecSoftElements[i];
        devBuffer[i * 3 + 1] = element.nType;                        // è½¯å…ƒä»¶ç±»åž‹
        devBuffer[i * 3 + 2] = static_cast<short>(element.nStartNo); // èµ·å§‹è½¯å…ƒä»¶ç¼–号
        devBuffer[i * 3 + 3] = element.nElementCount;                // ç‚¹æ•°
    }
    // å‡†å¤‡ dev æ•°æ®
    std::vector<short> devBuffer(vecSoftElements.size() * 3 + 1, 0); // æ¯ä¸ªè½¯å…ƒä»¶éœ€è¦ 3 ä¸ª short,外加一个计数器
    devBuffer[0] = static_cast<short>(vecSoftElements.size());                 // ç¬¬ä¸€ä¸ªå…ƒç´ æ˜¯è½¯å…ƒä»¶æ•°é‡
    for (size_t i = 0; i < vecSoftElements.size(); ++i) {
        const SoftElement& element = vecSoftElements[i];
        devBuffer[i * 3 + 1] = element.nType;                        // è½¯å…ƒä»¶ç±»åž‹
        devBuffer[i * 3 + 2] = static_cast<short>(element.nStartNo); // èµ·å§‹è½¯å…ƒä»¶ç¼–号
        devBuffer[i * 3 + 3] = element.nElementCount;                // ç‚¹æ•°
    }
    // è®¡ç®—读取数据所需缓冲区大小
    long nBufferSize = 0;
    for (const auto& element : vecSoftElements) {
        nBufferSize += element.nElementCount * 2; // æ¯ä¸ªç‚¹å ç”¨ 2 ä¸ªå­—节
    }
    // è®¡ç®—读取数据所需缓冲区大小
    long nBufferSize = 0;
    for (const auto& element : vecSoftElements) {
        nBufferSize += element.nElementCount * 2; // æ¯ä¸ªç‚¹å ç”¨ 2 ä¸ªå­—节
    }
    // é”ä¿æŠ¤åŠè°ƒç”¨ mdRandREx
    long nRet = 0;
    std::vector<short> vecBuffer(nBufferSize / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // ç¡®ä¿çº¿ç¨‹å®‰å…¨
        nRet = mdRandREx(m_nPath, station.nNetNo, station.nStNo, devBuffer.data(), vecBuffer.data(), nBufferSize);
    }
    // é”ä¿æŠ¤åŠè°ƒç”¨ mdRandREx
    long nRet = 0;
    std::vector<short> vecBuffer(nBufferSize / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // ç¡®ä¿çº¿ç¨‹å®‰å…¨
        nRet = mdRandREx(m_nPath, station.nNetNo, station.nStNo, devBuffer.data(), vecBuffer.data(), nBufferSize);
    }
    // é”™è¯¯å¤„理和日志记录
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
        return nRet;
    }
    // é”™è¯¯å¤„理和日志记录
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
        return nRet;
    }
    // å°†è¯»å–到的 short æ•°æ®è½¬æ¢ä¸º char æ•°æ®
    vecData.resize(nBufferSize);
    for (size_t i = 0; i < vecBuffer.size(); ++i) {
        vecData[i * 2] = static_cast<char>(vecBuffer[i] & 0xFF);            // ä½Žå­—节
        vecData[i * 2 + 1] = static_cast<char>((vecBuffer[i] >> 8) & 0xFF); // é«˜å­—节
    }
    // å°†è¯»å–到的 short æ•°æ®è½¬æ¢ä¸º char æ•°æ®
    vecData.resize(nBufferSize);
    for (size_t i = 0; i < vecBuffer.size(); ++i) {
        vecData[i * 2] = static_cast<char>(vecBuffer[i] & 0xFF);            // ä½Žå­—节
        vecData[i * 2 + 1] = static_cast<char>((vecBuffer[i] >> 8) & 0xFF); // é«˜å­—节
    }
    return nRet;
    return nRet;
}
// æ‰©å±•软元件随机写入(支持多个软元件)
// æ‰©å±•软元件随机写入(支持多个软元件)
long CPerformanceMelsec::WriteRandomDataEx(const StationIdentifier& station, const std::vector<SoftElement>& vecSoftElements, const std::vector<char>& vecData) {
    if (vecSoftElements.empty() || vecData.empty()) {
        UpdateLastError(ERROR_INVALID_PARAMETER);
        LOG_ERROR("Invalid parameters: soft elements or data is empty.");
        return ERROR_INVALID_PARAMETER;
    }
    if (vecSoftElements.empty() || vecData.empty()) {
        UpdateLastError(ERROR_INVALID_PARAMETER);
        LOG_ERROR("Invalid parameters: soft elements or data is empty.");
        return ERROR_INVALID_PARAMETER;
    }
    // å‡†å¤‡ dev æ•°æ®
    std::vector<long> devBuffer(vecSoftElements.size() * 3 + 1, 0); // æ¯ä¸ªè½¯å…ƒä»¶éœ€è¦ 3 ä¸ª long,外加一个计数器
    devBuffer[0] = static_cast<long>(vecSoftElements.size());                 // ç¬¬ä¸€ä¸ªå…ƒç´ æ˜¯è½¯å…ƒä»¶æ•°é‡
    for (size_t i = 0; i < vecSoftElements.size(); ++i) {
        const SoftElement& element = vecSoftElements[i];
        devBuffer[i * 3 + 1] = static_cast<long>(element.nType);    // è½¯å…ƒä»¶ç±»åž‹
        devBuffer[i * 3 + 2] = element.nStartNo;                    // èµ·å§‹è½¯å…ƒä»¶ç¼–号(已经是 long ç±»åž‹ï¼Œæ— éœ€è½¬æ¢ï¼‰
        devBuffer[i * 3 + 3] = static_cast<long>(element.nElementCount); // ç‚¹æ•°
    }
    // å‡†å¤‡ dev æ•°æ®
    std::vector<long> devBuffer(vecSoftElements.size() * 3 + 1, 0); // æ¯ä¸ªè½¯å…ƒä»¶éœ€è¦ 3 ä¸ª long,外加一个计数器
    devBuffer[0] = static_cast<long>(vecSoftElements.size());                 // ç¬¬ä¸€ä¸ªå…ƒç´ æ˜¯è½¯å…ƒä»¶æ•°é‡
    for (size_t i = 0; i < vecSoftElements.size(); ++i) {
        const SoftElement& element = vecSoftElements[i];
        devBuffer[i * 3 + 1] = static_cast<long>(element.nType);    // è½¯å…ƒä»¶ç±»åž‹
        devBuffer[i * 3 + 2] = element.nStartNo;                    // èµ·å§‹è½¯å…ƒä»¶ç¼–号(已经是 long ç±»åž‹ï¼Œæ— éœ€è½¬æ¢ï¼‰
        devBuffer[i * 3 + 3] = static_cast<long>(element.nElementCount); // ç‚¹æ•°
    }
    // é”ä¿æŠ¤åŠè°ƒç”¨ mdRandWEx
    long nRet = 0;
    std::vector<short> vecBuffer(vecData.size() / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // ç¡®ä¿çº¿ç¨‹å®‰å…¨
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRandWEx(m_nPath, station.nNetNo, station.nStNo, devBuffer.data(), vecBuffer.data(), static_cast<long>(vecBuffer.size()));
    }
    // é”ä¿æŠ¤åŠè°ƒç”¨ mdRandWEx
    long nRet = 0;
    std::vector<short> vecBuffer(vecData.size() / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // ç¡®ä¿çº¿ç¨‹å®‰å…¨
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRandWEx(m_nPath, station.nNetNo, station.nStNo, devBuffer.data(), vecBuffer.data(), static_cast<long>(vecBuffer.size()));
    }
    // é”™è¯¯å¤„理和日志记录
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    // é”™è¯¯å¤„理和日志记录
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// è¿œç¨‹è®¾å¤‡ç«™/远程站的缓冲存储器读取
// è¿œç¨‹è®¾å¤‡ç«™/远程站的缓冲存储器读取
long CPerformanceMelsec::ReadRemoteBuffer(const StationIdentifier& station, long nOffset, long nSize, std::vector<char>& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    if (nSize < 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    if (nSize < 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    long nActualSize = (nSize + 1) / 2;
    std::vector<short> vecBuffer(nActualSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        nRet = mdRemBufReadEx(m_nPath, station.nNetNo, station.nStNo, nOffset, &nActualSize, vecBuffer.data());
    }
    long nActualSize = (nSize + 1) / 2;
    std::vector<short> vecBuffer(nActualSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        nRet = mdRemBufReadEx(m_nPath, station.nNetNo, station.nStNo, nOffset, &nActualSize, vecBuffer.data());
    }
    if (nRet != 0) {
        UpdateLastError(nRet); // æ›´æ–°é”™è¯¯ç 
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertShortToChar(vecBuffer, vecData);
    }
    if (nRet != 0) {
        UpdateLastError(nRet); // æ›´æ–°é”™è¯¯ç 
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertShortToChar(vecBuffer, vecData);
    }
    return nRet;
    return nRet;
}
// è¿œç¨‹è®¾å¤‡ç«™/远程站的缓冲存储器写入
// è¿œç¨‹è®¾å¤‡ç«™/远程站的缓冲存储器写入
long CPerformanceMelsec::WriteRemoteBuffer(const StationIdentifier& station, long nOffset, const std::vector<char>& vecData) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // å°† vecData è½¬æ¢ä¸º short ç±»åž‹çš„缓冲区
    long nSize = static_cast<long>(vecData.size());
    std::vector<short> vecBuffer((nSize + 1) / 2, 0);
    // å°† vecData è½¬æ¢ä¸º short ç±»åž‹çš„缓冲区
    long nSize = static_cast<long>(vecData.size());
    std::vector<short> vecBuffer((nSize + 1) / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRemBufWriteEx(m_nPath, station.nNetNo, station.nStNo, nOffset, &nSize, vecBuffer.data());
    }
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRemBufWriteEx(m_nPath, station.nNetNo, station.nStNo, nOffset, &nSize, vecBuffer.data());
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// è¿œç¨‹ç«™çš„缓冲存储器读取 å¯¹è±¡ç«™IP地址指定
// è¿œç¨‹ç«™çš„缓冲存储器读取 å¯¹è±¡ç«™IP地址指定
long CPerformanceMelsec::ReadRemoteBufferByIp(const std::string& strIP, long nOffset, long nSize, std::vector<char>& vecData) {
    uint32_t nAddress = 0;
    if (nSize < 0 || !ConvertIpStringToUint32(strIP, nAddress)) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    uint32_t nAddress = 0;
    if (nSize < 0 || !ConvertIpStringToUint32(strIP, nAddress)) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    // å°†ç¼“冲区大小调整为 nSize
    vecData.resize(nSize, 0);
    std::vector<short> vecBuffer((nSize + 1) / 2, 0); // è½¬æ¢ä¸º short ç±»åž‹
    // å°†ç¼“冲区大小调整为 nSize
    vecData.resize(nSize, 0);
    std::vector<short> vecBuffer((nSize + 1) / 2, 0); // è½¬æ¢ä¸º short ç±»åž‹
    // è°ƒç”¨åº•层 SDK
    long nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        nRet = mdRemBufReadIPEx(m_nPath, static_cast<long>(nAddress), nOffset, &nSize, vecBuffer.data());
    }
    // è°ƒç”¨åº•层 SDK
    long nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        nRet = mdRemBufReadIPEx(m_nPath, static_cast<long>(nAddress), nOffset, &nSize, vecBuffer.data());
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertShortToChar(vecBuffer, vecData);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨ä¿æŠ¤
        ConvertShortToChar(vecBuffer, vecData);
    }
    return nRet;
    return nRet;
}
// è¿œç¨‹ç«™çš„缓冲存储器写入 å¯¹è±¡ç«™IP地址指定
// è¿œç¨‹ç«™çš„缓冲存储器写入 å¯¹è±¡ç«™IP地址指定
long CPerformanceMelsec::WriteRemoteBufferByIp(const std::string& strIP, long nOffset, const std::vector<char>& vecData) {
    uint32_t nAddress = 0;
    if (vecData.empty() || !ConvertIpStringToUint32(strIP, nAddress)) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    uint32_t nAddress = 0;
    if (vecData.empty() || !ConvertIpStringToUint32(strIP, nAddress)) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    // è½¬æ¢ vecData ä¸º short ç±»åž‹çš„缓冲区
    long nSize = static_cast<long>(vecData.size());
    std::vector<short> vecBuffer((nSize + 1) / 2, 0);
    // è½¬æ¢ vecData ä¸º short ç±»åž‹çš„缓冲区
    long nSize = static_cast<long>(vecData.size());
    std::vector<short> vecBuffer((nSize + 1) / 2, 0);
    long nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRemBufWriteIPEx(m_nPath, static_cast<long>(nAddress), nOffset, &nSize, vecBuffer.data());
    }
    long nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRemBufWriteIPEx(m_nPath, static_cast<long>(nAddress), nOffset, &nSize, vecBuffer.data());
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// è®¾ç½®(ON)对象站的指定位软元件
// è®¾ç½®(ON)对象站的指定位软元件
int CPerformanceMelsec::SetBitDevice(const StationIdentifier& station, const DeviceType enDevType, const short nDevNo) {
    // éªŒè¯ç«™ç‚¹å‚æ•°
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚æ•°
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdDevSet(m_nPath, CombineStation(station), nDevType, nDevNo);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx); // çº¿ç¨‹å®‰å…¨
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdDevSet(m_nPath, CombineStation(station), nDevType, nDevNo);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// å¤ä½(OFF)对象站的指定位软元件
// å¤ä½(OFF)对象站的指定位软元件
int CPerformanceMelsec::ResetBitDevice(const StationIdentifier& station, const DeviceType enDevType, const short enDevNo) {
    // éªŒè¯ç«™ç‚¹å‚æ•°
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚æ•°
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdDevRst(m_nPath, CombineStation(station), nDevType, enDevNo);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdDevRst(m_nPath, CombineStation(station), nDevType, enDevNo);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// æ‰©å±•位软元件设置
// æ‰©å±•位软元件设置
long CPerformanceMelsec::SetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo) {
    std::lock_guard<std::mutex> lock(m_mtx);
    std::lock_guard<std::mutex> lock(m_mtx);
    // æ£€æŸ¥å‚数有效性
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // æ£€æŸ¥å‚数有效性
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    long nDevType = static_cast<long>(enDevType);
    nRet = mdDevSetEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    long nDevType = static_cast<long>(enDevType);
    nRet = mdDevSetEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// æ‰©å±•位软元件复位
// æ‰©å±•位软元件复位
long CPerformanceMelsec::ResetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo) {
    std::lock_guard<std::mutex> lock(m_mtx);
    std::lock_guard<std::mutex> lock(m_mtx);
    // æ£€æŸ¥å‚数有效性
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // æ£€æŸ¥å‚数有效性
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    long nDevType = static_cast<long>(enDevType);
    nRet = mdDevRstEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    long nDevType = static_cast<long>(enDevType);
    nRet = mdDevRstEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo);
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// æ‰§è¡Œå¯¹è±¡ç«™çš„CPU
// æ‰§è¡Œå¯¹è±¡ç«™çš„CPU
int CPerformanceMelsec::ControlCPU(const StationIdentifier& station, ControlCode enControlCode) {
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯ç«™ç‚¹å‚数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // éªŒè¯æŽ§åˆ¶ç æ˜¯å¦åˆæ³•
    const auto nControlCode = static_cast<short>(enControlCode);
    if (nControlCode < 0 || nControlCode > 2) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM); // å‚数错误
        return ERROR_CODE_INVALID_PARAM;
    }
    // éªŒè¯æŽ§åˆ¶ç æ˜¯å¦åˆæ³•
    const auto nControlCode = static_cast<short>(enControlCode);
    if (nControlCode < 0 || nControlCode > 2) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM); // å‚数错误
        return ERROR_CODE_INVALID_PARAM;
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdControl(m_nPath, CombineStation(station), nControlCode);
    }
    // ç¡®ä¿çº¿ç¨‹å®‰å…¨çš„æœ€å°é”å®šèŒƒå›´
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        nRet = mdControl(m_nPath, CombineStation(station), nControlCode);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
// äº‹ä»¶ç­‰å¾…
// äº‹ä»¶ç­‰å¾…
int CPerformanceMelsec::WaitForBoardEvent(std::vector<short> vecEventNumbers, const int nTimeoutMs, EventDetails& details) {
    std::lock_guard<std::mutex> lock(m_mtx);
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
    }
    if (vecEventNumbers.empty() || vecEventNumbers.size() > 64) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    if (vecEventNumbers.empty() || vecEventNumbers.size() > 64) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    // ç¬¬ 0 ä¸ªå…ƒç´ å­˜å‚¨æ•°é‡ï¼Œæœ€å¤§æ”¯æŒ 64 ä¸ªäº‹ä»¶
    std::array<short, 65> eventno = { 0 };
    eventno[0] = static_cast<short>(vecEventNumbers.size());
    std::copy(vecEventNumbers.begin(), vecEventNumbers.end(), eventno.begin() + 1);
    // ç¬¬ 0 ä¸ªå…ƒç´ å­˜å‚¨æ•°é‡ï¼Œæœ€å¤§æ”¯æŒ 64 ä¸ªäº‹ä»¶
    std::array<short, 65> eventno = { 0 };
    eventno[0] = static_cast<short>(vecEventNumbers.size());
    std::copy(vecEventNumbers.begin(), vecEventNumbers.end(), eventno.begin() + 1);
    // åˆå§‹åŒ–输出参数
    details.nEventNo = 0;
    details.details.fill(0);
    // åˆå§‹åŒ–输出参数
    details.nEventNo = 0;
    details.details.fill(0);
    const int nRet = mdWaitBdEvent(m_nPath, eventno.data(), nTimeoutMs, &details.nEventNo, details.details.data());
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    const int nRet = mdWaitBdEvent(m_nPath, eventno.data(), nTimeoutMs, &details.nEventNo, details.details.data());
    if (nRet != 0) {
        UpdateLastError(nRet);
        LOG_ERROR(m_strLastError);
    }
    return nRet;
    return nRet;
}
//============================================辅助函数=======================================================
// æ›´æ–°æœ€è¿‘的错误信息
//============================================辅助函数=======================================================
// æ›´æ–°æœ€è¿‘的错误信息
void CPerformanceMelsec::UpdateLastError(const int nCode) {
    if (nCode == 0) {
        return;
    }
    if (nCode == 0) {
        return;
    }
    // æ£€æŸ¥é”™è¯¯ç æ˜¯å¦å­˜åœ¨äºŽæ˜ å°„表中
    const auto it = m_mapError.find(nCode);
    if (it != m_mapError.end()) {
        // å¦‚果找到,直接返回对应语言的错误信息
        m_strLastError = it->second;
    }
    else {
        // å¦‚果未找到,处理特殊范围
        m_strLastError = "Unknown error.";
        if (nCode == -28611 || nCode == -28612) {
            // ç³»ç»Ÿå‡ºé”™
            m_strLastError = "System error.";
        }
    // æ£€æŸ¥é”™è¯¯ç æ˜¯å¦å­˜åœ¨äºŽæ˜ å°„表中
    const auto it = m_mapError.find(nCode);
    if (it != m_mapError.end()) {
        // å¦‚果找到,直接返回对应语言的错误信息
        m_strLastError = it->second;
    }
    else {
        // å¦‚果未找到,处理特殊范围
        m_strLastError = "Unknown error.";
        if (nCode == -28611 || nCode == -28612) {
            // ç³»ç»Ÿå‡ºé”™
            m_strLastError = "System error.";
        }
        if (nCode >= -20480 && nCode <= -16384) {
            // CC-Link ç³»ç»Ÿæ£€æµ‹å‡ºçš„错误
            m_strLastError = "Error detected in the CC-Link system.";
        }
        if (nCode >= -20480 && nCode <= -16384) {
            // CC-Link ç³»ç»Ÿæ£€æµ‹å‡ºçš„错误
            m_strLastError = "Error detected in the CC-Link system.";
        }
        if (nCode >= -12288 && nCode <= -8193) {
            // CC-Link IE TSN ç³»ç»Ÿæ£€æµ‹å‡ºçš„错误
            m_strLastError = "Error detected in the CC-Link IE TSN system.";
        }
        if (nCode >= -12288 && nCode <= -8193) {
            // CC-Link IE TSN ç³»ç»Ÿæ£€æµ‹å‡ºçš„错误
            m_strLastError = "Error detected in the CC-Link IE TSN system.";
        }
        if (nCode >= -8192 && nCode <= -4097) {
            // CC-Link IE æŽ§åˆ¶ç½‘络系统检测出的错误
            m_strLastError = "Error detected in the CC-Link IE control network system.";
        }
        if (nCode >= -8192 && nCode <= -4097) {
            // CC-Link IE æŽ§åˆ¶ç½‘络系统检测出的错误
            m_strLastError = "Error detected in the CC-Link IE control network system.";
        }
        if (nCode >= -4096 && nCode <= -257) {
            // MELSECNET/10 æˆ– MELSECNET/网络系统错误范围
            m_strLastError = "Errors detected in MELSECNET/10 or MELSECNET/network system.";
        }
        if (nCode >= -4096 && nCode <= -257) {
            // MELSECNET/10 æˆ– MELSECNET/网络系统错误范围
            m_strLastError = "Errors detected in MELSECNET/10 or MELSECNET/network system.";
        }
        if (nCode >= 4096 && nCode <= 16383) {
            // MELSEC æ•°æ®é“¾æŽ¥åº“范围
            m_strLastError = "Internal error detected by MELSEC Data Link Library.";
        }
        if (nCode >= 4096 && nCode <= 16383) {
            // MELSEC æ•°æ®é“¾æŽ¥åº“范围
            m_strLastError = "Internal error detected by MELSEC Data Link Library.";
        }
        if (nCode == 18944 || nCode == 18945) {
            // é“¾æŽ¥å…³è”出错
            m_strLastError = "Link association error: Network does not exist, unsupported CPU, or incorrect network No./station number.";
        }
        if (nCode == 18944 || nCode == 18945) {
            // é“¾æŽ¥å…³è”出错
            m_strLastError = "Link association error: Network does not exist, unsupported CPU, or incorrect network No./station number.";
        }
        if (nCode >= 16384 && nCode <= 20479) {
            // PLC CPU æ£€æµ‹èŒƒå›´
            m_strLastError = "Errors detected by the programmable controller CPU in the target station.";
        }
        if (nCode >= 16384 && nCode <= 20479) {
            // PLC CPU æ£€æµ‹èŒƒå›´
            m_strLastError = "Errors detected by the programmable controller CPU in the target station.";
        }
        if (nCode >= 28416 && nCode <= 28671) {
            // å†—余功能模块范围
            m_strLastError = "Error detected in the redundancy module of the target station.";
        }
    }
        if (nCode >= 28416 && nCode <= 28671) {
            // å†—余功能模块范围
            m_strLastError = "Error detected in the redundancy module of the target station.";
        }
    }
}
// æ£€æŸ¥è¿žæŽ¥çŠ¶æ€å’Œç«™ç‚¹å‚æ•°æœ‰æ•ˆæ€§
// æ£€æŸ¥è¿žæŽ¥çŠ¶æ€å’Œç«™ç‚¹å‚æ•°æœ‰æ•ˆæ€§
int CPerformanceMelsec::ValidateStation(const StationIdentifier& station) const {
    // æ£€æŸ¥æ˜¯å¦å·²è¿žæŽ¥
    if (!m_bConnected.load()) {
        return ERROR_CODE_NOT_CONNECTED;
    }
    // æ£€æŸ¥æ˜¯å¦å·²è¿žæŽ¥
    if (!m_bConnected.load()) {
        return ERROR_CODE_NOT_CONNECTED;
    }
    // æ£€æŸ¥ç½‘络号和站点号范围
    if (station.nNetNo < 0 || station.nNetNo > 239 || station.nStNo < 0 || station.nStNo > 255) {
        return ERROR_CODE_INVALID_PARAM;
    }
    // æ£€æŸ¥ç½‘络号和站点号范围
    if (station.nNetNo < 0 || station.nNetNo > 239 || station.nStNo < 0 || station.nStNo > 255) {
        return ERROR_CODE_INVALID_PARAM;
    }
    return 0; // å‚数有效
    return 0; // å‚数有效
}
// éªŒè¯ç«™ç‚¹å‚数和数据有效性
// éªŒè¯ç«™ç‚¹å‚数和数据有效性
int CPerformanceMelsec::ValidateStationAndSize(const StationIdentifier& station, const short nCount) const {
    // éªŒè¯ç«™ç‚¹å‚æ•°
    const int nRet = ValidateStation(station);
    if (nRet != 0) {
        return nRet; // å¦‚果站点验证失败,返回对应错误码
    }
    // éªŒè¯ç«™ç‚¹å‚æ•°
    const int nRet = ValidateStation(station);
    if (nRet != 0) {
        return nRet; // å¦‚果站点验证失败,返回对应错误码
    }
    if (nCount <= 0) {
        return ERROR_CODE_INVALID_PARAM;
    }
    if (nCount <= 0) {
        return ERROR_CODE_INVALID_PARAM;
    }
    return 0; // éªŒè¯é€šè¿‡
    return 0; // éªŒè¯é€šè¿‡
}
// IP字符串转uint32_t
// IP字符串转uint32_t
bool CPerformanceMelsec::ConvertIpStringToUint32(const std::string& strIP, uint32_t& nIP) {
    nIP = 0;
    std::stringstream ss(strIP);
    std::string strSegment;
    int nShift = 24;
    nIP = 0;
    std::stringstream ss(strIP);
    std::string strSegment;
    int nShift = 24;
    while (std::getline(ss, strSegment, '.')) {
        const auto nByte = static_cast<uint32_t>(std::stoi(strSegment));
        if (nByte > 255) {
            return false;
        }
        nIP |= (nByte << nShift);
        nShift -= 8;
    }
    while (std::getline(ss, strSegment, '.')) {
        const auto nByte = static_cast<uint32_t>(std::stoi(strSegment));
        if (nByte > 255) {
            return false;
        }
        nIP |= (nByte << nShift);
        nShift -= 8;
    }
    return true;
    return true;
}
//============================================静态辅助函数====================================================
// å»¶æ—¶ï¼Œå¹¶ä¸”转发窗口消息
//============================================静态辅助函数====================================================
// å»¶æ—¶ï¼Œå¹¶ä¸”转发窗口消息
void CPerformanceMelsec::Delay(const unsigned int nDelayMs) {
    MSG message;
    // å¦‚果延迟时间为 0,仅处理一次消息队列
    if (nDelayMs == 0) {
        // éžé˜»å¡žçš„æ£€æŸ¥æ¶ˆæ¯é˜Ÿåˆ—
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&message);  // å°†æ¶ˆæ¯è½¬åŒ–为有效的窗口消息
            DispatchMessage(&message);   // æ´¾å‘消息给相应的窗口过程
        }
        return;
    }
    MSG message;
    // å¦‚果延迟时间为 0,仅处理一次消息队列
    if (nDelayMs == 0) {
        // éžé˜»å¡žçš„æ£€æŸ¥æ¶ˆæ¯é˜Ÿåˆ—
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&message);  // å°†æ¶ˆæ¯è½¬åŒ–为有效的窗口消息
            DispatchMessage(&message);   // æ´¾å‘消息给相应的窗口过程
        }
        return;
    }
    DWORD finish;
    const DWORD start = GetTickCount();  // èŽ·å–å½“å‰çš„æ—¶é—´æˆ³ï¼ˆä»Žç³»ç»Ÿå¯åŠ¨ä»¥æ¥çš„æ¯«ç§’æ•°ï¼‰
    do {
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&message);  // è½¬æ¢æ¶ˆæ¯
            DispatchMessage(&message);   // å¤„理消息
        }
        Sleep(1);   // æš‚停 1 æ¯«ç§’,防止过度占用 CPU
        finish = GetTickCount(); // èŽ·å–å½“å‰çš„æ—¶é—´æˆ³
    } while ((finish - start) < nDelayMs);  // å¾ªçŽ¯ç›´åˆ°ç»è¿‡çš„æ—¶é—´å¤§äºŽæŒ‡å®šçš„å»¶è¿Ÿæ—¶é—´
    DWORD finish;
    const DWORD start = GetTickCount();  // èŽ·å–å½“å‰çš„æ—¶é—´æˆ³ï¼ˆä»Žç³»ç»Ÿå¯åŠ¨ä»¥æ¥çš„æ¯«ç§’æ•°ï¼‰
    do {
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&message);  // è½¬æ¢æ¶ˆæ¯
            DispatchMessage(&message);   // å¤„理消息
        }
        Sleep(1);   // æš‚停 1 æ¯«ç§’,防止过度占用 CPU
        finish = GetTickCount(); // èŽ·å–å½“å‰çš„æ—¶é—´æˆ³
    } while ((finish - start) < nDelayMs);  // å¾ªçŽ¯ç›´åˆ°ç»è¿‡çš„æ—¶é—´å¤§äºŽæŒ‡å®šçš„å»¶è¿Ÿæ—¶é—´
}
BoardType CPerformanceMelsec::FindBoardTypeByChannel(const int nChannel) {
    if (nChannel >= MELSECNET_CHANNEL(1) && nChannel <= MELSECNET_CHANNEL(4)) {
        return BoardType::MELSECNET_H;
    }
    else if (nChannel >= CC_LINK_CHANNEL(1) && nChannel <= CC_LINK_CHANNEL(4)) {
        return BoardType::CC_LINK_VER_2;
    }
    else if (nChannel >= CC_LINK_IE_CONTROL_CHANNEL(1) && nChannel <= CC_LINK_IE_CONTROL_CHANNEL(4)) {
        return BoardType::CC_LINK_IE_CONTROL;
    }
    else if (nChannel >= CC_LINK_IE_FIELD_CHANNEL(1) && nChannel <= CC_LINK_IE_FIELD_CHANNEL(4)) {
        return BoardType::CC_LINK_IE_FIELD;
    }
    else if (nChannel >= CC_LINK_IE_TSN_CHANNEL(1) && nChannel <= CC_LINK_IE_TSN_CHANNEL(4)) {
        return BoardType::CC_LINK_IE_TSN;
    }
    return BoardType::UNKNOWN;
    if (nChannel >= MELSECNET_CHANNEL(1) && nChannel <= MELSECNET_CHANNEL(4)) {
        return BoardType::MELSECNET_H;
    }
    else if (nChannel >= CC_LINK_CHANNEL(1) && nChannel <= CC_LINK_CHANNEL(4)) {
        return BoardType::CC_LINK_VER_2;
    }
    else if (nChannel >= CC_LINK_IE_CONTROL_CHANNEL(1) && nChannel <= CC_LINK_IE_CONTROL_CHANNEL(4)) {
        return BoardType::CC_LINK_IE_CONTROL;
    }
    else if (nChannel >= CC_LINK_IE_FIELD_CHANNEL(1) && nChannel <= CC_LINK_IE_FIELD_CHANNEL(4)) {
        return BoardType::CC_LINK_IE_FIELD;
    }
    else if (nChannel >= CC_LINK_IE_TSN_CHANNEL(1) && nChannel <= CC_LINK_IE_TSN_CHANNEL(4)) {
        return BoardType::CC_LINK_IE_TSN;
    }
    return BoardType::UNKNOWN;
}
// åˆå¹¶ç½‘络号和站点号
// åˆå¹¶ç½‘络号和站点号
short CPerformanceMelsec::CombineStation(const StationIdentifier& station) {
    return static_cast<short>(station.nStNo | ((station.nNetNo << 8) & 0xFF00));
    return static_cast<short>(station.nStNo | ((station.nNetNo << 8) & 0xFF00));
}
// è®¡ç®—软元件类型
// è®¡ç®—软元件类型
short CPerformanceMelsec::CalculateDeviceType(const StationIdentifier& station, DeviceType enDevType) {
    int nDevType = static_cast<int>(enDevType);
    int nDevType = static_cast<int>(enDevType);
    // æ ¹æ®è½¯å…ƒä»¶ç±»åž‹çš„特定规则进行计算
    if (enDevType == DeviceType::LX || enDevType == DeviceType::LY ||
        enDevType == DeviceType::LB || enDevType == DeviceType::LW ||
        enDevType == DeviceType::LSB || enDevType == DeviceType::LSW) {
        // ç½‘络号加偏移
        nDevType += station.nNetNo;
    }
    else if (enDevType == DeviceType::ER) {
        // æ–‡ä»¶å¯„存器的块号加偏移
        nDevType += 0;
    }
    else if (enDevType == DeviceType::SPG) {
        // èµ·å§‹ I/O No. Ã· 16 çš„值
        nDevType += 0 / 16;
    }
    // æ ¹æ®è½¯å…ƒä»¶ç±»åž‹çš„特定规则进行计算
    if (enDevType == DeviceType::LX || enDevType == DeviceType::LY ||
        enDevType == DeviceType::LB || enDevType == DeviceType::LW ||
        enDevType == DeviceType::LSB || enDevType == DeviceType::LSW) {
        // ç½‘络号加偏移
        nDevType += station.nNetNo;
    }
    else if (enDevType == DeviceType::ER) {
        // æ–‡ä»¶å¯„存器的块号加偏移
        nDevType += 0;
    }
    else if (enDevType == DeviceType::SPG) {
        // èµ·å§‹ I/O No. Ã· 16 çš„值
        nDevType += 0 / 16;
    }
    return static_cast<short>(nDevType);
    return static_cast<short>(nDevType);
}
// std::vector<char>转换为std::vector<short>
// std::vector<char>转换为std::vector<short>
void CPerformanceMelsec::ConvertCharToShort(const std::vector<char>& vecChar, std::vector<short>& vecShort) {
    vecShort.resize((vecChar.size() + 1) / 2, 0); // è°ƒæ•´ short å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecChar.size(); i++) {
        if (i % 2 == 0) {
            vecShort[i / 2] = static_cast<unsigned char>(vecChar[i]);       // ä½Žå­—节
        }
        else {
            vecShort[i / 2] |= static_cast<unsigned char>(vecChar[i]) << 8; // é«˜å­—节
        }
    }
    vecShort.resize((vecChar.size() + 1) / 2, 0); // è°ƒæ•´ short å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecChar.size(); i++) {
        if (i % 2 == 0) {
            vecShort[i / 2] = static_cast<unsigned char>(vecChar[i]);       // ä½Žå­—节
        }
        else {
            vecShort[i / 2] |= static_cast<unsigned char>(vecChar[i]) << 8; // é«˜å­—节
        }
    }
}
// std::vector<short>转换为std::vector<char>
// std::vector<short>转换为std::vector<char>
void CPerformanceMelsec::ConvertShortToChar(const std::vector<short>& vecShort, std::vector<char>& vecChar) {
    vecChar.resize(vecShort.size() * 2); // è°ƒæ•´ char å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecShort.size(); i++) {
        vecChar[i * 2] = static_cast<char>(vecShort[i] & 0xFF);             // ä½Žå­—节
        vecChar[i * 2 + 1] = static_cast<char>((vecShort[i] >> 8) & 0xFF);  // é«˜å­—节
    }
    vecChar.resize(vecShort.size() * 2); // è°ƒæ•´ char å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecShort.size(); i++) {
        vecChar[i * 2] = static_cast<char>(vecShort[i] & 0xFF);             // ä½Žå­—节
        vecChar[i * 2 + 1] = static_cast<char>((vecShort[i] >> 8) & 0xFF);  // é«˜å­—节
    }
}
// std::vector<uint8_t>转换为std::vector<short>
// std::vector<uint8_t>转换为std::vector<short>
void CPerformanceMelsec::ConvertUint8ToShort(const std::vector<uint8_t>& vecUint8, std::vector<short>& vecShort) {
    vecShort.resize((vecUint8.size() + 1) / 2, 0); // è°ƒæ•´ short å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecUint8.size(); i++) {
        if (i % 2 == 0) {
            vecShort[i / 2] = static_cast<short>(vecUint8[i]);          // ä½Žå­—节
        }
        else {
            vecShort[i / 2] |= static_cast<short>(vecUint8[i] << 8);    // é«˜å­—节
        }
    }
    vecShort.resize((vecUint8.size() + 1) / 2, 0); // è°ƒæ•´ short å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecUint8.size(); i++) {
        if (i % 2 == 0) {
            vecShort[i / 2] = static_cast<short>(vecUint8[i]);          // ä½Žå­—节
        }
        else {
            vecShort[i / 2] |= static_cast<short>(vecUint8[i] << 8);    // é«˜å­—节
        }
    }
}
// std::vector<short>转换为std::vector<uint8_t>
// std::vector<short>转换为std::vector<uint8_t>
void CPerformanceMelsec::ConvertShortToUint8(const std::vector<short>& vecShort, std::vector<uint8_t>& vecUint8) {
    vecUint8.resize(vecShort.size() * 2); // è°ƒæ•´ uint8_t å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecShort.size(); i++) {
        vecUint8[i * 2] = static_cast<uint8_t>(vecShort[i] & 0xFF);             // ä½Žå­—节
        vecUint8[i * 2 + 1] = static_cast<uint8_t>((vecShort[i] >> 8) & 0xFF);  // é«˜å­—节
    }
    vecUint8.resize(vecShort.size() * 2); // è°ƒæ•´ uint8_t å®¹å™¨å¤§å°
    for (size_t i = 0; i < vecShort.size(); i++) {
        vecUint8[i * 2] = static_cast<uint8_t>(vecShort[i] & 0xFF);             // ä½Žå­—节
        vecUint8[i * 2 + 1] = static_cast<uint8_t>((vecShort[i] >> 8) & 0xFF);  // é«˜å­—节
    }
}
// std::vector<uint32_t>转换为std::vector<short>
// std::vector<uint32_t>转换为std::vector<short>
void CPerformanceMelsec::ConvertUint32ToShort(const std::vector<uint32_t>& vecUint32, std::vector<short>& vecShort) {
    vecShort.resize(vecUint32.size() * 2); // æ¯ä¸ª uint32_t è½¬æ¢ä¸ºä¸¤ä¸ª short
    for (size_t i = 0; i < vecUint32.size(); i++) {
        vecShort[i * 2] = static_cast<short>(vecUint32[i] & 0xFFFF);             // ä½Ž16位
        vecShort[i * 2 + 1] = static_cast<short>((vecUint32[i] >> 16) & 0xFFFF); // é«˜16位
    }
    vecShort.resize(vecUint32.size() * 2); // æ¯ä¸ª uint32_t è½¬æ¢ä¸ºä¸¤ä¸ª short
    for (size_t i = 0; i < vecUint32.size(); i++) {
        vecShort[i * 2] = static_cast<short>(vecUint32[i] & 0xFFFF);             // ä½Ž16位
        vecShort[i * 2 + 1] = static_cast<short>((vecUint32[i] >> 16) & 0xFFFF); // é«˜16位
    }
}
// std::vector<short>转换为std::vector<uint32_t>
// std::vector<short>转换为std::vector<uint32_t>
void CPerformanceMelsec::ConvertShortToUint32(const std::vector<short>& vecShort, std::vector<uint32_t>& vecUint32) {
    vecUint32.resize((vecShort.size() + 1) / 2, 0); // æ¯ä¸¤ä¸ª short åˆå¹¶ä¸ºä¸€ä¸ª uint32_t
    for (size_t i = 0; i < vecUint32.size(); i++) {
        vecUint32[i] = (static_cast<uint32_t>(static_cast<uint16_t>(vecShort[i * 2 + 1])) << 16) | // é«˜16位
            static_cast<uint32_t>(static_cast<uint16_t>(vecShort[i * 2]));              // ä½Ž16位
    }
    vecUint32.resize((vecShort.size() + 1) / 2, 0); // æ¯ä¸¤ä¸ª short åˆå¹¶ä¸ºä¸€ä¸ª uint32_t
    for (size_t i = 0; i < vecUint32.size(); i++) {
        vecUint32[i] = (static_cast<uint32_t>(static_cast<uint16_t>(vecShort[i * 2 + 1])) << 16) | // é«˜16位
            static_cast<uint32_t>(static_cast<uint16_t>(vecShort[i * 2]));              // ä½Ž16位
    }
}
//============================================模板辅助函数====================================================
// éªŒè¯ç«™ç‚¹å‚数和数据有效性
//============================================模板辅助函数====================================================
// éªŒè¯ç«™ç‚¹å‚数和数据有效性
template <typename T>
int CPerformanceMelsec::ValidateStationAndData(const StationIdentifier& station, const std::vector<T>& vecData) {
    // éªŒè¯ç«™ç‚¹å‚æ•°
    const int nRet = ValidateStation(station);
    if (nRet != 0) {
        return nRet; // å¦‚果站点验证失败,返回对应错误码
    }
    // éªŒè¯ç«™ç‚¹å‚æ•°
    const int nRet = ValidateStation(station);
    if (nRet != 0) {
        return nRet; // å¦‚果站点验证失败,返回对应错误码
    }
    // éªŒè¯æ•°æ®æ˜¯å¦ä¸ºç©º
    if (vecData.empty()) {
        return ERROR_CODE_INVALID_PARAM;
    }
    // éªŒè¯æ•°æ®æ˜¯å¦ä¸ºç©º
    if (vecData.empty()) {
        return ERROR_CODE_INVALID_PARAM;
    }
    return 0; // éªŒè¯é€šè¿‡
    return 0; // éªŒè¯é€šè¿‡
}
// ç”±ä½Žè½¬é«˜å®¹å™¨çš„æ¨¡æ¿ï¼ˆæ•´åž‹ï¼‰
// ç”±ä½Žè½¬é«˜å®¹å™¨çš„æ¨¡æ¿ï¼ˆæ•´åž‹ï¼‰
template <typename T, typename U>
void CPerformanceMelsec::ConvertLowToHigh(const std::vector<T>& vecLow, std::vector<U>& vecHigh) {
    static_assert(std::is_integral<T>::value && std::is_integral<U>::value, "T and U must be integral types");
    static_assert(std::is_integral<T>::value && std::is_integral<U>::value, "T and U must be integral types");
    // è‡ªåŠ¨è®¡ç®— nGroupSize
    constexpr size_t nGroupSize = sizeof(U) / sizeof(T);
    // è‡ªåŠ¨è®¡ç®— nGroupSize
    constexpr size_t nGroupSize = sizeof(U) / sizeof(T);
    // å¦‚æžœ T å’Œ U çš„大小相等,直接转换
    if (sizeof(T) == sizeof(U)) {
        vecHigh.assign(vecLow.begin(), vecLow.end());
        return;
    }
    // å¦‚æžœ T å’Œ U çš„大小相等,直接转换
    if (sizeof(T) == sizeof(U)) {
        vecHigh.assign(vecLow.begin(), vecLow.end());
        return;
    }
    // å¦‚æžœ U çš„大小是 T çš„倍数,正常组合
    static_assert(sizeof(U) > sizeof(T), "Size of U must be greater than or equal to size of T");
    // å¦‚æžœ U çš„大小是 T çš„倍数,正常组合
    static_assert(sizeof(U) > sizeof(T), "Size of U must be greater than or equal to size of T");
    // è®¡ç®—完整组的数量
    size_t nHighSize = (vecLow.size() + nGroupSize - 1) / nGroupSize; // å‘上取整
    vecHigh.resize(nHighSize, 0);
    // è®¡ç®—完整组的数量
    size_t nHighSize = (vecLow.size() + nGroupSize - 1) / nGroupSize; // å‘上取整
    vecHigh.resize(nHighSize, 0);
    // åˆå¹¶ä½Žä½æ•°æ®åˆ°é«˜ä½æ•°æ®
    for (size_t i = 0; i < vecLow.size(); i++) {
        vecHigh[i / nGroupSize] |= (static_cast<U>(vecLow[i]) << ((i % nGroupSize) * CHAR_BIT * sizeof(T)));
    }
    // åˆå¹¶ä½Žä½æ•°æ®åˆ°é«˜ä½æ•°æ®
    for (size_t i = 0; i < vecLow.size(); i++) {
        vecHigh[i / nGroupSize] |= (static_cast<U>(vecLow[i]) << ((i % nGroupSize) * CHAR_BIT * sizeof(T)));
    }
    return vecHigh;
    return vecHigh;
}
// ç”±é«˜è½¬ä½Žå®¹å™¨çš„æ¨¡æ¿ï¼ˆæ•´åž‹ï¼‰
// ç”±é«˜è½¬ä½Žå®¹å™¨çš„æ¨¡æ¿ï¼ˆæ•´åž‹ï¼‰
template <typename T, typename U>
void CPerformanceMelsec::ConvertHighToLow(const std::vector<T>& vecHigh, std::vector<U>& vecLow) {
    static_assert(std::is_integral<T>::value && std::is_integral<U>::value, "T and U must be integral types");
    static_assert(std::is_integral<T>::value && std::is_integral<U>::value, "T and U must be integral types");
    // è‡ªåŠ¨è®¡ç®— nGroupSize
    constexpr size_t nGroupSize = sizeof(T) / sizeof(U);
    // è‡ªåŠ¨è®¡ç®— nGroupSize
    constexpr size_t nGroupSize = sizeof(T) / sizeof(U);
    // å¦‚æžœ T å’Œ U çš„大小相等,直接转换
    if (sizeof(T) == sizeof(U)) {
        vecLow.assign(vecHigh.begin(), vecHigh.end());
        return;
    }
    // å¦‚æžœ T å’Œ U çš„大小相等,直接转换
    if (sizeof(T) == sizeof(U)) {
        vecLow.assign(vecHigh.begin(), vecHigh.end());
        return;
    }
    // å¦‚æžœ T çš„大小是 U çš„倍数,正常分解
    static_assert(sizeof(T) > sizeof(U), "Size of T must be greater than or equal to size of U");
    // å¦‚æžœ T çš„大小是 U çš„倍数,正常分解
    static_assert(sizeof(T) > sizeof(U), "Size of T must be greater than or equal to size of U");
    size_t nLowSize = vecHigh.size() * nGroupSize; // ä½Žå®¹å™¨çš„大小
    vecLow.resize(nLowSize, 0);
    size_t nLowSize = vecHigh.size() * nGroupSize; // ä½Žå®¹å™¨çš„大小
    vecLow.resize(nLowSize, 0);
    // åˆ†è§£é«˜ä½æ•°æ®åˆ°ä½Žä½æ•°æ®
    for (size_t i = 0; i < vecHigh.size(); i++) {
        for (size_t j = 0; j < nGroupSize; j++) {
            vecLow[i * nGroupSize + j] = static_cast<U>((vecHigh[i] >> (j * CHAR_BIT * sizeof(U))) & ((1ULL << (CHAR_BIT * sizeof(U))) - 1));
        }
    }
    // åˆ†è§£é«˜ä½æ•°æ®åˆ°ä½Žä½æ•°æ®
    for (size_t i = 0; i < vecHigh.size(); i++) {
        for (size_t j = 0; j < nGroupSize; j++) {
            vecLow[i * nGroupSize + j] = static_cast<U>((vecHigh[i] >> (j * CHAR_BIT * sizeof(U))) & ((1ULL << (CHAR_BIT * sizeof(U))) - 1));
        }
    }
    return vecLow;
    return vecLow;
}
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.h
@@ -12,151 +12,151 @@
#include <sstream>
#include <unordered_map>
// è¿žæŽ¥å‚æ•°
#define PLC_MAX_RETRY 3        // æœ€å¤§é‡è¯•次数:在与PLC通信时,如果发生通信错误,将最多重试3次
#define PLC_TIMEOUT 500        // è¶…时时间(毫秒):每次通信操作的超时等待时间为500毫秒
// è¿žæŽ¥å‚æ•°
#define PLC_MAX_RETRY 3        // æœ€å¤§é‡è¯•次数:在与PLC通信时,如果发生通信错误,将最多重试3次
#define PLC_TIMEOUT 500        // è¶…时时间(毫秒):每次通信操作的超时等待时间为500毫秒
/*
 * ç½‘络通道:指定通信所使用的网络通道号,通常在多通道通信中设置
 * 51 åˆ° 54 æ˜¯ MELSECNET/H çš„ 1-4 é€šé“
 * 81 åˆ° 84 æ˜¯ CC-Link çš„ 1-4 é€šé“
 * 151 åˆ° 154 æ˜¯ CC-Link IE æŽ§åˆ¶å™¨ç½‘络的 1-4 é€šé“
 * 181 åˆ° 184 æ˜¯ CC-Link IE çŽ°åœºç½‘ç»œçš„ 1-4 é€šé“
 * 281 åˆ° 284 æ˜¯ CC-Link IE TSN ç½‘络的 1-4 é€šé“
 * ç½‘络通道:指定通信所使用的网络通道号,通常在多通道通信中设置
 * 51 åˆ° 54 æ˜¯ MELSECNET/H çš„ 1-4 é€šé“
 * 81 åˆ° 84 æ˜¯ CC-Link çš„ 1-4 é€šé“
 * 151 åˆ° 154 æ˜¯ CC-Link IE æŽ§åˆ¶å™¨ç½‘络的 1-4 é€šé“
 * 181 åˆ° 184 æ˜¯ CC-Link IE çŽ°åœºç½‘ç»œçš„ 1-4 é€šé“
 * 281 åˆ° 284 æ˜¯ CC-Link IE TSN ç½‘络的 1-4 é€šé“
 **/
#define MELSECNET_CHANNEL(x) (50 + (x))           // x èŒƒå›´ï¼š1~4
#define CC_LINK_CHANNEL(x) (80 + (x))              // x èŒƒå›´ï¼š1~4
#define CC_LINK_IE_CONTROL_CHANNEL(x) (150 + (x)) // x èŒƒå›´ï¼š1~4
#define CC_LINK_IE_FIELD_CHANNEL(x) (180 + (x))   // x èŒƒå›´ï¼š1~4
#define CC_LINK_IE_TSN_CHANNEL(x) (280 + (x))     // x èŒƒå›´ï¼š1~4
#define MELSECNET_CHANNEL(x) (50 + (x))           // x èŒƒå›´ï¼š1~4
#define CC_LINK_CHANNEL(x) (80 + (x))              // x èŒƒå›´ï¼š1~4
#define CC_LINK_IE_CONTROL_CHANNEL(x) (150 + (x)) // x èŒƒå›´ï¼š1~4
#define CC_LINK_IE_FIELD_CHANNEL(x) (180 + (x))   // x èŒƒå›´ï¼š1~4
#define CC_LINK_IE_TSN_CHANNEL(x) (280 + (x))     // x èŒƒå›´ï¼š1~4
 // è‡ªå®šä¹‰é”™è¯¯ç 
#define ERROR_CODE_UNKNOWN                0x00010000 // Î´Öª
#define ERROR_CODE_NOT_CONNECTED        0x00020000 // æœªè¿žæŽ¥
#define ERROR_CODE_INVALID_PARAM        0x00030000 // å‚数无效
#define ERROR_CODE_INVALID_DATA            0x00040000 // æ•°æ®æ— æ•ˆ
#define ERROR_CODE_STATION_OUT_OF_RANGE 0x00050000 // ç«™å·è¶…出范围
#define ERROR_CODE_GROUP_OUT_OF_RANGE   0x00060000 // ç»„号超出范围
#define ERROR_CODE_NETWORK_OUT_OF_RANGE 0x00070000 // ç½‘络号超出范围
 // è‡ªå®šä¹‰é”™è¯¯ç 
#define ERROR_CODE_UNKNOWN                0x00010000 // æœªçŸ¥
#define ERROR_CODE_NOT_CONNECTED        0x00020000 // æœªè¿žæŽ¥
#define ERROR_CODE_INVALID_PARAM        0x00030000 // å‚数无效
#define ERROR_CODE_INVALID_DATA            0x00040000 // æ•°æ®æ— æ•ˆ
#define ERROR_CODE_STATION_OUT_OF_RANGE 0x00050000 // ç«™å·è¶…出范围
#define ERROR_CODE_GROUP_OUT_OF_RANGE   0x00060000 // ç»„号超出范围
#define ERROR_CODE_NETWORK_OUT_OF_RANGE 0x00070000 // ç½‘络号超出范围
// æ¿å—类型
// æ¿å—类型
enum class BoardType {
    UNKNOWN = -1,                                        // æœªçŸ¥ç±»åž‹
    UNKNOWN = -1,                                        // æœªçŸ¥ç±»åž‹
    MELSECNET_H = MELSECNET_CHANNEL(1),                    // MELSECNET/H
    CC_LINK_VER_2 = CC_LINK_CHANNEL(1),                    // CC-Link Ver. 2
    CC_LINK_IE_CONTROL = CC_LINK_IE_CONTROL_CHANNEL(1),    // CC-Link IE æŽ§åˆ¶ç½‘络
    CC_LINK_IE_FIELD = CC_LINK_IE_FIELD_CHANNEL(1),     // CC-Link IE çŽ°åœºç½‘ç»œ
    CC_LINK_IE_CONTROL = CC_LINK_IE_CONTROL_CHANNEL(1),    // CC-Link IE æŽ§åˆ¶ç½‘络
    CC_LINK_IE_FIELD = CC_LINK_IE_FIELD_CHANNEL(1),     // CC-Link IE çŽ°åœºç½‘ç»œ
    CC_LINK_IE_TSN = CC_LINK_IE_TSN_CHANNEL(1)          // CC-Link IE TSN
};
// è½¯å…ƒä»¶ç±»åž‹æžšä¸¾
// è½¯å…ƒä»¶ç±»åž‹æžšä¸¾
enum class DeviceType {
    /*
     * ER、LX、LY、LB、LW、LSB、LSW和SPG软元件都是范围型
     * ER:DevER0~256
     * LX:DevLX1~255,DevLX(x)    (DevX*1000+(x))
     * LY:DevLY1~255,DevLY(x)    (DevY*1000+(x))
     * LB:DevLB1~255,DevLB(x)    (DevB*1000+(x))
     * LW:DevLW1~255,DevLW(x)    (DevW*1000+(x))
     * LSB:DevLSB1~255,DevLSB(x) (DevQSB*1000+(x))
     * LSW:DevLSW1~255,DevLSW(x) (DevQSW*1000+(x))
     * SPG:DevSPG0~255,DevSPG(x) (29*1000+(x))
     * æ‰©å±•文件寄存器代码指定(10进制数)的后3位数及软元件名指定的数值中,应指定块No.(0~256)
     * é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ä»£ç æŒ‡å®š(10进制数)的后3位数及软元件名指定的数值中,应指定网络No.(1~255)
     * æ™ºèƒ½åŠŸèƒ½æ¨¡å—è½¯å…ƒä»¶ä»£ç æŒ‡å®š(10进制数)的后3位数及软元件名指定的数值中,应指定(起始I/ONo.÷16)的值
     * æ‰©å±•文件寄存器和链接直接软元件在随机读取(mdRandR、mdRandREx)函数中,即使指定实际不存在的软元件也有可能正常结束
     * MAIL和MAILMC在SEND功能及RECV功能中,与软元件访问一样,指定各功能对应的软元件类型,进行数据的发送(mdSend、mdSendEx)或数据的读取(mdReceive、mdReceiveEx)
     * ER、LX、LY、LB、LW、LSB、LSW和SPG软元件都是范围型
     * ER:DevER0~256
     * LX:DevLX1~255,DevLX(x)    (DevX*1000+(x))
     * LY:DevLY1~255,DevLY(x)    (DevY*1000+(x))
     * LB:DevLB1~255,DevLB(x)    (DevB*1000+(x))
     * LW:DevLW1~255,DevLW(x)    (DevW*1000+(x))
     * LSB:DevLSB1~255,DevLSB(x) (DevQSB*1000+(x))
     * LSW:DevLSW1~255,DevLSW(x) (DevQSW*1000+(x))
     * SPG:DevSPG0~255,DevSPG(x) (29*1000+(x))
     * æ‰©å±•文件寄存器代码指定(10进制数)的后3位数及软元件名指定的数值中,应指定块No.(0~256)
     * é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ä»£ç æŒ‡å®š(10进制数)的后3位数及软元件名指定的数值中,应指定网络No.(1~255)
     * æ™ºèƒ½åŠŸèƒ½æ¨¡å—è½¯å…ƒä»¶ä»£ç æŒ‡å®š(10进制数)的后3位数及软元件名指定的数值中,应指定(起始I/ONo.÷16)的值
     * æ‰©å±•文件寄存器和链接直接软元件在随机读取(mdRandR、mdRandREx)函数中,即使指定实际不存在的软元件也有可能正常结束
     * MAIL和MAILMC在SEND功能及RECV功能中,与软元件访问一样,指定各功能对应的软元件类型,进行数据的发送(mdSend、mdSendEx)或数据的读取(mdReceive、mdReceiveEx)
     **/
    X = 0x0001,      // è¾“å…¥ (位)
    Y = 0x0002,      // è¾“出 (位)
    L = 0x0003,      // é”å­˜ç»§ç”µå™¨ (位)
    M = 0x0004,      // å†…部继电器 (位)
    SM = 0x0005,     // ç‰¹æ®Šç»§ç”µå™¨ (位)
    F = 0x0006,      // æŠ¥è­¦å™¨ (位)
    TT = 0x0007,     // å®šæ—¶å™¨ (触点) (位)
    TC = 0x0008,     // è®¡æ•°å™¨ (线圈) (位)
    CT = 0x0009,     // è®¡æ•°å™¨ (触点) (位)
    CC = 0x000A,     // è®¡æ•°å™¨ (线圈) (字)
    TN = 0x000B,     // å®šæ—¶å™¨ (当前值) (字)
    CN = 0x000C,     // è®¡æ•°å™¨ (当前值) (字)
    D = 0x000D,      // æ•°æ®å¯„存器 (字)
    SD = 0x000E,     // ç‰¹æ®Šå¯„存器 (字)
    TM = 0x000F,     // å®šæ—¶å™¨ (设置值主) (字)
    TS = 0x0010,     // å®šæ—¶å™¨ (设置值主1) (字)
    TS2 = 0x3E82,    // å®šæ—¶å™¨ (设置值主2) (字)
    TS3 = 0x3E83,    // å®šæ—¶å™¨ (设置值主3) (字)
    CM = 0x0011,     // è®¡æ•°å™¨ (设置值主) (字)
    CS = 0x0012,     // è®¡æ•°å™¨ (设置值主1) (字)
    CS2 = 0x4652,    // è®¡æ•°å™¨ (设置值主2) (字)
    CS3 = 0x4653,    // è®¡æ•°å™¨ (设置值主3) (字)
    A = 0x0013,      // ç´¯åР噍 (字)
    Z = 0x0014,      // å˜å€å¯„存器 (字)
    V = 0x0015,      // å˜å€å¯„存器 (字)
    R = 0x0016,      // æ–‡ä»¶å¯„存器 (块切换方式) (字)
    ER = 0x55F0,     // æ‰©å±•文件寄存器 (块切换方式) (0x55F0~0x56F0) (字) (在随机读取(mdRandR、mdRandREx)函数中,即使指定实际不存在的软元件也有可能正常结束。(读取数据不正确。))
    ZR = 0x00DC,     // æ–‡ä»¶å¯„存器 (连号访问方式) (字)
    B = 0x0017,      // é“¾æŽ¥ç»§ç”µå™¨ (位)
    W = 0x0018,      // é“¾æŽ¥å¯„存器 (字)
    QSB = 0x0019,    // é“¾æŽ¥ç‰¹æ®Šç»§ç”µå™¨ (位)
    STT = 0x001A,    // ç´¯è®¡å®šæ—¶å™¨ (触点) (位)
    STC = 0x001B,    // ç´¯è®¡å®šæ—¶å™¨ (线圈) (位)
    QSW = 0x001C,    // é“¾æŽ¥ç‰¹æ®Šå¯„存器 (字)
    QV = 0x001E,     // å˜å€ç»§ç”µå™¨ (位)
    MRB = 0x0021,     // éšæœºè®¿é—®ç¼“冲 (字)
    STN = 0x0023,    // ç´¯è®¡å®šæ—¶å™¨ (当前值) (字)
    LZ = 0x0026,     // è¶…长变址寄存器 (双字)
    RD = 0x0027,     // åˆ·æ–°æ•°æ®å¯„存器 (字)
    LTT = 0x0029,    // è¶…长定时器 (触点) (位)
    LTC = 0x002A,    // è¶…长定时器 (线圈) (位)
    LTN = 0x002B,    // è¶…长定时器 (当前值) (双字)
    LCT = 0x002C,    // è¶…长计数器 (触点) (位)
    LCC = 0x002D,    // è¶…长计数器 (线圈) (位)
    LCN = 0x002E,    // è¶…长计数器 (当前值) (双字)
    LSTT = 0x002F,   // è¶…长累计定时器 (触点) (位)
    LSTC = 0x0030,   // è¶…长累计定时器 (线圈) (位)
    LSTN = 0x0031,   // è¶…长累计定时器 (当前值) (双字)
    SPB = 0x0032,     // ç¼“冲存储器 (字)
    MAIL = 0x0065,   // ç‰¹æ®Šè½¯å…ƒä»¶ç±»åž‹ï¼šé‚®ä»¶ç±»åž‹ (10进制 101)
    MAILMC = 0x0066, // ç‰¹æ®Šè½¯å…ƒä»¶ç±»åž‹ï¼šæ— ç¡®è®¤é‚®ä»¶ (10进制 102)
    LX = 0x03E8,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接输入) (0x03E9~0x04E7) (位)
    LY = 0x07D0,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接输出) (0x07D1~0x08CF) (位)
    LB = 0x59D8,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接继电器) (0x59D9~0x5AD7) (位)
    LW = 0x5DC0,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接寄存器) (0x5DC1~0x5EBF) (字)
    LSB = 0x61A8,    // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接特殊继电器) (0x61A9~0x62A7) (位)
    LSW = 0x6D60,    // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接特殊寄存器) (0x6D61~0x6E5F) (字)
    SPG = 0x7147,    // æ™ºèƒ½åŠŸèƒ½æ¨¡å—è½¯å…ƒä»¶ (0x7148~0x7247) (字)
    X = 0x0001,      // è¾“å…¥ (位)
    Y = 0x0002,      // è¾“出 (位)
    L = 0x0003,      // é”å­˜ç»§ç”µå™¨ (位)
    M = 0x0004,      // å†…部继电器 (位)
    SM = 0x0005,     // ç‰¹æ®Šç»§ç”µå™¨ (位)
    F = 0x0006,      // æŠ¥è­¦å™¨ (位)
    TT = 0x0007,     // å®šæ—¶å™¨ (触点) (位)
    TC = 0x0008,     // è®¡æ•°å™¨ (线圈) (位)
    CT = 0x0009,     // è®¡æ•°å™¨ (触点) (位)
    CC = 0x000A,     // è®¡æ•°å™¨ (线圈) (字)
    TN = 0x000B,     // å®šæ—¶å™¨ (当前值) (字)
    CN = 0x000C,     // è®¡æ•°å™¨ (当前值) (字)
    D = 0x000D,      // æ•°æ®å¯„存器 (字)
    SD = 0x000E,     // ç‰¹æ®Šå¯„存器 (字)
    TM = 0x000F,     // å®šæ—¶å™¨ (设置值主) (字)
    TS = 0x0010,     // å®šæ—¶å™¨ (设置值主1) (字)
    TS2 = 0x3E82,    // å®šæ—¶å™¨ (设置值主2) (字)
    TS3 = 0x3E83,    // å®šæ—¶å™¨ (设置值主3) (字)
    CM = 0x0011,     // è®¡æ•°å™¨ (设置值主) (字)
    CS = 0x0012,     // è®¡æ•°å™¨ (设置值主1) (字)
    CS2 = 0x4652,    // è®¡æ•°å™¨ (设置值主2) (字)
    CS3 = 0x4653,    // è®¡æ•°å™¨ (设置值主3) (字)
    A = 0x0013,      // ç´¯åР噍 (字)
    Z = 0x0014,      // å˜å€å¯„存器 (字)
    V = 0x0015,      // å˜å€å¯„存器 (字)
    R = 0x0016,      // æ–‡ä»¶å¯„存器 (块切换方式) (字)
    ER = 0x55F0,     // æ‰©å±•文件寄存器 (块切换方式) (0x55F0~0x56F0) (字) (在随机读取(mdRandR、mdRandREx)函数中,即使指定实际不存在的软元件也有可能正常结束。(读取数据不正确。))
    ZR = 0x00DC,     // æ–‡ä»¶å¯„存器 (连号访问方式) (字)
    B = 0x0017,      // é“¾æŽ¥ç»§ç”µå™¨ (位)
    W = 0x0018,      // é“¾æŽ¥å¯„存器 (字)
    QSB = 0x0019,    // é“¾æŽ¥ç‰¹æ®Šç»§ç”µå™¨ (位)
    STT = 0x001A,    // ç´¯è®¡å®šæ—¶å™¨ (触点) (位)
    STC = 0x001B,    // ç´¯è®¡å®šæ—¶å™¨ (线圈) (位)
    QSW = 0x001C,    // é“¾æŽ¥ç‰¹æ®Šå¯„存器 (字)
    QV = 0x001E,     // å˜å€ç»§ç”µå™¨ (位)
    MRB = 0x0021,     // éšæœºè®¿é—®ç¼“冲 (字)
    STN = 0x0023,    // ç´¯è®¡å®šæ—¶å™¨ (当前值) (字)
    LZ = 0x0026,     // è¶…长变址寄存器 (双字)
    RD = 0x0027,     // åˆ·æ–°æ•°æ®å¯„存器 (字)
    LTT = 0x0029,    // è¶…长定时器 (触点) (位)
    LTC = 0x002A,    // è¶…长定时器 (线圈) (位)
    LTN = 0x002B,    // è¶…长定时器 (当前值) (双字)
    LCT = 0x002C,    // è¶…长计数器 (触点) (位)
    LCC = 0x002D,    // è¶…长计数器 (线圈) (位)
    LCN = 0x002E,    // è¶…长计数器 (当前值) (双字)
    LSTT = 0x002F,   // è¶…长累计定时器 (触点) (位)
    LSTC = 0x0030,   // è¶…长累计定时器 (线圈) (位)
    LSTN = 0x0031,   // è¶…长累计定时器 (当前值) (双字)
    SPB = 0x0032,     // ç¼“冲存储器 (字)
    MAIL = 0x0065,   // ç‰¹æ®Šè½¯å…ƒä»¶ç±»åž‹ï¼šé‚®ä»¶ç±»åž‹ (10进制 101)
    MAILMC = 0x0066, // ç‰¹æ®Šè½¯å…ƒä»¶ç±»åž‹ï¼šæ— ç¡®è®¤é‚®ä»¶ (10进制 102)
    LX = 0x03E8,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接输入) (0x03E9~0x04E7) (位)
    LY = 0x07D0,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接输出) (0x07D1~0x08CF) (位)
    LB = 0x59D8,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接继电器) (0x59D9~0x5AD7) (位)
    LW = 0x5DC0,     // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接寄存器) (0x5DC1~0x5EBF) (字)
    LSB = 0x61A8,    // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接特殊继电器) (0x61A9~0x62A7) (位)
    LSW = 0x6D60,    // é“¾æŽ¥ç›´æŽ¥è½¯å…ƒä»¶ (链接特殊寄存器) (0x6D61~0x6E5F) (字)
    SPG = 0x7147,    // æ™ºèƒ½åŠŸèƒ½æ¨¡å—è½¯å…ƒä»¶ (0x7148~0x7247) (字)
};
// æ•°æ®ç±»åž‹
// æ•°æ®ç±»åž‹
enum class DataType {
    BIT = 1,   // Î» (1λ)
    WORD = 2,  // å­— (16位)
    DWORD = 4  // åŒå­— (32位)
    BIT = 1,   // ä½ (1位)
    WORD = 2,  // å­— (16位)
    DWORD = 4  // åŒå­— (32位)
};
// æŽ§åˆ¶ä»£ç 
// æŽ§åˆ¶ä»£ç 
enum class ControlCode {
    RUN = 0,   // è¿œç¨‹ RUN
    STOP = 1,  // è¿œç¨‹ STOP
    PAUSE = 2  // è¿œç¨‹ PAUSE
    RUN = 0,   // è¿œç¨‹ RUN
    STOP = 1,  // è¿œç¨‹ STOP
    PAUSE = 2  // è¿œç¨‹ PAUSE
};
// ç‰ˆæœ¬ä¿¡æ¯
// ç‰ˆæœ¬ä¿¡æ¯
struct BoardVersion {
    char fixedValue[2];       // å›ºå®šå€¼
    char checksum[2];         // æ ¡éªŒå’Œ
    char swVersion[2];        // è½¯ä»¶ç‰ˆæœ¬
    char date[6];             // æ—¥æœŸ (格式 YYMMDD)
    uint32_t reserved;        // ä¿ç•™åŒºåŸŸ (4 å­—节)
    char swModel[16];         // è½¯ä»¶åž‹å·
    char hwModel[16];         // ç¡¬ä»¶åž‹å·
    char twoPortMemory[2];    // ä¸¤ç«¯å£å­˜å‚¨å™¨å ç”¨å®¹é‡
    char twoPortAttribute[2]; // ä¸¤ç«¯å£å±žæ€§
    char availableBias[2];    // å¯ä½¿ç”¨åç½®
    char moduleType[10];      // æœºåž‹ç±»åž‹
    char fixedValue[2];       // å›ºå®šå€¼
    char checksum[2];         // æ ¡éªŒå’Œ
    char swVersion[2];        // è½¯ä»¶ç‰ˆæœ¬
    char date[6];             // æ—¥æœŸ (格式 YYMMDD)
    uint32_t reserved;        // ä¿ç•™åŒºåŸŸ (4 å­—节)
    char swModel[16];         // è½¯ä»¶åž‹å·
    char hwModel[16];         // ç¡¬ä»¶åž‹å·
    char twoPortMemory[2];    // ä¸¤ç«¯å£å­˜å‚¨å™¨å ç”¨å®¹é‡
    char twoPortAttribute[2]; // ä¸¤ç«¯å£å±žæ€§
    char availableBias[2];    // å¯ä½¿ç”¨åç½®
    char moduleType[10];      // æœºåž‹ç±»åž‹
    // è¾“出结构体内容为字符串 (便于调试)
    // è¾“出结构体内容为字符串 (便于调试)
    std::string toString() const {
        std::ostringstream oss;
        oss << "Fixed Value: " << fixedValue[0] << fixedValue[1] << "\n"
@@ -174,18 +174,18 @@
    }
};
// ç«™ç‚¹æ ‡è¯†ç¬¦ï¼Œé»˜è®¤ä½¿ç”¨æœ¬ç«™
// ç«™ç‚¹æ ‡è¯†ç¬¦ï¼Œé»˜è®¤ä½¿ç”¨æœ¬ç«™
struct StationIdentifier {
    /*
     * [Network No.]
     * 0 è¡¨ç¤ºæœ¬ç«™
     * 1~239 è¡¨ç¤ºæ™®é€šç½‘络号
     * 0 è¡¨ç¤ºæœ¬ç«™
     * 1~239 è¡¨ç¤ºæ™®é€šç½‘络号
     **/
     /*
      * [Station No.]
      * MELSECNET/H:1~64 è¡¨ç¤ºå…¶ä»–站点,255 è¡¨ç¤ºæœ¬ç«™
      * CC-Link ç³»åˆ—网络的范围类似,区别在于站号的取值范围
      * MELSECNET/H:1~64 è¡¨ç¤ºå…¶ä»–站点,255 è¡¨ç¤ºæœ¬ç«™
      * CC-Link ç³»åˆ—网络的范围类似,区别在于站号的取值范围
      * MELSECNET/H             : 1~64(Other stations),255(Own station)
      * CC-Link                 : 0~63(Other stations),255(Own station)
      * CC-Link IE Controller   : 1~120(Other stations),255(Own station)
@@ -194,15 +194,15 @@
      **/
      /*
       * é«˜ 8 ä½ï¼ˆç½‘络号): æŒ‡å®šè®¾å¤‡æ‰€å±žçš„网络
       * ä½Ž 8 ä½ï¼ˆç«™ç‚¹å·ï¼‰ï¼š æŒ‡å®šè®¾å¤‡åœ¨ç½‘络中的编号
       * ç”¨ä¸€ä¸ªå‚数传递设备的网络号和站点号时: nSt = station.nStNo | ((station.nNetNo << 8) & 0xFF00);
       * é«˜ 8 ä½ï¼ˆç½‘络号): æŒ‡å®šè®¾å¤‡æ‰€å±žçš„网络
       * ä½Ž 8 ä½ï¼ˆç«™ç‚¹å·ï¼‰ï¼š æŒ‡å®šè®¾å¤‡åœ¨ç½‘络中的编号
       * ç”¨ä¸€ä¸ªå‚数传递设备的网络号和站点号时: nSt = station.nStNo | ((station.nNetNo << 8) & 0xFF00);
       **/
    short nNetNo = 0;    // ç½‘络编号:PLC所连接的网络编号,0表示默认网络
    short nStNo = 255;   // ç«™ç‚¹ç¼–号:指定与PLC连接的站点编号,255通常表示广播或所有站点
    short nNetNo = 0;    // ç½‘络编号:PLC所连接的网络编号,0表示默认网络
    short nStNo = 255;   // ç«™ç‚¹ç¼–号:指定与PLC连接的站点编号,255通常表示广播或所有站点
    // è‡ªå®šä¹‰æž„造函数,覆盖默认值
    // è‡ªå®šä¹‰æž„造函数,覆盖默认值
    explicit StationIdentifier(const short net, const short st) : nNetNo(net), nStNo(st) {}
    StationIdentifier() 
@@ -211,24 +211,24 @@
        nStNo = 255;
    }
    // å°†â€œç½‘络号”和“站点号”组合成一个最终编码
    // å°†â€œç½‘络号”和“站点号”组合成一个最终编码
    short StationIdentifier::toNetworkStationCode() const {
        return static_cast<short>(nStNo | ((nNetNo << 8) & 0xFF00));
    }
    // é‡è½½ < è¿ç®—符(用于排序或比较,通常用于 map æˆ– set ä¸­ä½œä¸º key)
    // é‡è½½ < è¿ç®—符(用于排序或比较,通常用于 map æˆ– set ä¸­ä½œä¸º key)
    bool operator<(const StationIdentifier& other) const {
        return std::tie(nNetNo, nStNo) <
            std::tie(other.nNetNo, other.nStNo);
    }
    // é‡è½½ == è¿ç®—符(用于相等比较)
    // é‡è½½ == è¿ç®—符(用于相等比较)
    bool operator==(const StationIdentifier& other) const {
        return std::tie(nNetNo, nStNo) ==
            std::tie(other.nNetNo, other.nStNo);
    }
    // é‡è½½ = è¿ç®—符(用于赋值)
    // é‡è½½ = è¿ç®—符(用于赋值)
    StationIdentifier& operator=(const StationIdentifier& other) {
        if (this != &other) {
            nNetNo = other.nNetNo;
@@ -238,16 +238,16 @@
    }
};
// æ¿çŠ¶æ€
// æ¿çŠ¶æ€
struct BoardStatus {
    short nStationValue = 0;    // ç«™å·çš„设备值 (buf[0])
    short nGroupValue = 0;      // ç»„ No. çš„设备值 (buf[1])
    short nNetworkValue = 0;    // ç½‘络 No. çš„设备值 (buf[2])
    short nReserved1 = 0;       // ä¿ç•™å­—段 (buf[3])
    short nReserved2 = 0;       // ä¿ç•™å­—段 (buf[4])
    short nReserved3 = 0;       // ä¿ç•™å­—段 (buf[5])
    short nStationValue = 0;    // ç«™å·çš„设备值 (buf[0])
    short nGroupValue = 0;      // ç»„ No. çš„设备值 (buf[1])
    short nNetworkValue = 0;    // ç½‘络 No. çš„设备值 (buf[2])
    short nReserved1 = 0;       // ä¿ç•™å­—段 (buf[3])
    short nReserved2 = 0;       // ä¿ç•™å­—段 (buf[4])
    short nReserved3 = 0;       // ä¿ç•™å­—段 (buf[5])
    // å°†æ•°ç»„映射到结构体
    // å°†æ•°ç»„映射到结构体
    static BoardStatus fromBuffer(const short buf[6]) {
        return {
            buf[0],
@@ -259,7 +259,7 @@
        };
    }
    // å°†ç»“构体内容映射到数组
    // å°†ç»“构体内容映射到数组
    void toBuffer(short buf[6]) const {
        buf[0] = nStationValue;
        buf[1] = nGroupValue;
@@ -269,7 +269,7 @@
        buf[5] = nReserved3;
    }
    // è°ƒè¯•输出
    // è°ƒè¯•输出
    std::string toString() const {
        std::ostringstream oss;
        oss << "Station Value: " << nStationValue << "\n"
@@ -282,12 +282,12 @@
    }
};
// äº‹ä»¶è¯¦æƒ…
// äº‹ä»¶è¯¦æƒ…
struct EventDetails {
    short nEventNo;                      // å‘生的事件号
    std::array<short, 4> details;         // å­˜å‚¨äº‹ä»¶è¯¦æƒ…信息
    short nEventNo;                      // å‘生的事件号
    std::array<short, 4> details;         // å­˜å‚¨äº‹ä»¶è¯¦æƒ…信息
    // è§£æžäº‹ä»¶è¯¦æƒ…,返回格式化字符串
    // è§£æžäº‹ä»¶è¯¦æƒ…,返回格式化字符串
    std::string toString() const {
        std::ostringstream oss;
        oss << "Details[0]: " << details[0] << ", "
@@ -298,33 +298,33 @@
    }
};
// SoftElement ç»“构体定义
// SoftElement ç»“构体定义
struct SoftElement {
    short nType;         // è½¯å…ƒä»¶ç±»åž‹
    short nElementCount; // ç‚¹æ•°
    long nStartNo;       // èµ·å§‹è½¯å…ƒä»¶ç¼–号
    short nType;         // è½¯å…ƒä»¶ç±»åž‹
    short nElementCount; // ç‚¹æ•°
    long nStartNo;       // èµ·å§‹è½¯å…ƒä»¶ç¼–号
};
// é”™è¯¯ä¿¡æ¯
// é”™è¯¯ä¿¡æ¯
struct ErrorInfo {
    int nErrorCode = 0;              // é”™è¯¯ç 
    std::string strErrorMessageCn;   // ä¸­æ–‡æè¿°
    std::string strErrorMessageEn;   // è‹±æ–‡æè¿°
    int nErrorCode = 0;              // é”™è¯¯ç 
    std::string strErrorMessageCn;   // ä¸­æ–‡æè¿°
    std::string strErrorMessageEn;   // è‹±æ–‡æè¿°
    // å°†ç»“构体序列化为字符串
    // å°†ç»“构体序列化为字符串
    std::string toString() const {
        std::ostringstream oss;
        oss << nErrorCode << "|" << strErrorMessageCn << "|" << strErrorMessageEn;
        return oss.str();
    }
    // ä»Žå­—符串反序列化为结构体
    // ä»Žå­—符串反序列化为结构体
    static ErrorInfo fromString(const std::string& line) {
        ErrorInfo info;
        std::istringstream iss(line);
        std::string token;
        // ä½¿ç”¨åˆ†éš”符 "|" è§£æžå­—符串
        // ä½¿ç”¨åˆ†éš”符 "|" è§£æžå­—符串
        std::getline(iss, token, '|');
        info.nErrorCode = std::stoi(token);
@@ -338,47 +338,47 @@
    }
};
using BitContainer = std::vector<bool>;            // æ¯ä¸ªå…ƒç´ å­˜å‚¨ 1  ä½
using WordContainer = std::vector<uint16_t>;    // æ¯ä¸ªå…ƒç´ å­˜å‚¨ 16 ä½
using DWordContainer = std::vector<uint32_t>;    // æ¯ä¸ªå…ƒç´ å­˜å‚¨ 32 ä½
using BitContainer = std::vector<bool>;            // æ¯ä¸ªå…ƒç´ å­˜å‚¨ 1  ä½
using WordContainer = std::vector<uint16_t>;    // æ¯ä¸ªå…ƒç´ å­˜å‚¨ 16 ä½
using DWordContainer = std::vector<uint32_t>;    // æ¯ä¸ªå…ƒç´ å­˜å‚¨ 32 ä½
// CPerformanceMelsec ç±»å£°æ˜Ž
// CPerformanceMelsec ç±»å£°æ˜Ž
class CPerformanceMelsec {
public:
    // èŽ·å–æœ€è¿‘çš„é”™è¯¯ä¿¡æ¯
    // èŽ·å–æœ€è¿‘çš„é”™è¯¯ä¿¡æ¯
    std::string GetLastError() const;
    // é”™è¯¯ä¿¡æ¯åŠ è½½ä¸Žä¿å­˜æŽ¥å£
    static bool LoadErrorInfoFromFile(const std::string& filename);  // ä»Žæ–‡ä»¶åŠ è½½é”™è¯¯ä¿¡æ¯
    static bool SaveErrorInfoToFile(const std::string& filename);    // ä¿å­˜é”™è¯¯ä¿¡æ¯åˆ°æ–‡ä»¶
    // é”™è¯¯ä¿¡æ¯åŠ è½½ä¸Žä¿å­˜æŽ¥å£
    static bool LoadErrorInfoFromFile(const std::string& filename);  // ä»Žæ–‡ä»¶åŠ è½½é”™è¯¯ä¿¡æ¯
    static bool SaveErrorInfoToFile(const std::string& filename);    // ä¿å­˜é”™è¯¯ä¿¡æ¯åˆ°æ–‡ä»¶
    // è¿žæŽ¥/断开
    // è¿žæŽ¥/断开
    int Connect(short nChannel, short nMode = -1);
    int Disconnect();
    // åˆå§‹åŒ–可编程控制器软元件信息表
    // åˆå§‹åŒ–可编程控制器软元件信息表
    int InitializeController();
    //    èŽ·å–ç‰ˆæœ¬ä¿¡æ¯
    //    èŽ·å–ç‰ˆæœ¬ä¿¡æ¯
    int GetBoardVersion(BoardVersion& version);
    // æ¿å¤ä½
    // æ¿å¤ä½
    int BoardReset();
    // æ¿LED读取
    // æ¿LED读取
    int ReadBoardLed(std::vector<short>& vecLedBuffer);
    // è¯»å–目标站点CPU类型
    // è¯»å–目标站点CPU类型
    int ReadCPUCode(const StationIdentifier& station, short& nCPUCode);
    // æ¿æ¨¡å¼èŽ·å–/设置
    // æ¿æ¨¡å¼èŽ·å–/设置
    int SetBoardMode(short nMode);
    int GetBoardMode(short& nMode);
    // èŽ·å–æ¿çŠ¶æ€
    // èŽ·å–æ¿çŠ¶æ€
    int GetBoardStatus(BoardStatus& status);
    // è¯»å†™æ•°æ®
    // è¯»å†™æ•°æ®
    int ReadData(const StationIdentifier& station, long nDevType, long nDevNo, long nSize, std::vector<short>& vecData);
    int ReadBitData(const StationIdentifier& station, DeviceType enDevType, short nDevNo, short nBitCount, BitContainer& vecData);
    int ReadWordData(const StationIdentifier& station, DeviceType enDevType, short nDevNo, short nWordCount, WordContainer& vecData);
@@ -388,7 +388,7 @@
    int WriteWordData(const StationIdentifier& station, DeviceType enDevType, short nDevNo, const WordContainer& vecData);
    int WriteDWordData(const StationIdentifier& station, DeviceType enDevType, short nDevNo, const DWordContainer& vecData);
    // æ‰©å±•读写数据
    // æ‰©å±•读写数据
    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);
@@ -398,56 +398,56 @@
    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);
    long WriteRandomDataEx(const StationIdentifier& station, const std::vector<SoftElement>& vecSoftElements, const std::vector<char>& vecData);
    // è¿œç¨‹è®¾å¤‡ç«™/远程站的缓冲存储器读写
    // è¿œç¨‹è®¾å¤‡ç«™/远程站的缓冲存储器读写
    long ReadRemoteBuffer(const StationIdentifier& station, long nOffset, long nSize, std::vector<char>& vecData);
    long WriteRemoteBuffer(const StationIdentifier& station, long nOffset, const std::vector<char>& vecData);
    long ReadRemoteBufferByIp(const std::string& strIP, long nOffset, long nSize, std::vector<char>& vecData);
    long WriteRemoteBufferByIp(const std::string& strIP, long nOffset, const std::vector<char>& vecData);
    // è®¾ç½®/复位对象站的指定位软元件
    // è®¾ç½®/复位对象站的指定位软元件
    int SetBitDevice(const StationIdentifier& station, DeviceType enDevType, short nDevNo);
    int ResetBitDevice(const StationIdentifier& station, DeviceType enDevType, short enDevNo);
    // æ‰©å±•设置/复位对象站的指定位软元件
    // æ‰©å±•设置/复位对象站的指定位软元件
    long SetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo);
    long ResetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo);
    // æ‰§è¡Œå¯¹è±¡ç«™çš„CPU
    // æ‰§è¡Œå¯¹è±¡ç«™çš„CPU
    int ControlCPU(const StationIdentifier& station, ControlCode enControlCode);
    // äº‹ä»¶ç­‰å¾…,vecEventNumbers[0, 64],nTimeoutMs[-1, 2147483647]
    // åŒæ—¶å‘生了多个事件的情况下,首先检测出其中一个事件。 å†æ¬¡æ‰§è¡Œäº†æœ¬å‡½æ•°çš„æƒ…况下检测出其它事件。
    // äº‹ä»¶ç­‰å¾…,vecEventNumbers[0, 64],nTimeoutMs[-1, 2147483647]
    // åŒæ—¶å‘生了多个事件的情况下,首先检测出其中一个事件。 å†æ¬¡æ‰§è¡Œäº†æœ¬å‡½æ•°çš„æƒ…况下检测出其它事件。
    int WaitForBoardEvent(std::vector<short> vecEventNumbers, int nTimeoutMs, EventDetails& details);
private:
    // é”å®šä¸Žè§£é”ï¼ˆå¤šçº¿ç¨‹åŒæ­¥ä¿æŠ¤ï¼‰
    // é”å®šä¸Žè§£é”ï¼ˆå¤šçº¿ç¨‹åŒæ­¥ä¿æŠ¤ï¼‰
    void Lock() { m_mtx.lock(); }
    void Unlock() { m_mtx.unlock(); }
protected:
    // æž„造函数/析构函数
    // æž„造函数/析构函数
    explicit CPerformanceMelsec(BoardType enBoardType);
    virtual ~CPerformanceMelsec();
    // è¾…助函数
    void UpdateLastError(int nCode);                                 // æ›´æ–°æœ€è¿‘的错误信息
    int ValidateStation(const StationIdentifier& station) const;     // æ£€æŸ¥è¿žæŽ¥çŠ¶æ€å’Œç«™ç‚¹å‚æ•°æœ‰æ•ˆæ€§
    // è¾…助函数
    void UpdateLastError(int nCode);                                 // æ›´æ–°æœ€è¿‘的错误信息
    int ValidateStation(const StationIdentifier& station) const;     // æ£€æŸ¥è¿žæŽ¥çŠ¶æ€å’Œç«™ç‚¹å‚æ•°æœ‰æ•ˆæ€§
    int ValidateStationAndSize(const StationIdentifier& station, short nCount) const;
    // é™æ€è¾…助函数
    static void Delay(unsigned int nDelayMs);                        // å»¶æ—¶ï¼Œå¹¶ä¸”转发窗口消息
    static BoardType FindBoardTypeByChannel(int nChannel);            // æŸ¥æ‰¾æ¿å—类型
    static short CombineStation(const StationIdentifier& station);  // åˆå¹¶ç½‘络号和站点号
    static short CalculateDeviceType(const StationIdentifier& station, DeviceType enDevType); // è®¡ç®—软元件类型
    // é™æ€è¾…助函数
    static void Delay(unsigned int nDelayMs);                        // å»¶æ—¶ï¼Œå¹¶ä¸”转发窗口消息
    static BoardType FindBoardTypeByChannel(int nChannel);            // æŸ¥æ‰¾æ¿å—类型
    static short CombineStation(const StationIdentifier& station);  // åˆå¹¶ç½‘络号和站点号
    static short CalculateDeviceType(const StationIdentifier& station, DeviceType enDevType); // è®¡ç®—软元件类型
    // IP转换
    // IP转换
    static bool ConvertIpStringToUint32(const std::string& strIP, uint32_t& nIP);
    // å®¹å™¨è½¬æ¢
    // å®¹å™¨è½¬æ¢
    static void ConvertCharToShort(const std::vector<char>& vecChar, std::vector<short>& vecShort);
    static void ConvertShortToChar(const std::vector<short>& vecShort, std::vector<char>& vecChar);
    static void ConvertUint8ToShort(const std::vector<uint8_t>& vecUint8, std::vector<short>& vecShort);
@@ -455,7 +455,7 @@
    static void ConvertUint32ToShort(const std::vector<uint32_t>& vecUint32, std::vector<short>& vecShort);
    static void ConvertShortToUint32(const std::vector<short>& vecShort, std::vector<uint32_t>& vecUint32);
    // æ¨¡æ¿è¾…助函数
    // æ¨¡æ¿è¾…助函数
    template <typename T>
    int ValidateStationAndData(const StationIdentifier& station, const std::vector<T>& vecData);
@@ -465,15 +465,15 @@
    template <typename T, typename U>
    void ConvertHighToLow(const std::vector<T>& vecHigh, std::vector<U>& vecLow);
    // æˆå‘˜å˜é‡
    std::mutex m_mtx;                       // äº’斥锁保护
    BoardType m_enBoardType;                // æ¿å—类型
    long m_nPath;                           // é€šä¿¡è·¯å¾„
    std::atomic<bool> m_bConnected;         // æ˜¯å¦å·²è¿žæŽ¥
    std::string m_strLastError;             // æœ€è¿‘一次错误信息
    // æˆå‘˜å˜é‡
    std::mutex m_mtx;                       // äº’斥锁保护
    BoardType m_enBoardType;                // æ¿å—类型
    long m_nPath;                           // é€šä¿¡è·¯å¾„
    std::atomic<bool> m_bConnected;         // æ˜¯å¦å·²è¿žæŽ¥
    std::string m_strLastError;             // æœ€è¿‘一次错误信息
    // é™æ€æˆå‘˜å˜é‡
    static std::unordered_map<int, std::string> m_mapError; // é”™è¯¯ç æ˜ å°„表
    // é™æ€æˆå‘˜å˜é‡
    static std::unordered_map<int, std::string> m_mapError; // é”™è¯¯ç æ˜ å°„表
};
#endif // PERFORMANCE_MELSEC_H
SourceCode/Bond/SGMeasurement/Logger.cpp
@@ -3,8 +3,8 @@
CLogger& CLogger::Instance()
{
    static CLogger instance;
    return instance;
    static CLogger instance;
    return instance;
}
CLogger::CLogger()
@@ -14,66 +14,66 @@
CLogger::~CLogger()
{
    CloseLogFile();
    CloseLogFile();
}
void CLogger::OpenLogFile()
{
    CSingleLock lock(&m_csLogLock, TRUE);
    CSingleLock lock(&m_csLogLock, TRUE);
    CTime now = CTime::GetCurrentTime();
    CString strLogDir = _T("Log");
    CTime now = CTime::GetCurrentTime();
    CString strLogDir = _T("Log");
    if (!PathFileExists(strLogDir)) {
        CreateDirectory(strLogDir, NULL);
    }
    if (!PathFileExists(strLogDir)) {
        CreateDirectory(strLogDir, NULL);
    }
    CString strNewPath;
    strNewPath.Format(_T("%s\\SG_%04d%02d%02d.log"), strLogDir, now.GetYear(), now.GetMonth(), now.GetDay());
    CString strNewPath;
    strNewPath.Format(_T("%s\\SG_%04d%02d%02d.log"), strLogDir, now.GetYear(), now.GetMonth(), now.GetDay());
    if (m_strCurrentLogPath.CompareNoCase(strNewPath) != 0 || m_logFile.m_pStream == nullptr) {
        if (m_logFile.m_pStream) {
            m_logFile.Flush();
            m_logFile.Close();
        }
    if (m_strCurrentLogPath.CompareNoCase(strNewPath) != 0 || m_logFile.m_pStream == nullptr) {
        if (m_logFile.m_pStream) {
            m_logFile.Flush();
            m_logFile.Close();
        }
        if (m_logFile.Open(strNewPath,
            CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::typeBinary)) {
        if (m_logFile.Open(strNewPath,
            CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::typeBinary)) {
            if (m_logFile.GetLength() == 0) {
                WCHAR bom = 0xFEFF;
                m_logFile.Write(&bom, sizeof(WCHAR));
            }
            if (m_logFile.GetLength() == 0) {
                WCHAR bom = 0xFEFF;
                m_logFile.Write(&bom, sizeof(WCHAR));
            }
            m_logFile.SeekToEnd();
            m_strCurrentLogPath = strNewPath;
        }
    }
            m_logFile.SeekToEnd();
            m_strCurrentLogPath = strNewPath;
        }
    }
}
void CLogger::WriteLine(CString str)
{
    CSingleLock lock(&m_csLogLock, TRUE);
    CSingleLock lock(&m_csLogLock, TRUE);
    OpenLogFile();
    OpenLogFile();
    if (m_logFile.m_pStream) {
        CTime now = CTime::GetCurrentTime();
        CString strTime;
        strTime.Format(_T("[%02d:%02d:%02d]"), now.GetHour(), now.GetMinute(), now.GetSecond());
    if (m_logFile.m_pStream) {
        CTime now = CTime::GetCurrentTime();
        CString strTime;
        strTime.Format(_T("[%02d:%02d:%02d]"), now.GetHour(), now.GetMinute(), now.GetSecond());
        CString strLine = strTime + str + _T("\r\n");
        m_logFile.Write((LPCTSTR)strLine, strLine.GetLength() * sizeof(WCHAR));
    }
        CString strLine = strTime + str + _T("\r\n");
        m_logFile.Write((LPCTSTR)strLine, strLine.GetLength() * sizeof(WCHAR));
    }
}
void CLogger::CloseLogFile()
{
    CSingleLock lock(&m_csLogLock, TRUE);
    CSingleLock lock(&m_csLogLock, TRUE);
    if (m_logFile.m_pStream) {
        m_logFile.Flush();
        m_logFile.Close();
        m_strCurrentLogPath.Empty();
    }
    if (m_logFile.m_pStream) {
        m_logFile.Flush();
        m_logFile.Close();
        m_strCurrentLogPath.Empty();
    }
}
SourceCode/Bond/SGMeasurement/Logger.h
@@ -4,25 +4,25 @@
class CLogger
{
public:
    static CLogger& Instance();  // èŽ·å–å•ä¾‹
    void WriteLine(CString str); // å†™ä¸€è¡Œæ—¥å¿—
    void CloseLogFile();         // å…³é—­æ—¥å¿—文件
    static CLogger& Instance();  // èŽ·å–å•ä¾‹
    void WriteLine(CString str); // å†™ä¸€è¡Œæ—¥å¿—
    void CloseLogFile();         // å…³é—­æ—¥å¿—文件
private:
    CLogger();
    ~CLogger();
    void OpenLogFile();          // å†…部打开文件
    CLogger();
    ~CLogger();
    void OpenLogFile();          // å†…部打开文件
    CCriticalSection m_csLogLock;
    CString m_strCurrentLogPath;
    CStdioFile m_logFile;
    CCriticalSection m_csLogLock;
    CString m_strCurrentLogPath;
    CStdioFile m_logFile;
};
#define LOG_LINE(x) CLogger::Instance().WriteLine(x)
#define LOG_LINEF(fmt, ...)                     \
    do {                                        \
        CString __log__;                        \
        __log__.Format(fmt, __VA_ARGS__);       \
        CLogger::Instance().WriteLine(__log__); \
    } while (0)
    do {                                        \
        CString __log__;                        \
        __log__.Format(fmt, __VA_ARGS__);       \
        CLogger::Instance().WriteLine(__log__); \
    } while (0)
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp
@@ -1,309 +1,356 @@
#include "pch.h"
#include "PLCSignalListener.h"
// === æ—¥å¿—打印类型 ===
// === æ—¥å¿—打印类型 ===
#define LOG_TYPE_ERROR     -1
#define LOG_TYPE_SUCCESS    0
#define LOG_TYPE_WARNING    1
#define LOG_TYPE_NORMAL     2
// === æ—¥å¿—打印宏定义 ===
// === æ—¥å¿—打印宏定义 ===
#define LOG_MSG(msg, type) LogInfo(msg, type)
// === PLC å¿ƒè·³ç›¸å…³é…ç½® ===
#define PLC_HEARTBEAT_PC_TO_PLC_ADDR   0x107F   // PC -> PLC:PC å†™å…¥å¿ƒè·³
#define PLC_HEARTBEAT_PLC_TO_PC_ADDR   0x6C40   // PLC -> PC:PC è¯»å– PLC å†™å…¥çš„心跳
#define MAX_MISSED_HEARTBEAT           5        // å…è®¸è¿žç»­ä¸¢å¤±å¿ƒè·³çš„æœ€å¤§æ¬¡æ•°ï¼Œè¶…过则判定 PLC æŽ‰çº¿
// === PLC å¿ƒè·³ç›¸å…³é…ç½® ===
#define PLC_HEARTBEAT_PC_TO_PLC_ADDR   0x107F   // PC -> PLC:PC å†™å…¥å¿ƒè·³
#define PLC_HEARTBEAT_PLC_TO_PC_ADDR   0x6C40   // PLC -> PC:PC è¯»å– PLC å†™å…¥çš„心跳
#define MAX_MISSED_HEARTBEAT           5        // å…è®¸è¿žç»­ä¸¢å¤±å¿ƒè·³çš„æœ€å¤§æ¬¡æ•°ï¼Œè¶…过则判定 PLC æŽ‰çº¿
// === PLC å‘½ä»¤è¾“入配置(PLC -> PC) ===
#define PLC_CMD_BIT_START       0x6CD3  // PLC命令起始位(通常为B6CD3)
#define PLC_CMD_BIT_COUNT       2       // æ€»å…±å‡ ä¸ªå‘½ä»¤ä½ï¼ˆB6CD3=Start, B6CD4=Stop)
// === PLC å‘½ä»¤è¾“入配置(PLC -> PC) ===
#define PLC_CMD_BIT_START       0x6CD3          // PLC命令起始位(通常为B6CD3)
#define PLC_CMD_BIT_COUNT       2               // æ€»å…±å‡ ä¸ªå‘½ä»¤ä½ï¼ˆB6CD3=Start, B6CD4=Stop)
// === PLC åº”答输出配置(PC -> PLC) ===
#define PLC_ACK_MAX_LIFE        25      // PLC响应信号最大保留周期数(每周期为 m_nIntervalMs æ¯«ç§’)
#define PLC_ACK_BASE_BIT        0x1060  // PLC应答起始地址(B1060表示B6CD3的应答;B1061表示B6CD4的应答)
// === PLC åº”答输出配置(PC -> PLC) ===
#define PLC_ACK_MAX_LIFE        25              // PLC响应信号最大保留周期数(每周期为 m_nIntervalMs æ¯«ç§’)
#define PLC_ACK_BASE_BIT        0x1060          // PLC应答起始地址(B1060表示B6CD3的应答;B1061表示B6CD4的应答)
// === PLC软元件类型宏(用于应答、数据写入)===
#define PLC_BIT_DEVICE_TYPE     DeviceType::B   // ä½æ“ä½œè®¾å¤‡ç±»åž‹ï¼ˆå¦‚M、B)
#define PLC_WORD_DEVICE_TYPE    DeviceType::W   // å­—操作设备类型(如D、W)
// === PLC软元件类型宏(用于应答、数据写入)===
#define PLC_BIT_DEVICE_TYPE     DeviceType::B   // ä½æ“ä½œè®¾å¤‡ç±»åž‹ï¼ˆå¦‚M、B)
#define PLC_WORD_DEVICE_TYPE    DeviceType::W   // å­—操作设备类型(如D、W)
// === PLC结果寄存器地址配置 ===
#define PLC_RESULT_ADDR_START   0x37B0  // PLC结果寄存器起始地址(如W37B0)
#define PLC_RESULT_ADDR_COUNT   4       // ç»“果寄存器数量(如W37B0, W37B2, W37B4, W37B6)
// === PLC结果寄存器地址配置 ===
#define PLC_RESULT_ADDR_START   0x37B0          // PLC结果寄存器起始地址(如W37B0)
#define PLC_RESULT_ADDR_COUNT   4               // ç»“果寄存器数量(如W37B0, W37B2, W37B4, W37B6)
// === PLC äº§å“ID配置(PLC -> PC)===
#define PLC_PRODUCT_ID_ADDR      0x1B160        // äº§å“ID起始地址 (W1B160)
#define PLC_PRODUCT_ID_WORDS     10             // äº§å“ID长度(10个Word)
#define IS_RISING_EDGE(prev, curr) (!(prev) && (curr))
CPLCSignalListener::CPLCSignalListener() = default;
CPLCSignalListener::~CPLCSignalListener() {
    Stop();
    Stop();
}
bool CPLCSignalListener::Initialize(StationIdentifier station, int nIntervalMs/* = 200*/)
{
    m_pPlc = std::make_unique<CCCLinkIEControl>();
    if (!m_pPlc) {
        LOG_MSG(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl å®žä¾‹ã€‚"), LOG_TYPE_ERROR);
        return false;
    }
    m_pPlc = std::make_unique<CCCLinkIEControl>();
    if (!m_pPlc) {
        LOG_MSG(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl å®žä¾‹ã€‚"), LOG_TYPE_ERROR);
        return false;
    }
    int ret = m_pPlc->Connect(CC_LINK_IE_CONTROL_CHANNEL(1));
    if (ret != 0) {
        m_bConnected = false;
    int ret = m_pPlc->Connect(CC_LINK_IE_CONTROL_CHANNEL(1));
    if (ret != 0) {
        m_bConnected = false;
        CString strError;
        strError.Format(_T("PLC控制器连接失败,错误码:%d"), ret);
        LOG_MSG(strError, LOG_TYPE_ERROR);
        CString strError;
        strError.Format(_T("PLC控制器连接失败,错误码:%d"), ret);
        LOG_MSG(strError, LOG_TYPE_ERROR);
        return false;
    }
        return false;
    }
    m_bConnected = true;
    m_station = station;
    m_nIntervalMs = nIntervalMs;
    m_bConnected = true;
    m_station = station;
    m_nIntervalMs = nIntervalMs;
    m_vecPrevBits.assign(PLC_CMD_BIT_COUNT, false);
    m_vecPrevBits.assign(PLC_CMD_BIT_COUNT, false);
    return true;
    return true;
}
void CPLCSignalListener::SetStartCallback(Callback cb)
{
    m_cbStart = std::move(cb);
    m_cbStart = std::move(cb);
}
void CPLCSignalListener::SetStopCallback(Callback cb)
{
    m_cbStop = std::move(cb);
    m_cbStop = std::move(cb);
}
void CPLCSignalListener::SetAnalyzeCallback(AnalyzeCallback cb)
{
    m_cbAnalyze = std::move(cb);
    m_cbAnalyze = std::move(cb);
}
void CPLCSignalListener::SetLogCallback(LogCallback cb)
{
    m_cbLog = std::move(cb);
    m_cbLog = std::move(cb);
}
bool CPLCSignalListener::Start()
{
    if (m_bRunning || !m_pPlc) {
        LOG_MSG(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
        return false;
    }
    if (m_bRunning || !m_pPlc) {
        LOG_MSG(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
        return false;
    }
    m_bRunning = true;
    m_thread = std::thread(&CPLCSignalListener::ThreadProc, this);
    m_bRunning = true;
    m_thread = std::thread(&CPLCSignalListener::ThreadProc, this);
    StartHeartbeatMonitor();
    return true;
    StartHeartbeatMonitor();
    return true;
}
void CPLCSignalListener::Stop()
{
    m_bRunning = false;
    if (m_thread.joinable()) {
        m_thread.join();
    }
    m_bRunning = false;
    if (m_thread.joinable()) {
        m_thread.join();
    }
    StopHeartbeatMonitor();
    StopHeartbeatMonitor();
}
void CPLCSignalListener::LogInfo(const CString& strText, int nType)
{
    if (m_cbLog) {
        m_cbLog(strText, nType);
    }
    if (m_cbLog) {
        m_cbLog(strText, nType);
    }
}
bool CPLCSignalListener::SendHeartbeat()
{
    if (!m_pPlc || !m_bConnected) {
        return false;
    }
    if (!m_pPlc || !m_bConnected) {
        return false;
    }
    static bool bToggle = false;
    bToggle = !bToggle;
    static bool bToggle = false;
    bToggle = !bToggle;
    int ret = m_pPlc->WriteBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_HEARTBEAT_PC_TO_PLC_ADDR, BitContainer{ bToggle });
    int ret = m_pPlc->WriteBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_HEARTBEAT_PC_TO_PLC_ADDR, BitContainer{ bToggle });
    return (ret == 0);
    return (ret == 0);
}
bool CPLCSignalListener::CheckHeartbeat()
{
    static bool bLastHeartbeat = false;
    static bool bLastHeartbeat = false;
    if (!m_pPlc || !m_bConnected) {
        return false;
    }
    if (!m_pPlc || !m_bConnected) {
        return false;
    }
    BitContainer vec;
    int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_HEARTBEAT_PLC_TO_PC_ADDR, 1, vec);
    if (ret != 0 || vec.empty()) {
        return false;
    }
    BitContainer vec;
    int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_HEARTBEAT_PLC_TO_PC_ADDR, 1, vec);
    if (ret != 0 || vec.empty()) {
        return false;
    }
    bool bCurrent = vec[0];
    bool bChanged = (bCurrent != bLastHeartbeat);
    bLastHeartbeat = bCurrent;
    bool bCurrent = vec[0];
    bool bChanged = (bCurrent != bLastHeartbeat);
    bLastHeartbeat = bCurrent;
    return bChanged;
    return bChanged;
}
bool CPLCSignalListener::MonitorHeartbeat()
{
    if (CheckHeartbeat()) {
        m_nMissedHeartbeatCount = 0;
    if (CheckHeartbeat()) {
        m_nMissedHeartbeatCount = 0;
        if (m_bHeartbeatLost) {
            m_bHeartbeatLost = false;
            LOG_MSG(_T("PLC心跳恢复!"), LOG_TYPE_SUCCESS);
        }
        if (m_bHeartbeatLost) {
            m_bHeartbeatLost = false;
            LOG_MSG(_T("PLC心跳恢复!"), LOG_TYPE_SUCCESS);
        }
        return true;
    }
    else {
        m_nMissedHeartbeatCount++;
        return true;
    }
    else {
        m_nMissedHeartbeatCount++;
        if (m_nMissedHeartbeatCount > MAX_MISSED_HEARTBEAT) {
            if (!m_bHeartbeatLost) {
                m_bHeartbeatLost = true;
        if (m_nMissedHeartbeatCount > MAX_MISSED_HEARTBEAT) {
            if (!m_bHeartbeatLost) {
                m_bHeartbeatLost = true;
                m_nMissedHeartbeatCount = 0;
                LOG_MSG(_T("PLC心跳信号中断!"), LOG_TYPE_ERROR);
            }
            return false;
        }
    }
                LOG_MSG(_T("PLC心跳信号中断!"), LOG_TYPE_ERROR);
            }
            return false;
        }
    }
    return true;
    return true;
}
void CPLCSignalListener::StartHeartbeatMonitor()
{
    m_bHeartbeatRunning = true;
    m_heartbeatThread = std::thread([this]() {
        while (m_bHeartbeatRunning) {
            SendHeartbeat();
            MonitorHeartbeat();
            std::this_thread::sleep_for(std::chrono::milliseconds(m_nIntervalMs * 5));
        }
    });
    m_bHeartbeatRunning = true;
    m_heartbeatThread = std::thread([this]() {
        while (m_bHeartbeatRunning) {
            SendHeartbeat();
            MonitorHeartbeat();
            std::this_thread::sleep_for(std::chrono::milliseconds(m_nIntervalMs * 5));
        }
    });
}
void CPLCSignalListener::StopHeartbeatMonitor()
{
    m_bHeartbeatRunning = false;
    if (m_heartbeatThread.joinable()) {
        m_heartbeatThread.join();
    }
    m_bHeartbeatRunning = false;
    if (m_heartbeatThread.joinable()) {
        m_heartbeatThread.join();
    }
}
void CPLCSignalListener::PulseBitDevice(DeviceType eDevType, long nBitNo, int nDelayMs/* = 50*/)
{
    m_pPlc->SetBitDeviceEx(m_station, eDevType, nBitNo);
    ::Sleep(nDelayMs);
    m_pPlc->ResetBitDeviceEx(m_station, eDevType, nBitNo);
    m_pPlc->SetBitDeviceEx(m_station, eDevType, nBitNo);
    ::Sleep(nDelayMs);
    m_pPlc->ResetBitDeviceEx(m_station, eDevType, nBitNo);
}
void CPLCSignalListener::HandleAckLife(int i, bool bCurrTriggerBit)
{
    if (m_vecAckSent[i] && !bCurrTriggerBit) {
        m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
        m_vecAckSent[i] = false;
    }
    if (m_vecAckSent[i] && !bCurrTriggerBit) {
        m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
        m_vecAckSent[i] = false;
    }
    if (m_vecAckSent[i]) {
        if (++m_vecAckCounter[i] > PLC_ACK_MAX_LIFE) {
            m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
            m_vecAckSent[i] = false;
        }
    }
    if (m_vecAckSent[i]) {
        if (++m_vecAckCounter[i] > PLC_ACK_MAX_LIFE) {
            m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
            m_vecAckSent[i] = false;
        }
    }
}
void CPLCSignalListener::ThreadProc()
{
    while (m_bRunning && m_bConnected) {
        BitContainer vecBits;
        int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_CMD_BIT_START, PLC_CMD_BIT_COUNT, vecBits);
        if (ret != 0 && vecBits.size() != PLC_CMD_BIT_COUNT) {
            ::Sleep(m_nIntervalMs);
    while (m_bRunning && m_bConnected) {
        BitContainer vecBits;
        int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_CMD_BIT_START, PLC_CMD_BIT_COUNT, vecBits);
        if (ret != 0 && vecBits.size() != PLC_CMD_BIT_COUNT) {
            ::Sleep(m_nIntervalMs);
            CString strError;
            strError.Format(_T("PLC读取位数据失败,错误码:%d"), ret);
            LOG_MSG(strError, LOG_TYPE_ERROR);
            CString strError;
            strError.Format(_T("PLC读取位数据失败,错误码:%d"), ret);
            LOG_MSG(strError, LOG_TYPE_ERROR);
            continue;
        }
            continue;
        }
        for (int i = 0; i < PLC_CMD_BIT_COUNT; ++i) {
            if (IS_RISING_EDGE(m_vecPrevBits[i], vecBits[i])) {
                // ä¸Šå‡æ²¿è§¦å‘
                switch (i) {
                case 0:
                    if (m_cbStart) {
                        m_cbStart();
                        WriteOutValues(OutValuesArray{ 0.0, 0.0, 0.0, 0.0 });
                        if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                            m_vecAckSent[i] = true;
                            m_vecAckCounter[i] = 0;
                        }
                    }
                    break;
        for (int i = 0; i < PLC_CMD_BIT_COUNT; ++i) {
            if (IS_RISING_EDGE(m_vecPrevBits[i], vecBits[i])) {
                // ä¸Šå‡æ²¿è§¦å‘
                switch (i) {
                case 0:
                    if (m_cbStart) {
                        m_cbStart();
                        WriteOutValues(OutValuesArray{ 0.0, 0.0, 0.0, 0.0 });
                        if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                            m_vecAckSent[i] = true;
                            m_vecAckCounter[i] = 0;
                        }
                case 1:
                    if (m_cbStop) {
                        m_cbStop();
                        if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                            m_vecAckSent[i] = true;
                            m_vecAckCounter[i] = 0;
                        }
                    }
                        std::string strProductID;
                        if (ReadProductID(strProductID)) {
                            CString msg;
                            msg.Format(_T("读取到产品ID:%s"), strProductID);
                            LOG_MSG(msg, LOG_TYPE_SUCCESS);
                        }
                    }
                    break;
                    if (m_cbAnalyze) {
                        auto results = m_cbAnalyze();
                        WriteOutValues(results);
                    }
                    break;
                }
            }
                case 1:
                    if (m_cbStop) {
                        m_cbStop();
                        if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                            m_vecAckSent[i] = true;
                            m_vecAckCounter[i] = 0;
                        }
                    }
            HandleAckLife(i, vecBits[i]);
            m_vecPrevBits[i] = vecBits[i];
        }
                    if (m_cbAnalyze) {
                        auto results = m_cbAnalyze();
                        WriteOutValues(results);
                    }
                    break;
                }
            }
        ::Sleep(m_nIntervalMs);
    }
            HandleAckLife(i, vecBits[i]);
            m_vecPrevBits[i] = vecBits[i];
        }
        ::Sleep(m_nIntervalMs);
    }
}
bool CPLCSignalListener::WriteOutValues(const OutValuesArray& values)
{
    if (!m_pPlc || !m_bConnected) {
        LOG_MSG(_T("PLC未连接或未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
        return false;
    }
    if (!m_pPlc || !m_bConnected) {
        LOG_MSG(_T("PLC未连接或未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
        return false;
    }
    if (PLC_RESULT_ADDR_COUNT != 4) {
        LOG_MSG(_T("PLC结果寄存器数量配置错误,必须为4个。"), LOG_TYPE_ERROR);
        return false;
    }
    if (PLC_RESULT_ADDR_COUNT != 4) {
        LOG_MSG(_T("PLC结果寄存器数量配置错误,必须为4个。"), LOG_TYPE_ERROR);
        return false;
    }
    for (int i = 0; i < PLC_RESULT_ADDR_COUNT; ++i) {
        // æ”¾å¤§100倍并四舍五入,转为PLC整数
        uint16_t nScaled = static_cast<uint16_t>(std::round(values[i] * 100.0));
        WordContainer vec = { nScaled };
    for (int i = 0; i < PLC_RESULT_ADDR_COUNT; ++i) {
        // æ”¾å¤§1000倍并四舍五入,转为PLC整数
        int32_t  nScaled = static_cast<int32_t>(std::round(values[i] * 1000.0));
        DWordContainer vec = { static_cast<uint32_t>(nScaled) };
        short nTargetAddr = PLC_RESULT_ADDR_START + i * 2;
        int ret = m_pPlc->WriteWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, nTargetAddr, vec);
        if (ret != 0) {
            CString msg;
            msg.Format(_T("写入OUT%d到地址%d失败,值=%.2f"), i + 1, nTargetAddr, values[i]);
            LOG_MSG(msg, LOG_TYPE_ERROR);
            return false;
        }
    }
        short nTargetAddr = PLC_RESULT_ADDR_START + i * 2;
        int ret = m_pPlc->WriteDWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, nTargetAddr, vec);
        if (ret != 0) {
            CString msg;
            msg.Format(_T("写入OUT%d到地址%d失败,值=%.2f"), i + 1, nTargetAddr, values[i]);
            LOG_MSG(msg, LOG_TYPE_ERROR);
            return false;
        }
    }
    return true;
    return true;
}
bool CPLCSignalListener::ReadProductID(std::string& strProductID)
{
    if (!m_pPlc || !m_bConnected) {
        LOG_MSG(_T("PLC未连接或未初始化,无法读取产品ID。"), LOG_TYPE_ERROR);
        return false;
    }
    WordContainer vec;
    int ret = m_pPlc->ReadWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, PLC_PRODUCT_ID_ADDR, PLC_PRODUCT_ID_WORDS, vec);
    if (ret != 0 || vec.size() != PLC_PRODUCT_ID_WORDS) {
        CString msg;
        msg.Format(_T("读取产品ID失败,错误码=%d"), ret);
        LOG_MSG(msg, LOG_TYPE_ERROR);
        return false;
    }
    strProductID.clear();
    strProductID.reserve(PLC_PRODUCT_ID_WORDS * 2);
    for (auto w : vec) {
        char c1 = static_cast<char>(w & 0xFF);          // ä½Žå­—节
        char c2 = static_cast<char>((w >> 8) & 0xFF);   // é«˜å­—节
        if (c1 == '\0') {
            break;
        }
        strProductID.push_back(c1);
        if (c2 == '\0') {
            break;
        }
        strProductID.push_back(c2);
    }
    return true;
}
SourceCode/Bond/SGMeasurement/PLCSignalListener.h
@@ -15,164 +15,173 @@
class CPLCSignalListener
{
public:
    CPLCSignalListener();
    ~CPLCSignalListener();
    CPLCSignalListener();
    ~CPLCSignalListener();
    /**
     * @brief åˆå§‹åŒ– PLC ä¿¡å·ç›‘听器。
     *
     * @param station      ç›®æ ‡ PLC çš„站号标识符。
     * @param nIntervalMs  è½®è¯¢å‘¨æœŸï¼ˆå•位:毫秒),默认 200ms。
     * @return true        åˆå§‹åŒ–成功。
     * @return false       åˆå§‹åŒ–失败。
     */
    bool Initialize(StationIdentifier station, int nIntervalMs = 200);
    /**
     * @brief åˆå§‹åŒ– PLC ä¿¡å·ç›‘听器。
     *
     * @param station      ç›®æ ‡ PLC çš„站号标识符。
     * @param nIntervalMs  è½®è¯¢å‘¨æœŸï¼ˆå•位:毫秒),默认 200ms。
     * @return true        åˆå§‹åŒ–成功。
     * @return false       åˆå§‹åŒ–失败。
     */
    bool Initialize(StationIdentifier station, int nIntervalMs = 200);
    /**
     * @brief è®¾ç½®å¼€å§‹å‘½ä»¤çš„回调函数(对应 PLC çš„ Start ä¿¡å·ï¼‰ã€‚
     *
     * @param cb  ç”¨æˆ·è‡ªå®šä¹‰çš„开始回调函数。
     */
    void SetStartCallback(Callback cb);
    /**
     * @brief è®¾ç½®å¼€å§‹å‘½ä»¤çš„回调函数(对应 PLC çš„ Start ä¿¡å·ï¼‰ã€‚
     *
     * @param cb  ç”¨æˆ·è‡ªå®šä¹‰çš„开始回调函数。
     */
    void SetStartCallback(Callback cb);
    /**
     * @brief è®¾ç½®åœæ­¢å‘½ä»¤çš„回调函数(对应 PLC çš„ Stop ä¿¡å·ï¼‰ã€‚
     *
     * @param cb  ç”¨æˆ·è‡ªå®šä¹‰çš„停止回调函数。
     */
    void SetStopCallback(Callback cb);
    /**
     * @brief è®¾ç½®åœæ­¢å‘½ä»¤çš„回调函数(对应 PLC çš„ Stop ä¿¡å·ï¼‰ã€‚
     *
     * @param cb  ç”¨æˆ·è‡ªå®šä¹‰çš„停止回调函数。
     */
    void SetStopCallback(Callback cb);
    /**
     * @brief è®¾ç½®åˆ†æžè®¡ç®—回调函数,在接收到停止命令后调用。
     *
     * @param cb  è¿”回计算结果数组(OUT1~OUT4)的函数。
     */
    void SetAnalyzeCallback(AnalyzeCallback cb);
    /**
     * @brief è®¾ç½®åˆ†æžè®¡ç®—回调函数,在接收到停止命令后调用。
     *
     * @param cb  è¿”回计算结果数组(OUT1~OUT4)的函数。
     */
    void SetAnalyzeCallback(AnalyzeCallback cb);
    /**
     * @brief è®¾ç½®æ—¥å¿—输出回调函数。
     *
     * @param cb  ç”¨æˆ·æä¾›çš„æ—¥å¿—输出接口(包含日志内容和日志类型)。
     */
    void SetLogCallback(LogCallback cb);
    /**
     * @brief è®¾ç½®æ—¥å¿—输出回调函数。
     *
     * @param cb  ç”¨æˆ·æä¾›çš„æ—¥å¿—输出接口(包含日志内容和日志类型)。
     */
    void SetLogCallback(LogCallback cb);
    /**
     * @brief å¯åŠ¨ä¿¡å·ç›‘å¬çº¿ç¨‹ã€‚
     *
     * @return true   å¯åŠ¨æˆåŠŸã€‚
     * @return false  å¯åŠ¨å¤±è´¥ï¼ˆå¯èƒ½å·²è¿è¡Œæˆ–æœªåˆå§‹åŒ–ï¼‰ã€‚
     */
    bool Start();
    /**
     * @brief å¯åŠ¨ä¿¡å·ç›‘å¬çº¿ç¨‹ã€‚
     *
     * @return true   å¯åŠ¨æˆåŠŸã€‚
     * @return false  å¯åŠ¨å¤±è´¥ï¼ˆå¯èƒ½å·²è¿è¡Œæˆ–æœªåˆå§‹åŒ–ï¼‰ã€‚
     */
    bool Start();
    /**
     * @brief åœæ­¢ä¿¡å·ç›‘听线程。
     */
    void Stop();
    /**
     * @brief åœæ­¢ä¿¡å·ç›‘听线程。
     */
    void Stop();
    /**
     * @brief å‘ PLC å†™å…¥åˆ†æžç»“果值(通常为 OUT1 ~ OUT4)。
     *
     * @param values  åŒ…含四个 double ç±»åž‹çš„结果值,将转换为整数后写入 PLC。
     * @return true   å†™å…¥æˆåŠŸã€‚
     * @return false  å†™å…¥å¤±è´¥ã€‚
     */
    bool WriteOutValues(const OutValuesArray& values);
    /**
     * @brief å‘ PLC å†™å…¥åˆ†æžç»“果值(通常为 OUT1 ~ OUT4)。
     *
     * @param values  åŒ…含四个 double ç±»åž‹çš„结果值,将转换为整数后写入 PLC。
     * @return true   å†™å…¥æˆåŠŸã€‚
     * @return false  å†™å…¥å¤±è´¥ã€‚
     */
    bool WriteOutValues(const OutValuesArray& values);
    /**
     * @brief è¯»å– PLC å†…部的产品 ID(字符串形式)。
     *
     * @param strProductID  è¾“出参数,存储读取到的产品 ID。
     * @return true         è¯»å–成功。
     * @return false        è¯»å–失败。
     */
    bool ReadProductID(std::string& strProductID);
private:
    /**
     * @brief è¾“出日志信息(封装日志回调)。
     *
     * @param strText æ—¥å¿—内容文本。
     * @param nType   æ—¥å¿—类型(LOG_TYPE_NORMAL / ERROR / WARNING ç­‰ï¼‰ã€‚
     */
    void LogInfo(const CString& strText, int nType);
    /**
     * @brief è¾“出日志信息(封装日志回调)。
     *
     * @param strText æ—¥å¿—内容文本。
     * @param nType   æ—¥å¿—类型(LOG_TYPE_NORMAL / ERROR / WARNING ç­‰ï¼‰ã€‚
     */
    void LogInfo(const CString& strText, int nType);
    /**
     * @brief å‘ PLC å†™å…¥å¿ƒè·³ä¿¡å·ï¼ˆäº¤æ›¿ä½ï¼‰ã€‚
     *
     * @return true  å†™å…¥æˆåŠŸã€‚
     * @return false å†™å…¥å¤±è´¥ï¼ˆå¦‚未连接)。
     */
    bool SendHeartbeat();
    /**
     * @brief å‘ PLC å†™å…¥å¿ƒè·³ä¿¡å·ï¼ˆäº¤æ›¿ä½ï¼‰ã€‚
     *
     * @return true  å†™å…¥æˆåŠŸã€‚
     * @return false å†™å…¥å¤±è´¥ï¼ˆå¦‚未连接)。
     */
    bool SendHeartbeat();
    /**
     * @brief æ£€æŸ¥ä»Ž PLC è¯»å–到的心跳信号是否发生变化。
     *
     * @return true  å¿ƒè·³æœ‰å˜åŒ–(PLC æ­£å¸¸åœ¨çº¿ï¼‰ã€‚
     * @return false å¿ƒè·³æ— å˜åŒ–(可能离线)。
     */
    bool CheckHeartbeat();
    /**
     * @brief æ£€æŸ¥ä»Ž PLC è¯»å–到的心跳信号是否发生变化。
     *
     * @return true  å¿ƒè·³æœ‰å˜åŒ–(PLC æ­£å¸¸åœ¨çº¿ï¼‰ã€‚
     * @return false å¿ƒè·³æ— å˜åŒ–(可能离线)。
     */
    bool CheckHeartbeat();
    /**
     * @brief ç›‘控 PLC å¿ƒè·³æ˜¯å¦ä¸­æ–­ï¼Œå¹¶è‡ªåŠ¨è®°å½•çŠ¶æ€ã€‚
     *
     * @return true  PLC åœ¨çº¿æˆ–在允许的未响应次数内。
     * @return false PLC å¿ƒè·³ä¸­æ–­ï¼Œè¶…过允许阈值。
     */
    bool MonitorHeartbeat();
    /**
     * @brief ç›‘控 PLC å¿ƒè·³æ˜¯å¦ä¸­æ–­ï¼Œå¹¶è‡ªåŠ¨è®°å½•çŠ¶æ€ã€‚
     *
     * @return true  PLC åœ¨çº¿æˆ–在允许的未响应次数内。
     * @return false PLC å¿ƒè·³ä¸­æ–­ï¼Œè¶…过允许阈值。
     */
    bool MonitorHeartbeat();
    /**
     * @brief å¯åŠ¨å¿ƒè·³ç›‘æŽ§çº¿ç¨‹ï¼ˆç‹¬ç«‹äºŽä¿¡å·ç›‘å¬çº¿ç¨‹ï¼‰ã€‚
     */
    void StartHeartbeatMonitor();
    /**
     * @brief å¯åŠ¨å¿ƒè·³ç›‘æŽ§çº¿ç¨‹ï¼ˆç‹¬ç«‹äºŽä¿¡å·ç›‘å¬çº¿ç¨‹ï¼‰ã€‚
     */
    void StartHeartbeatMonitor();
    /**
     * @brief åœæ­¢å¿ƒè·³ç›‘控线程。
     */
    void StopHeartbeatMonitor();
    /**
     * @brief åœæ­¢å¿ƒè·³ç›‘控线程。
     */
    void StopHeartbeatMonitor();
    /**
     * @brief å‘指定软元件位写入一个脉冲(先写1,延时后自动写0)。
     *
     * @param eDevType ä½è½¯å…ƒä»¶ç±»åž‹ï¼ˆå¦‚ M/B)。
     * @param nBitNo   ä½åœ°å€ç¼–号。
     * @param nDelayMs è„‰å†²æŒç»­æ—¶é—´ï¼Œé»˜è®¤50毫秒。
     */
    void PulseBitDevice(DeviceType eDevType, long nBitNo, int nDelayMs = 50);
    /**
     * @brief å‘指定软元件位写入一个脉冲(先写1,延时后自动写0)。
     *
     * @param eDevType ä½è½¯å…ƒä»¶ç±»åž‹ï¼ˆå¦‚ M/B)。
     * @param nBitNo   ä½åœ°å€ç¼–号。
     * @param nDelayMs è„‰å†²æŒç»­æ—¶é—´ï¼Œé»˜è®¤50毫秒。
     */
    void PulseBitDevice(DeviceType eDevType, long nBitNo, int nDelayMs = 50);
    /**
     * @brief ç®¡ç† ACK å“åº”的生命周期,超时自动清除。
     *
     * @param i                æŽ§åˆ¶ä½ç´¢å¼•(0=Start,1=Stop)。
     * @param bCurrTriggerBit  å½“前从 PLC è¯»å–到的触发位状态。
     */
    void HandleAckLife(int i, bool bCurrTriggerBit);
    /**
     * @brief ç®¡ç† ACK å“åº”的生命周期,超时自动清除。
     *
     * @param i                æŽ§åˆ¶ä½ç´¢å¼•(0=Start,1=Stop)。
     * @param bCurrTriggerBit  å½“前从 PLC è¯»å–到的触发位状态。
     */
    void HandleAckLife(int i, bool bCurrTriggerBit);
    /**
     * @brief ä¸»ç›‘听线程逻辑,循环读取 PLC æŽ§åˆ¶ä¿¡å·å¹¶å¤„理触发。
     */
    void ThreadProc();
    /**
     * @brief ä¸»ç›‘听线程逻辑,循环读取 PLC æŽ§åˆ¶ä¿¡å·å¹¶å¤„理触发。
     */
    void ThreadProc();
private:
    // === PLC é€šä¿¡æ ¸å¿ƒå¯¹è±¡ ===
    std::unique_ptr<CCCLinkIEControl> m_pPlc; // PLC é€šä¿¡æŽ§åˆ¶å™¨
    StationIdentifier m_station;              // PLC ç«™å·
    std::atomic<bool> m_bConnected{ false };  // æ˜¯å¦æˆåŠŸè¿žæŽ¥
    // === PLC é€šä¿¡æ ¸å¿ƒå¯¹è±¡ ===
    std::unique_ptr<CCCLinkIEControl> m_pPlc; // PLC é€šä¿¡æŽ§åˆ¶å™¨
    StationIdentifier m_station;              // PLC ç«™å·
    std::atomic<bool> m_bConnected{ false };  // æ˜¯å¦æˆåŠŸè¿žæŽ¥
    // === æŽ§åˆ¶å‚æ•° ===
    int m_nIntervalMs = 200;                  // è½®è¯¢å‘¨æœŸï¼ˆms)
    // === æŽ§åˆ¶å‚æ•° ===
    int m_nIntervalMs = 200;                  // è½®è¯¢å‘¨æœŸï¼ˆms)
    // === å‘½ä»¤è§¦å‘状态缓存 ===
    std::vector<bool> m_vecPrevBits;          // ä¸Šä¸€å‘¨æœŸçš„命令位状态(用于检测上升沿)
    // === å‘½ä»¤è§¦å‘状态缓存 ===
    std::vector<bool> m_vecPrevBits;          // ä¸Šä¸€å‘¨æœŸçš„命令位状态(用于检测上升沿)
    // === å›žè°ƒå‡½æ•°ï¼ˆæŽ§åˆ¶/计算/日志)===
    Callback m_cbStart;                       // Start å‘½ä»¤å›žè°ƒ
    Callback m_cbStop;                        // Stop å‘½ä»¤å›žè°ƒ
    AnalyzeCallback m_cbAnalyze;              // Analyze è®¡ç®—回调(返回 OUT1~OUT4)
    LogCallback m_cbLog;                      // æ—¥å¿—输出回调
    // === å›žè°ƒå‡½æ•°ï¼ˆæŽ§åˆ¶/计算/日志)===
    Callback m_cbStart;                       // Start å‘½ä»¤å›žè°ƒ
    Callback m_cbStop;                        // Stop å‘½ä»¤å›žè°ƒ
    AnalyzeCallback m_cbAnalyze;              // Analyze è®¡ç®—回调(返回 OUT1~OUT4)
    LogCallback m_cbLog;                      // æ—¥å¿—输出回调
    // === ä¸»çº¿ç¨‹æŽ§åˆ¶ ===
    std::atomic<bool> m_bRunning{ false };    // ä¸»ç›‘听线程是否运行
    std::thread m_thread;                     // ä¸»ç›‘听线程对象
    // === ä¸»çº¿ç¨‹æŽ§åˆ¶ ===
    std::atomic<bool> m_bRunning{ false };    // ä¸»ç›‘听线程是否运行
    std::thread m_thread;                     // ä¸»ç›‘听线程对象
    // === ACK ä¿¡å·çŠ¶æ€ ===
    std::array<bool, 2> m_vecAckSent = { false, false }; // æ˜¯å¦å·²å‘送应答信号(B10/B11)
    std::array<int, 2>  m_vecAckCounter = { 0, 0 };      // å¯¹åº”应答信号的生命周期计数器
    // === ACK ä¿¡å·çŠ¶æ€ ===
    std::array<bool, 2> m_vecAckSent = { false, false }; // æ˜¯å¦å·²å‘送应答信号(B10/B11)
    std::array<int, 2>  m_vecAckCounter = { 0, 0 };      // å¯¹åº”应答信号的生命周期计数器
    // === å¿ƒè·³æ£€æµ‹ç›¸å…³ ===
    std::thread m_heartbeatThread;                  // å¿ƒè·³æ£€æµ‹çº¿ç¨‹å¯¹è±¡
    std::atomic<bool> m_bHeartbeatRunning = false;  // å¿ƒè·³çº¿ç¨‹è¿è¡Œæ ‡å¿—
    std::atomic<bool> m_bHeartbeatLost = false;     // å¿ƒè·³ä¸¢å¤±æ ‡å¿—
    int m_nMissedHeartbeatCount = 0;                // å¿ƒè·³æœªå˜åŒ–次数(用于检测 PLC æŽ‰çº¿ï¼‰
    // === å¿ƒè·³æ£€æµ‹ç›¸å…³ ===
    std::thread m_heartbeatThread;                  // å¿ƒè·³æ£€æµ‹çº¿ç¨‹å¯¹è±¡
    std::atomic<bool> m_bHeartbeatRunning = false;  // å¿ƒè·³çº¿ç¨‹è¿è¡Œæ ‡å¿—
    std::atomic<bool> m_bHeartbeatLost = false;     // å¿ƒè·³ä¸¢å¤±æ ‡å¿—
    int m_nMissedHeartbeatCount = 0;                // å¿ƒè·³æœªå˜åŒ–次数(用于检测 PLC æŽ‰çº¿ï¼‰
};
SourceCode/Bond/SGMeasurement/SGMeasurement.cpp
@@ -1,4 +1,4 @@

// SGMeasurement.cpp: å®šä¹‰åº”用程序的类行为。
//
SourceCode/Bond/SGMeasurement/SGMeasurement.h
@@ -1,4 +1,4 @@

// SGMeasurement.h: PROJECT_NAME åº”用程序的主头文件
//
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj
@@ -175,7 +175,7 @@
      <PreprocessorDefinitions>_WINDOWS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
      <AdditionalIncludeDirectories>.;..;.\DLL\64bit;.\CCLinkPerformance;..\MELSECSDK\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <Optimization>MaxSpeed</Optimization>
      <Optimization>Disabled</Optimization>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj.user
@@ -4,7 +4,7 @@
    <RESOURCE_FILE>SGMeasurement.rc</RESOURCE_FILE>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <DebuggerFlavor>WindowsRemoteDebugger</DebuggerFlavor>
    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <RemoteDebuggerCommand>\\DESKTOP-IODBVIQ\SGMeasurement\$(ProjectName).exe</RemoteDebuggerCommand>
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
@@ -1,4 +1,4 @@

// SGMeasurementDlg.cpp: å®žçŽ°æ–‡ä»¶
//
@@ -247,7 +247,7 @@
    strFullLogLine.Format(_T("%s %s"), strLevel, strContent);
    // å†™å…¥æ—¥å¿—文件
    // LOG_LINE(strFullLogLine);
    LOG_LINE(strFullLogLine);
}
void CSGMeasurementDlg::HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight/* = RGB(255, 165, 0)*/)
@@ -639,7 +639,8 @@
    fAvg1 = CalcAverage(vecGlass1);
    fAvg2 = CalcAverage(vecGlass2);
    fOffset = std::fabs(fAvg2 - fAvg1);  // ç¬¬äºŒç‰‡ - ç¬¬ä¸€ç‰‡
    fOffset = fAvg2 - fAvg1;                // ç¬¬äºŒç‰‡ - ç¬¬ä¸€ç‰‡
    //fOffset = std::fabs(fAvg2 - fAvg1);    // ç¬¬äºŒç‰‡ - ç¬¬ä¸€ç‰‡
    CString strLog;
    strLog.Format(_T("第一片玻璃平均值: %.3f,第二片玻璃平均值: %.3f,偏移量: %.3f"), fAvg1, fAvg2, fOffset);
@@ -723,27 +724,27 @@
    if (m_nUseTrigger) {
        UpdateControlStatus(m_bConnected, m_bSaving);
        AfxMessageBox(_T("当前是硬触发模式,请检查触发器状态。"), MB_ICONINFORMATION);
        return -1.0f;
        return 0xFF;
    }
    if (!m_bConnected) {
        AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
        return -1.0f;
        return 0xFF;
    }
    if (m_bSaving) {
        AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING);
        return -1.0f;
        return 0xFF;
    }
    if (nOutNo < 1 || nOutNo > 4) {
        AppendLogLineRichStyled(_T("输出端口编号无效,必须在 1 åˆ° 4 ä¹‹é—´ã€‚"), LOG_COLOR_ERROR);
        return -1.0f;
        return 0xFF;
    }
    if (m_nSavePointCount < 0) {
        AppendLogLineRichStyled(_T("数据点数必须大于 0。"), LOG_COLOR_ERROR);
        return -1.0f;
        return 0xFF;
    }
    std::vector<float> vecBuffer(m_nSavePointCount, 0.0f);
@@ -754,7 +755,7 @@
        CString strError;
        strError.Format(_T("读取 OUT%d æ•°æ®å¤±è´¥ï¼Œé”™è¯¯ç ï¼š%#X"), nOutNo, nRet);
        AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
        return -1.0f;
        return 0xFF;
    }
    vecBuffer.resize(nReceived);
@@ -763,7 +764,7 @@
    std::vector<float> vecGlass1, vecGlass2;
    if (!SplitGlassSegments(nOutNo, vecBuffer, vecGlass1, vecGlass2, m_fJumpThreshold, m_nJumpWindow, m_nValleyMargin, m_nMinGlass1Count)) {
        AppendLogLineRichStyled(_T("未能识别出两片玻璃的数据。"), LOG_COLOR_WARNING);
        return -1.0f;
        return 0xFF;
    }
    std::vector<float> vecGlass1Filtered, vecGlass2Filtered;
@@ -923,17 +924,12 @@
    m_plcListener.SetAnalyzeCallback([this]() {
        if (!m_bConnected) {
            AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
            return std::array<double, 4>{ -1.0, -1.0, -1.0, -1.0 };
            return std::array<double, 4>{ 0xFF, 0xFF, 0xFF, 0xFF };
        }
        std::array<double, 4> result;
        for (int i = 0; i < 4; ++i) {
            result[i] = AnalyzeStoredData(i + 1); // OUT1 ~ OUT4
        }
        if (std::any_of(result.begin(), result.end(), [](double v) { return v < 0; })) {
            AppendLogLineRichStyled(_T("分析失败,某些输出端口数据无效。"), LOG_COLOR_ERROR);
            return std::array<double, 4>{ -1.0, -1.0, -1.0, -1.0 };
        }
        CString strLog;
@@ -998,11 +994,21 @@
        RC nRet = SGIF_GetCalcDataALL(DeviceID, value);
        if (nRet == RC_OK) {
            for (int i = 0; i < 4; ++i) {
                m_dOutValues[i] = value[i].Value;
            }
                double dNew = value[i].Value;
                if (fabs(m_dOutValues[i] - dNew) > 1e-6) {
                    m_dOutValues[i] = dNew;
            // æ›´æ–°ç»‘定控件
            UpdateData(FALSE);
                    CString str;
                    str.Format(_T("%.3f"), dNew);
                    switch (i) {
                    case 0: GetDlgItem(IDC_EDIT_OUT1)->SetWindowText(str); break;
                    case 1: GetDlgItem(IDC_EDIT_OUT2)->SetWindowText(str); break;
                    case 2: GetDlgItem(IDC_EDIT_OUT3)->SetWindowText(str); break;
                    case 3: GetDlgItem(IDC_EDIT_OUT4)->SetWindowText(str); break;
                    }
                }
            }
        }
        else {
            CString strError;
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h
@@ -1,4 +1,4 @@

// SGMeasurementDlg.h: å¤´æ–‡ä»¶
//
SourceCode/Bond/SGMeasurement/framework.h
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN            // ä»Ž Windows å¤´ä¸­æŽ’除极少使用的资料
SourceCode/Bond/SGMeasurement/pch.cpp
@@ -1,4 +1,4 @@
// pch.cpp: ä¸Žé¢„编译标头对应的源文件
// pch.cpp: ä¸Žé¢„编译标头对应的源文件
#include "pch.h"
SourceCode/Bond/SGMeasurement/pch.h
@@ -1,4 +1,4 @@
// pch.h: è¿™æ˜¯é¢„编译标头文件。
// pch.h: è¿™æ˜¯é¢„编译标头文件。
// ä¸‹æ–¹åˆ—出的文件仅编译一次,提高了将来生成的生成性能。
// è¿™è¿˜å°†å½±å“ IntelliSense æ€§èƒ½ï¼ŒåŒ…括代码完成和许多代码浏览功能。
// ä½†æ˜¯ï¼Œå¦‚果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
SourceCode/Bond/SGMeasurement/resource.h
@@ -1,4 +1,4 @@
//{{NO_DEPENDENCIES}}
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ ç”Ÿæˆçš„包含文件。
// ä¾› SGMeasurement.rc ä½¿ç”¨
//
SourceCode/Bond/SGMeasurement/targetver.h
@@ -1,4 +1,4 @@
#pragma once
#pragma once
// åŒ…括 SDKDDKVer.h å°†å®šä¹‰å¯ç”¨çš„æœ€é«˜ç‰ˆæœ¬çš„ Windows å¹³å°ã€‚
SourceCode/Bond/Servo/AlarmManager.cpp
@@ -1,4 +1,5 @@
#include "stdafx.h"
#include "Common.h"
#include "AlarmManager.h"
#include <sstream>
#include <fstream>
@@ -88,7 +89,53 @@
            FOREIGN KEY (unit_id) REFERENCES units(unit_id)
        )
    )";
    return m_pDB->executeQuery(createAlarmsTableQuery);
    if (!m_pDB->executeQuery(createAlarmsTableQuery)) {
        return false;
    }
    // è®¾å¤‡åˆ—表 (ID -> åç§°)
    std::vector<std::pair<int, std::string>> devices = {
        {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}
    };
    // æ’å…¥ devices å’Œå¯¹åº”的默认 unit
    for (const auto& dev : devices) {
        int nDeviceId = dev.first;
        const std::string& strDeviceName = dev.second;
        // æ’入设备
        std::ostringstream ossDev;
        ossDev << "INSERT OR IGNORE INTO devices (device_id, device_name) VALUES("
            << nDeviceId << ", '" << strDeviceName << "')";
        if (!m_pDB->executeQuery(ossDev.str())) {
            return false;
        }
        // æ’入默认单元 (unit_id = 0, unit_name = device_name)
        std::ostringstream ossUnit;
        ossUnit << "INSERT OR IGNORE INTO units (device_id, unit_id, unit_name) VALUES("
            << nDeviceId << ", 0, '" << strDeviceName << "')";
        if (!m_pDB->executeQuery(ossUnit.str())) {
            return false;
        }
    }
    return true;
}
// é”€æ¯æŠ¥è­¦è¡¨
@@ -487,17 +534,7 @@
}
// ç­›é€‰æŠ¥è­¦æ•°æ®
std::vector<AlarmData> AlarmManager::getFilteredAlarms(
    const std::string& id,
    const std::string& severityLevel,
    const std::string& deviceName,
    const std::string& unitName,
    const std::string& description,
    const std::string& startTime,
    const std::string& endTime,
    int pageNumber,
    int pageSize) {
std::vector<AlarmData> AlarmManager::getFilteredAlarms(const std::string& keyword, const std::string& startTime, const std::string& endTime, int pageNumber, int pageSize) {
    if (!m_pDB) {
        return {};
    }
@@ -507,25 +544,20 @@
        SELECT a.id, a.severity_level, a.device_id, a.unit_id, d.device_name, u.unit_name, a.description, a.start_time, a.end_time
        FROM alarms a
        JOIN devices d ON a.device_id = d.device_id
        JOIN units u ON a.device_id = u.device_id AND a.unit_id = u.unit_id
        JOIN units u   ON a.device_id = u.device_id AND a.unit_id = u.unit_id
        WHERE 1=1)";
    // ä¼ å…¥çš„过滤条件
    if (!id.empty()) {
        query << " AND a.id = '" << id << "'";
    // ç»Ÿä¸€å…³é”®å­—模糊查询
    if (!keyword.empty()) {
        query << " AND ("
            << "a.id LIKE '%" << keyword << "%' OR "
            << "a.severity_level LIKE '%" << keyword << "%' OR "
            << "d.device_name LIKE '%" << keyword << "%' OR "
            << "u.unit_name LIKE '%" << keyword << "%' OR "
            << "a.description LIKE '%" << keyword << "%')";
    }
    if (!severityLevel.empty()) {
        query << " AND a.severity_level = '" << severityLevel << "'";
    }
    if (!deviceName.empty()) {
        query << " AND d.device_name LIKE '%" << deviceName << "%'";
    }
    if (!unitName.empty()) {
        query << " AND u.unit_name LIKE '%" << unitName << "%'";
    }
    if (!description.empty()) {
        query << " AND a.description LIKE '%" << description << "%'";
    }
    // æ—¶é—´æ¡ä»¶
    if (!startTime.empty()) {
        query << " AND a.start_time >= '" << startTime << "'";
    }
@@ -534,9 +566,10 @@
    }
    // åˆ†é¡µè®¾ç½®
    int offset = (pageNumber - 1) * pageSize;
    query << " ORDER BY a.start_time DESC LIMIT " << pageSize << " OFFSET " << offset;
    int nOffset = (pageNumber - 1) * pageSize;
    query << " ORDER BY a.start_time DESC LIMIT " << pageSize << " OFFSET " << nOffset;
    // æŸ¥è¯¢
    auto results = m_pDB->fetchResults(query.str());
    // éåŽ†æŸ¥è¯¢ç»“æžœï¼Œå¡«å…… AlarmData ç»“构体
@@ -560,41 +593,30 @@
}
// èŽ·å–ç¬¦åˆæ¡ä»¶çš„æŠ¥è­¦æ€»æ•°
int AlarmManager::getTotalAlarmCount(
    const std::string& id,
    const std::string& severityLevel,
    const std::string& deviceName,
    const std::string& unitName,
    const std::string& description,
    const std::string& startTime,
    const std::string& endTime) {
int AlarmManager::getTotalAlarmCount(const std::string& keyword, const std::string& startTime, const std::string& endTime) {
    if (!m_pDB) {
        return 0;
    }
    std::ostringstream query;
    query << "SELECT COUNT(*) FROM alarms a "
        << "JOIN devices d ON a.device_id = d.device_id "
        << "JOIN units u ON a.device_id = u.device_id AND a.unit_id = u.unit_id "
        << "WHERE 1=1";
    query << R"(
        SELECT COUNT(*)
        FROM alarms a
        JOIN devices d ON a.device_id = d.device_id
        JOIN units u   ON a.device_id = u.device_id AND a.unit_id = u.unit_id
        WHERE 1=1)";
    // æ·»åŠ è¿‡æ»¤æ¡ä»¶
    if (!id.empty()) {
        query << " AND a.id = '" << id << "'";
    // ç»Ÿä¸€å…³é”®å­—模糊查询
    if (!keyword.empty()) {
        query << " AND ("
            << "a.id LIKE '%" << keyword << "%' OR "
            << "a.severity_level LIKE '%" << keyword << "%' OR "
            << "d.device_name LIKE '%" << keyword << "%' OR "
            << "u.unit_name LIKE '%" << keyword << "%' OR "
            << "a.description LIKE '%" << keyword << "%')";
    }
    if (!severityLevel.empty()) {
        query << " AND a.severity_level = '" << severityLevel << "'";
    }
    if (!deviceName.empty()) {
        query << " AND d.device_name LIKE '%" << deviceName << "%'";
    }
    if (!unitName.empty()) {
        query << " AND u.unit_name LIKE '%" << unitName << "%'";
    }
    if (!description.empty()) {
        query << " AND a.description LIKE '%" << description << "%'";
    }
    // æ—¶é—´æ¡ä»¶
    if (!startTime.empty()) {
        query << " AND a.start_time >= '" << startTime << "'";
    }
SourceCode/Bond/Servo/AlarmManager.h
@@ -115,49 +115,25 @@
     */
    std::vector<AlarmData> getAlarms(int startPosition, int count);
    /**
     * èŽ·å–ç­›é€‰åŽçš„æŠ¥è­¦æ•°æ®
     * @param id æŠ¥è­¦ID的筛选条件
     * @param severityLevel æŠ¥è­¦ç­‰çº§ç­›é€‰æ¡ä»¶
     * @param deviceName è®¾å¤‡åç§°çš„筛选条件
     * @param unitName å•元名称的筛选条件
     * @param description æŠ¥è­¦æè¿°çš„筛选条件
     * @param startTime èµ·å§‹æ—¶é—´ç­›é€‰æ¡ä»¶
     * @param endTime ç»“束时间筛选条件
     * @param pageNumber é¡µç 
     * @param pageSize æ¯é¡µçš„记录数
     * @return åŒ…含查询结果的报警数据
     */
    std::vector<AlarmData> getFilteredAlarms(
        const std::string& id,
        const std::string& severityLevel,
        const std::string& deviceName,
        const std::string& unitName,
        const std::string& description,
        const std::string& startTime,
        const std::string& endTime,
        int pageNumber,
        int pageSize);
    /**
     * ç­›é€‰æŠ¥è­¦æ•°æ®
     * @param keyword å…³é”®å­—筛选条件
     * @param startTime èµ·å§‹æ—¶é—´ç­›é€‰æ¡ä»¶
     * @param endTime ç»“束时间筛选条件
     * @param pageNumber é¡µç 
     * @param pageSize æ¯é¡µè®°å½•æ•°
     * @return åŒ…含筛选后报警数据的结构体
     */
    std::vector<AlarmData> getFilteredAlarms(const std::string& keyword, const std::string& startTime, const std::string& endTime, int pageNumber, int pageSize);
    /**
     * èŽ·å–ç¬¦åˆæ¡ä»¶çš„æŠ¥è­¦æ€»æ•°
     * @param id æŠ¥è­¦ID的筛选条件
     * @param severityLevel æŠ¥è­¦ç­‰çº§ç­›é€‰æ¡ä»¶
     * @param deviceName è®¾å¤‡åç§°çš„筛选条件
     * @param unitName å•元名称的筛选条件
     * @param description æŠ¥è­¦æè¿°çš„筛选条件
     * @param keyword å…³é”®å­—筛选条件
     * @param startTime èµ·å§‹æ—¶é—´ç­›é€‰æ¡ä»¶
     * @param endTime ç»“束时间筛选条件
     * @return ç¬¦åˆæ¡ä»¶çš„æŠ¥è­¦æ€»æ•°
     */
    int getTotalAlarmCount(
        const std::string& id,
        const std::string& severityLevel,
        const std::string& deviceName,
        const std::string& unitName,
        const std::string& description,
        const std::string& startTime,
        const std::string& endTime);
    int getTotalAlarmCount(const std::string& keyword, const std::string& startTime, const std::string& endTime);
    /**
     * æ›´æ–°æŠ¥è­¦ç»“束时间
SourceCode/Bond/Servo/CAligner.cpp
@@ -32,7 +32,7 @@
    void CAligner::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CAligner>initPins");
        LOGD("<CAligner>initPins");
        addPin(SERVO::PinType::INPUT, _T("In1"));
        addPin(SERVO::PinType::INPUT, _T("In2"));
        addPin(SERVO::PinType::OUTPUT, _T("Out1"));
@@ -68,6 +68,25 @@
                delete pStep;
            }
        }
        // VCR Event Report
        // æœºå™¨ä¸ŠæŠ¥æ‰«ç ç»“果,扫码器预计安装在巡边检机器上
        {
            CEqReadStep* pStep = new CEqReadStep(0x5fef, 15 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeVCREventReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_VCR1_EVENT_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0x4a);
            pStep->setReturnDev(0x91e);
            if (addStep(STEP_ID_VCR1_EVENT_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
    }
    void CAligner::onReceiveLBData(const char* pszData, size_t size)
SourceCode/Bond/Servo/CBakeCooling.cpp
@@ -32,7 +32,7 @@
    void CBakeCooling::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CBakeCooling>initPins");
        LOGD("<CBakeCooling>initPins");
        addPin(SERVO::PinType::INPUT, _T("In1"));
        addPin(SERVO::PinType::INPUT, _T("In2"));
        addPin(SERVO::PinType::OUTPUT, _T("Out"));
SourceCode/Bond/Servo/CBonder.cpp
@@ -692,7 +692,7 @@
        // 3.上腔压力合计
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("上腔压力合计", "", this->getName().c_str(), v * 0.01f));
        params.push_back(CParam("上腔压力合计", "", this->getName().c_str(), ((short)v) * 0.01f));
        i += 2;
        // 4.管道真空规值
@@ -763,7 +763,12 @@
        params.push_back(CParam("下腔温度6", "", this->getName().c_str(), v * 0.1f));
        i += 2;
        // 18.压合剩余时间
        // 18.加热剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("加热剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
        // 19.压合剩余时间
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8;
        params.push_back(CParam("压合剩余时间", "", this->getName().c_str(), v * 0.01f));
        i += 2;
SourceCode/Bond/Servo/CEFEM.cpp
@@ -133,7 +133,7 @@
                    LOGI("<CEquipment-%s>发送RobotCmdS成功.", m_strName.c_str());
                }
                else {
                    LOGI("<CEquipment-%s>发送RobotCmds失败,code:%d", m_strName.c_str(), code);
                    LOGE("<CEquipment-%s>发送RobotCmds失败,code:%d", m_strName.c_str(), code);
                }
                return 0;
@@ -367,7 +367,7 @@
    void CEFEM::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CEFEM>initPins");
        LOGD("<CEFEM>initPins");
    }
    void CEFEM::initSteps()
@@ -456,25 +456,6 @@
            pStep->setName(STEP_EQ_CIM_MESSAGE_CONFIRM);
            pStep->setWriteSignalDev(0x49);
            if (addStep(STEP_ID_CIM_MSG_CONFIRM_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
        {
            // VCR Event Report
            // æœºå™¨ä¸ŠæŠ¥æ‰«ç ç»“果,扫码器预计安装在巡边检机器上
            CEqReadStep* pStep = new CEqReadStep(0x5fef, 15 * 2,
                [&](void* pFrom, int code, const char* pszData, size_t size) -> int {
                    if (code == ROK && pszData != nullptr && size > 0) {
                        decodeVCREventReport((CStep*)pFrom, pszData, size);
                    }
                    return -1;
                });
            pStep->setName(STEP_EQ_VCR1_EVENT_REPORT);
            pStep->setProp("Port", (void*)1);
            pStep->setWriteSignalDev(0x4a);
            pStep->setReturnDev(0x91e);
            if (addStep(STEP_ID_VCR1_EVENT_REPORT, pStep) != 0) {
                delete pStep;
            }
        }
@@ -893,7 +874,7 @@
    void CEFEM::printDebugRobotState()
    {
        LOGI("<CEFEM>Robot status:%d, ARM1:%s, ARM2:%s",
        LOGD("<CEFEM>Robot status:%d, ARM1:%s, ARM2:%s",
            m_robotData.status,
            m_robotData.armState[0] ? _T("ON") : _T("OFF"),
            m_robotData.armState[1] ? _T("ON") : _T("OFF"));
SourceCode/Bond/Servo/CEqAlarmStep.cpp
@@ -58,7 +58,7 @@
        m_nAlarmCode = (unsigned int)CToolUnits::toInt16(&szBuffer[6]);
        m_nAlarmLevel = (unsigned int)CToolUnits::toInt16(&szBuffer[8]);
        LOGI("<CEqAlarmStep> Equipment Alarm state Changed<State:%d, Unit:%d, Level:%d, Code:%d, ID:%d>",
        LOGE("<CEqAlarmStep> Equipment Alarm state Changed<State:%d, Unit:%d, Level:%d, Code:%d, ID:%d>",
            m_nAlarmState, m_nUnitId, m_nAlarmLevel, m_nAlarmCode, m_nAlarmId,
            m_strText.c_str(), m_strDescription.c_str());
@@ -68,7 +68,7 @@
    int CEqAlarmStep::onComplete()
    {
        CReadStep::onComplete();
        LOGI("<CEqAlarmStep> onComplete.");
        LOGD("<CEqAlarmStep> onComplete.");
        return 0;
    }
@@ -76,7 +76,7 @@
    int CEqAlarmStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqAlarmStep> onTimeout.");
        LOGE("<CEqAlarmStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqJobEventStep.cpp
@@ -57,7 +57,7 @@
    int CEqJobEventStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqJobEventStep> onTimeout.");
        LOGE("<CEqJobEventStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqPortChangeStep.cpp
@@ -80,7 +80,7 @@
    int CEqPortChangeStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEQPortChangeStep> onTimeout.");
        LOGE("<CEQPortChangeStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqProcessStep.cpp
@@ -119,7 +119,7 @@
    int CEqProcessStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqProcessStep> onTimeout.");
        LOGE("<CEqProcessStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqReadIntStep.cpp
@@ -71,7 +71,7 @@
    int CEqReadIntStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqReadIntStep> onTimeout.");
        LOGE("<CEqReadIntStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqReadStep.cpp
@@ -53,14 +53,14 @@
        int nRet = m_pCclink->ReadData2(m_station, DeviceType::W, m_nDataDev,
            (long)min(READ_BUFFER_MAX, m_nReadSize), szBuffer);
        if (0 != nRet) {
            LOGI("<CEqReadStep>Read data error.");
            LOGE("<CEqReadStep>Read data error.");
            if (m_onReadBlock != nullptr) {
                m_onReadBlock(this, RERROR, nullptr, 0);
            }
            return -1;
        }
        LOGI("<CEqReadStep>read data succeed.");
        LOGD("<CEqReadStep>read data succeed.");
        if (m_onReadBlock != nullptr) {
            m_onReadBlock(this, ROK, szBuffer, m_nReadSize);
        }
@@ -72,7 +72,7 @@
    int CEqReadStep::onComplete()
    {
        CReadStep::onComplete();
        LOGI("<CEqReadStep> onComplete.");
        LOGD("<CEqReadStep> onComplete.");
        if (m_onReadBlock != nullptr) {
            m_onReadBlock(this, RCOMPLETE, nullptr, 0);
        }
@@ -83,7 +83,7 @@
    int CEqReadStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqReadStep> onTimeout.");
        LOGE("<CEqReadStep> onTimeout.");
        if (m_onReadBlock != nullptr) {
            m_onReadBlock(this, RTIMEOUT, nullptr, 0);
        }
SourceCode/Bond/Servo/CEqStatusStep.cpp
@@ -93,7 +93,7 @@
    int CEqStatusStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqStatusStep> onTimeout.");
        LOGE("<CEqStatusStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEqVcrEventStep.cpp
@@ -70,7 +70,7 @@
    int CEqVcrEventStep::onTimeout()
    {
        CReadStep::onTimeout();
        LOGI("<CEqVcrEventStep> onTimeout.");
        LOGE("<CEqVcrEventStep> onTimeout.");
        return 0;
    }
SourceCode/Bond/Servo/CEquipment.cpp
@@ -5,6 +5,7 @@
#include "CArm.h"
#include "CGlassPool.h"
#include "Servo.h"
#include "GlassJson.h"
namespace SERVO {
@@ -335,11 +336,16 @@
            for (int i = 0; i < SLOT_MAX; i++) {
                m_slot[i].serialize(ar);
                CGlass* pGlass = (CGlass *)m_slot[i].getContext();
                if (pGlass != nullptr) {
                    pGlass->serialize(ar);
                if (pGlass != nullptr) {
                    const std::string pretty = GlassJson::ToPrettyString(*pGlass);
                    CString strPretty = CString(pretty.c_str());
                    ar << strPretty;
                    CGlass* pBuddy = pGlass->getBuddy();
                    if (pBuddy != nullptr) {
                        pBuddy->serialize(ar);
                        const std::string prettyBuddy = GlassJson::ToPrettyString(*pBuddy);
                        CString strPrettyBuddy = CString(prettyBuddy.c_str());
                        ar << strPrettyBuddy;
                    }
                }
            }
@@ -349,17 +355,29 @@
            for (int i = 0; i < SLOT_MAX; i++) {
                m_slot[i].serialize(ar);
                if (m_slot[i].getTempContext() != nullptr) {
                    CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
                    pGlass->serialize(ar);
                    m_slot[i].setContext(pGlass);
                    if (pGlass->getBuddy() != nullptr) {
                        CGlass* pBuddy = theApp.m_model.m_glassPool.allocaGlass();
                        pBuddy->serialize(ar);
                        pGlass->forceSetBuddy(pBuddy);
                    CString strPretty;
                    std::string pretty;
                    ar >> strPretty;
                    pretty = (LPTSTR)(LPCTSTR)strPretty;
                    if (!pretty.empty()) {
                        CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
                        GlassJson::FromString(pretty, *pGlass);
                        m_slot[i].setContext(pGlass);
                        if (!pGlass->getBuddyId().empty()) {
                            CGlass* pBuddy = theApp.m_model.m_glassPool.allocaGlass();
                            CString strPrettyBuddy;
                            std::string prettyBuddy;
                            ar >> strPrettyBuddy;
                            prettyBuddy = (LPTSTR)(LPCTSTR)strPrettyBuddy;
                            GlassJson::FromString(prettyBuddy, *pBuddy);
                            pGlass->forceSetBuddy(pBuddy);
                        }
                    }
                }
            }
            // æ¢³ç†å„玻璃之间的绑定关系
            /*
            Lock();
@@ -839,7 +857,7 @@
        CEquipment* pFromEq = pFromPin->getEquipment();
        ASSERT(pFromEq);
        LOGI("<CEquipment><%s-%s>收到来自<%s.%s>的Intent<%d,%s,0x%x>",
        LOGD("<CEquipment><%s-%s>收到来自<%s.%s>的Intent<%d,%s,0x%x>",
            this->getName().c_str(),
            pPin->getName().c_str(),
            pFromEq->getName().c_str(),
@@ -916,7 +934,9 @@
        ASSERT(pGlass);
        Lock();
        pGlass->addPath(m_nID, getSlotUnit(putSlot));
        pGlass->addPath(m_nID, getSlotUnit(putSlot), putSlot);
        CGlass* pBuddy = pGlass->getBuddy();
        if (pBuddy != nullptr) pBuddy->addPath(m_nID, getSlotUnit(putSlot), putSlot);
        m_slot[putSlot - 1].setContext(pGlass);
        pGlass->release();                // tempFetchOut需要调用一次release
        Unlock();
@@ -990,6 +1010,22 @@
        return nullptr;
    }
    int CEquipment::getAllGlass(std::vector<CGlass*>& glasses)
    {
        Lock();
        for (int i = 0; i < SLOT_MAX; i++) {
            if (!m_slot[i].isEnable()) continue;
            CGlass* pGlass = (CGlass*)m_slot[i].getContext();
            if (pGlass != nullptr) {
                pGlass->addRef();
                glasses.push_back(pGlass);
            }
        }
        Unlock();
        return (int)glasses.size();
    }
    CJobDataS* CEquipment::getJobDataSWithCassette(int cassetteSequenceNo, int jobSequenceNo)
@@ -1138,7 +1174,7 @@
                    LOGI("<CEquipment-%s>设置DispatchingMode成功.", m_strName.c_str());
                }
                else {
                    LOGI("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_strName.c_str(), code);
                    LOGE("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_strName.c_str(), code);
                }
                return 0;
@@ -1168,7 +1204,7 @@
                LOGI("<CEquipment-%s>返回值: %d", m_strName.c_str(), retCode);
            }
            else {
                LOGI("<CEquipment-%s>设置indexerOperationMode失败,code:%d", m_strName.c_str(), code);
                LOGE("<CEquipment-%s>设置indexerOperationMode失败,code:%d", m_strName.c_str(), code);
            }
            if (onWritedRetBlock != nullptr) {
@@ -1199,7 +1235,7 @@
            }
            else {
                m_recipesManager.syncFailed();
                LOGI("<CEquipment-%s>请求单元<%d>主配方列表失败,code:%d", m_strName.c_str(), unitNo, code);
                LOGE("<CEquipment-%s>请求单元<%d>主配方列表失败,code:%d", m_strName.c_str(), unitNo, code);
            }
            return 0;
@@ -1234,7 +1270,7 @@
            }
            else {
                m_recipesManager.syncFailed();
                LOGI("<CEquipment-%s>请求单元<%d>主配方参数列表失败,code:%d", m_strName.c_str(), unitNo, code);
                LOGE("<CEquipment-%s>请求单元<%d>主配方参数列表失败,code:%d", m_strName.c_str(), unitNo, code);
            }
            return 0;
@@ -1358,32 +1394,32 @@
    CSlot* CEquipment::getProcessedSlot(MaterialsType putSlotType, BOOL bJobMode/* = FALSE*/)
    {
        for (int i = 0; i < SLOT_MAX; i++) {
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 001");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 001");
            if (!m_slot[i].isEnable()) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 002");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 002");
            if (m_slot[i].isLock()) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 003");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 003");
            CGlass* pGlass = (CGlass*)m_slot[i].getContext();
            if (!isSlotProcessed(i)) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 004");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 004");
            if (pGlass == nullptr) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 005");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 005");
            if (!pGlass->isScheduledForProcessing()) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 006");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 006");
            if (bJobMode && pGlass->getProcessJob() == nullptr) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 007");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 007");
            if(pGlass->getInspResult(m_nID, 0) == InspResult::Fail) continue;
            int lsPath = m_slot[i].getLinkSignalPath();
            if(!m_bLinkSignalToUpstream[lsPath][SIGNAL_UPSTREAM_INLINE]
                || m_bLinkSignalToUpstream[lsPath][SIGNAL_UPSTREAM_TROUBLE]
                || !m_bLinkSignalToUpstream[lsPath][SIGNAL_INTERLOCK]
                || !m_bLinkSignalToUpstream[lsPath][SIGNAL_SEND_ABLE] ) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 008");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 008");
            MaterialsType glassType = pGlass->getType();
            if (glassType == MaterialsType::G1 && putSlotType == MaterialsType::G2) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 009");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 009");
            if (glassType == MaterialsType::G2 && putSlotType == MaterialsType::G1) continue;
            if (m_nTestFlag == 1) LOGI("getProcessedSlot 00a");
            if (m_nTestFlag == 1) LOGD("getProcessedSlot 00a");
            return &m_slot[i];
        }
@@ -1537,7 +1573,8 @@
        CGlass* pGlass = this->getGlassWithCassette(processData.getCassetteSequenceNo(),
            processData.getJobSequenceNo());
        if (pGlass == nullptr) {
            LOGE("<CEquipment-%s>找不到对应Glass, å…³è”工艺参数失败。", this->getName().c_str(),
            LOGE("<CEquipment-%s>找不到对应Glass, å…³è”工艺参数失败。CassetteSequenceNo:%d/%d",
                this->getName().c_str(),
                processData.getCassetteSequenceNo(),
                processData.getJobSequenceNo());
            return -1;
@@ -1547,7 +1584,14 @@
        std::vector<CParam> params;
        this->parsingParams((const char*)rawData.data(), rawData.size(), params);
        pGlass->addParams(params);
        // å…³è”çš„Glass也要更新
        CGlass* pBuddy = pGlass->getBuddy();
        LOGI("<Equipment-%s>decodeProcessDataReport pBuddy=%x %s", getName().c_str(), pBuddy, pGlass->getID().c_str());
        if (pBuddy != nullptr) {
            LOGI("<Equipment-%s>decodeProcessDataReport addParams pBuddy=%x %s", getName().c_str(), pBuddy, pGlass->getID().c_str());
            pBuddy->addParams(params);
        }
        return nRet;
    }
@@ -1724,13 +1768,23 @@
            vcrEventReport.getGlassId().c_str());
        // æ›´æ–°Glass的ID
        CGlass* pGlass = getGlassWithCassette(vcrEventReport.getCassetteSequenceNo(),
            vcrEventReport.getJobSequenceNo());
        if (pGlass != nullptr) {
            pGlass->setID(vcrEventReport.getGlassId().c_str());
        }
        // ç¼“å­˜Attribute,用于调试时显示信息
        unsigned int weight = 201;
        CAttributeVector& attrubutes = pStep->attributeVector();
        vcrEventReport.getAttributeVector(attrubutes, weight);
        // 0426, å…ˆå›ºå®šè¿”回1(OK)
        ((CReadStep*)pStep)->setReturnCode((short)VCR_Reply_Code::OK);
        return 0;
    }
@@ -1769,7 +1823,12 @@
                getName().c_str(), cassetteNo, jobSequenceNo);
            return -1;
        }
        pGlass->setInspResult(m_nID, 0, judgeStringToInspResult(strPanelJudgeData));
        auto result = judgeStringToInspResult(strPanelJudgeData);
        pGlass->setInspResult(m_nID, 0, result);
        if (m_listener.onPanelDataReport != nullptr) {
            m_listener.onPanelDataReport(this, pGlass);
        }
        return 0;
    }
SourceCode/Bond/Servo/CEquipment.h
@@ -72,6 +72,7 @@
        ONMAPMISMATCH        onMapMismatch;
        ONPORTSTATUSCHANGED    onPortStatusChanged;
        ONVCREVENTREPORT    onSVDataReport;
        ONVCREVENTREPORT    onPanelDataReport;
    } EquipmentListener;
@@ -206,6 +207,7 @@
        CGlass* getGlassWithCassette(int cassetteSequenceNo, int jobSequenceNo);
        CGlass* getAnyGlass();
        CGlass* getGlass(const char* pszGlassId);
        int getAllGlass(std::vector<CGlass*>& glasses);
        CJobDataS* getJobDataSWithCassette(int cassetteSequenceNo, int jobSequenceNo);
        // éªŒè¯çŽ»ç’ƒå’Œæ§½æ˜¯å¦åŒ¹é…
SourceCode/Bond/Servo/CEquipmentPage3.cpp
@@ -91,7 +91,7 @@
                AfxMessageBox("设置EAS模式成功!");
            }
            else {
                LOGI("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_pEquipment->getName().c_str(), code);
                LOGE("<CEquipment-%s>设置DispatchingMode失败,code:%d", m_pEquipment->getName().c_str(), code);
                AfxMessageBox("设置EAS模式失败!");
            }
SourceCode/Bond/Servo/CExpandableListCtrl.cpp
@@ -3,7 +3,11 @@
IMPLEMENT_DYNAMIC(CExpandableListCtrl, CListCtrl)
CExpandableListCtrl::CExpandableListCtrl() {}
CExpandableListCtrl::CExpandableListCtrl()
{
    m_popupCols = { };
}
CExpandableListCtrl::~CExpandableListCtrl() {}
BEGIN_MESSAGE_MAP(CExpandableListCtrl, CListCtrl)
@@ -17,7 +21,6 @@
    if (CListCtrl::OnCreate(lpCreateStruct) == -1)
        return -1;
    // æŠ¥è¡¨é£Žæ ¼åˆ—举例
    SetExtendedStyle(GetExtendedStyle()
        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
@@ -26,13 +29,12 @@
void CExpandableListCtrl::PreSubclassWindow()
{
    // æŠ¥è¡¨é£Žæ ¼åˆ—举例
    SetExtendedStyle(GetExtendedStyle()
        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
    CListCtrl::PreSubclassWindow();
}
// ===== æ ‘ API =====
CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector<CString>& cols)
{
    auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
@@ -67,15 +69,12 @@
void CExpandableListCtrl::RebuildVisible()
{
    // 1) é‡å»ºå¯è§åºåˆ—
    m_visible.clear();
    for (auto& r : m_roots) appendVisible(r.get());
    // 2) é‡ç»˜/重填数据
    SetRedraw(FALSE);
    DeleteAllItems();
    // æ’入可见行
    for (int i = 0; i < (int)m_visible.size(); ++i) {
        Node* n = m_visible[i];
        LVITEM lvi{};
@@ -85,11 +84,16 @@
        lvi.pszText = const_cast<LPTSTR>((LPCTSTR)(n->cols.empty() ? _T("") : n->cols[0]));
        InsertItem(&lvi);
        for (int col = 1; col < GetHeaderCtrl()->GetItemCount(); ++col) {
        const int colCount = GetHeaderCtrl() ? GetHeaderCtrl()->GetItemCount() : 1;
        for (int col = 1; col < colCount; ++col) {
            CString txt = (col < (int)n->cols.size()) ? n->cols[col] : _T("");
            SetItemText(i, col, txt);
        }
    }
    // é‡å»ºåŽï¼ŒæŒ‰è¡Œå·é¢œè‰²æ•°ç»„对齐
    m_rowColors.resize(GetItemCount());
    SetRedraw(TRUE);
    Invalidate();
}
@@ -119,6 +123,69 @@
    return m_visible[i];
}
// ===== é¢œè‰² API =====
void CExpandableListCtrl::SetNodeColor(Node* n, COLORREF text, COLORREF bk)
{
    if (!n) return;
    RowColor rc{};
    rc.text = text; rc.bk = bk;
    rc.hasText = (text != CLR_DEFAULT);
    rc.hasBk = (bk != CLR_DEFAULT);
    m_colorByNode[n] = rc;
    for (int i = 0; i < (int)m_visible.size(); ++i) {
        if (m_visible[i] == n) {
            RedrawItems(i, i);
            UpdateWindow();
            return;
        }
    }
}
void CExpandableListCtrl::ClearNodeColor(Node* n)
{
    if (!n) return;
    auto it = m_colorByNode.find(n);
    if (it != m_colorByNode.end()) {
        m_colorByNode.erase(it);
        for (int i = 0; i < (int)m_visible.size(); ++i) {
            if (m_visible[i] == n) {
                RedrawItems(i, i);
                UpdateWindow();
                return;
            }
        }
    }
}
void CExpandableListCtrl::ClearAllColors()
{
    m_colorByNode.clear();
    m_rowColors.clear();
    Invalidate(FALSE);
}
// å…¼å®¹æ—§æŽ¥å£ï¼šæŒ‰â€œå¯è§è¡Œå·â€ç€è‰²
void CExpandableListCtrl::SetItemColor(DWORD_PTR iItem, COLORREF TextColor, COLORREF TextBkColor)
{
    SetItemColorByVisibleIndex((int)iItem, TextColor, TextBkColor);
}
void CExpandableListCtrl::SetItemColorByVisibleIndex(int row, COLORREF text, COLORREF bk)
{
    if (row < 0) return;
    if (row >= (int)m_rowColors.size())
        m_rowColors.resize(row + 1);
    RowColor rc{};
    rc.text = text; rc.bk = bk;
    rc.hasText = (text != CLR_DEFAULT);
    rc.hasBk = (bk != CLR_DEFAULT);
    m_rowColors[row] = rc;
    RedrawItems(row, row);
    UpdateWindow();
}
CRect CExpandableListCtrl::expanderRectForRow(int row) const
{
    CRect rcLabel;
@@ -127,7 +194,7 @@
    Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row);
    if (!n || n->children.empty())
        return CRect(0, 0, 0, 0); // å¶å­ä¸å ä½ï¼Œæ–‡æœ¬å°±ä¸ä¼šè¢«å¤šæŽ¨ä¸€æ ¼
        return CRect(0, 0, 0, 0);
    const int indent = n->level;
    const int left = rcLabel.left + m_expanderPadding + indent * 16;
@@ -140,14 +207,44 @@
    );
}
// é¢œè‰²è®¡ç®—:优先 Node*,其次行号;若需要则让系统高亮覆盖
void CExpandableListCtrl::computeColorsForRow(int row, COLORREF& outText, COLORREF& outBk) const
{
    outText = ListView_GetTextColor(const_cast<CExpandableListCtrl*>(this)->m_hWnd);
    outBk = ListView_GetBkColor(const_cast<CExpandableListCtrl*>(this)->m_hWnd);
    const bool selected = (const_cast<CExpandableListCtrl*>(this)->GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0;
    const bool focusOnCtrl = (const_cast<CExpandableListCtrl*>(this)->GetSafeHwnd() == ::GetFocus());
    if (m_preserveSelHighlight && selected) {
        outBk = GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE);
        outText = GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
        return;
    }
    // Node* é¢œè‰²
    if (Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row)) {
        auto it = m_colorByNode.find(n);
        if (it != m_colorByNode.end()) {
            if (it->second.hasText) outText = it->second.text;
            if (it->second.hasBk)   outBk = it->second.bk;
            return;
        }
    }
    // è¡Œå·é¢œè‰²
    if (row >= 0 && row < (int)m_rowColors.size()) {
        const RowColor& rc = m_rowColors[row];
        if (rc.hasText) outText = rc.text;
        if (rc.hasBk)   outBk = rc.bk;
    }
}
void CExpandableListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMITEMACTIVATE pia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
    if (pia->iItem >= 0) {
        CPoint pt = pia->ptAction;
        // å‘½ä¸­å±•开按钮?
        CRect expRc = expanderRectForRow(pia->iItem);
        if (expRc.PtInRect(pt)) {
            Node* n = GetNodeByVisibleIndex(pia->iItem);
@@ -156,6 +253,38 @@
            }
        }
    }
    // â€”— è‹¥ç‚¹å‡»åˆ°éœ€è¦â€œå…¨æ–‡æ˜¾ç¤ºâ€çš„列,则向父窗口发送自定义通知 â€”— //
    if (!m_popupCols.empty()) {
        LPNMITEMACTIVATE pia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
        // ç”¨ SubItemHitTest æ›´ç²¾å‡†æ‹¿åˆ°åˆ—
        LVHITTESTINFO ht{};
        ht.pt = pia->ptAction;
        int hit = SubItemHitTest(&ht);
        if (hit >= 0 && ht.iItem >= 0 && ht.iSubItem >= 0) {
            const int row = ht.iItem;
            const int col = ht.iSubItem;
            if (m_popupCols.count(col)) {
                CString full = GetItemText(row, col);
                if (!full.IsEmpty() && _IsCellTruncated(row, col, full)) {
                    NMC_ELC_SHOWFULLTEXT nm{};
                    nm.hdr.hwndFrom = m_hWnd;
                    nm.hdr.idFrom = GetDlgCtrlID();
                    nm.hdr.code = ELCN_SHOWFULLTEXT;
                    nm.iItem = row;
                    nm.iSubItem = col;
                    nm.text = full;
                    if (CWnd* pParent = GetParent()) {
                        pParent->SendMessage(WM_NOTIFY, nm.hdr.idFrom, reinterpret_cast<LPARAM>(&nm));
                    }
                }
            }
        }
    }
    *pResult = 0;
}
@@ -170,8 +299,15 @@
        return;
    case CDDS_ITEMPREPAINT:
    {
        const int row = (int)pCD->nmcd.dwItemSpec;
        COLORREF txt, bk;
        computeColorsForRow(row, txt, bk);
        pCD->clrText = txt;
        pCD->clrTextBk = bk;
        *pResult = CDRF_NOTIFYSUBITEMDRAW;
        return;
    }
    case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
    {
@@ -179,135 +315,191 @@
        const int col = pCD->iSubItem;
        CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
        if (col == 0)
        {
            CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc);
            Node* n = GetNodeByVisibleIndex(row);
            if (!n) { *pResult = CDRF_DODEFAULT; return; }
            // 1) èƒŒæ™¯/前景颜色:按是否选中
            const bool selected = (GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0;
            const bool focusOnCtrl = (GetSafeHwnd() == ::GetFocus());
            COLORREF bk = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE)
                : ListView_GetBkColor(m_hWnd);
            COLORREF txt = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)
                : ListView_GetTextColor(m_hWnd);
            // ä»…在需要时填充背景(避免“黑一片”)
            CBrush bkBrush(bk);
            pDC->FillRect(rc, &bkBrush);
            // 2) å±•å¼€/折叠指示(参考旧项目的右对齐坐标法,做像素对齐,纯GDI)
            if (!n->children.empty())
            {
                CRect box = expanderRectForRow(row);
                // ---- å¯è°ƒå‚数:与旧代码命名一致 ----
                // å³ä¾§ç•™ç™½ï¼ˆä¸Žæ–‡æœ¬é—´éš™/网格线保持距离)
                const int ROFFSET = 2;
                // é—­åˆ/展开的“宽度”设置:奇数更顺眼(9/11 éƒ½è¡Œï¼‰
                const int WIDE = max(9, min(min(box.Width(), box.Height()), 13)); // â–¶ çš„边长
                const int WIDE2 = WIDE / 2;                                        // ä¸€åŠ
                const int EXPANDED_WIDE = WIDE;                                           // â–¼ çš„边长
                // è½»å¾®å†…缩,避免贴边(与你旧代码“按钮要刷一下”同效)
                box.DeflateRect(1, 1);
                // ç»Ÿä¸€åšå¶æ•°å¯¹é½ï¼Œå‡å°‘半像素锯齿
                auto even = [](int v) { return (v & 1) ? (v - 1) : v; };
                // è®¡ç®—“自下向上”的基准偏移,与旧 TreeCtrl ä¸€è‡´
                // è¿™é‡Œç”¨ box ä½œä¸º pRect
                POINT pt[3];
                if (n->expanded) {
                    // â–¼
                    int nBottomOffset = (box.Height() - EXPANDED_WIDE) / 2;
                    pt[0].x = box.right - ROFFSET - EXPANDED_WIDE;
                    pt[0].y = box.bottom - nBottomOffset;
                    pt[1].x = box.right - ROFFSET;
                    pt[1].y = box.bottom - nBottomOffset;
                    pt[2].x = box.right - ROFFSET;
                    pt[2].y = box.bottom - nBottomOffset - EXPANDED_WIDE;
                }
                else {
                    // â–¶
                    int nBottomOffset = (box.Height() - WIDE) / 2;
                    pt[0].x = box.right - ROFFSET - WIDE2;
                    pt[0].y = box.bottom - nBottomOffset - WIDE;
                    pt[1].x = box.right - ROFFSET - WIDE2;
                    pt[1].y = box.bottom - nBottomOffset;
                    pt[2].x = box.right - ROFFSET;
                    pt[2].y = box.bottom - nBottomOffset - WIDE2;
                }
                // ä»…填充,不描边(描边会加重台阶感);颜色用 txt ä¸Žä¸»é¢˜ä¸€è‡´
                HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(NULL_PEN));
                HBRUSH   hBrush = CreateSolidBrush(txt);
                HGDIOBJ oldBrush = pDC->SelectObject(hBrush);
                pDC->Polygon(pt, 3);
                pDC->SelectObject(oldPen);
                pDC->SelectObject(oldBrush);
                DeleteObject(hBrush);
            }
            // 3) æ–‡æœ¬ï¼šåŸºäºŽé¦–列区域右移(区分是否有子节点)
            const int indentPx = n->level * 14;
            const int baseLeft = rc.left + m_expanderPadding + indentPx;
            CRect textRc = rc;
            if (!n->children.empty()) {
                // æœ‰å­é¡¹ï¼šé¢„留按钮位 + æ–‡æœ¬é—´éš™
                textRc.left = baseLeft + m_expanderSize + m_textGap;
            }
            else {
                // å¶å­è¡Œï¼šä¸é¢„留按钮位,只给一点点叶子间隙(让层级缩进仍然生效)
                constexpr int kLeafGap = 2; // ä½ å¯è°ƒ 0~4
                textRc.left = baseLeft + kLeafGap;
            }
            pDC->SetBkMode(TRANSPARENT);
            pDC->SetTextColor(txt);
            CString txt0 = n->cols.empty() ? _T("") : n->cols[0];
            pDC->DrawText(txt0, textRc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
            // â€”— ç”»å®Œä¸‰è§’与文本之后,补一条该行的底部横向网格线 â€”—
            // ä»…当开启了 LVS_EX_GRIDLINES æ‰ç»˜åˆ¶
            if (GetExtendedStyle() & LVS_EX_GRIDLINES)
            {
                // ç”¨æ•´è¡Œ bounds,保证横线贯穿所有列的可见宽度
                CRect rcRow;
                GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
                // åº•è¾¹ y åæ ‡ï¼ˆä¸Žç³»ç»Ÿç½‘格线对齐)
                const int y = rcRow.bottom - 1;
                // é¢œè‰²ä¸Žç³»ç»Ÿé£Žæ ¼æŽ¥è¿‘;若觉得偏浅,可换 COLOR_3DSHADOW
                CPen pen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT));
                CPen* oldPen = pDC->SelectObject(&pen);
                // æ¨ªçº¿ä»Žè¡Œå·¦åˆ°è¡Œå³ï¼ˆå½“前可见区域)
                pDC->MoveTo(rcRow.left, y);
                pDC->LineTo(rcRow.right, y);
                pDC->SelectObject(oldPen);
            }
            *pResult = CDRF_SKIPDEFAULT;
        // å¦‚果没有树节点(纯平列表),首列也走默认绘制
        Node* n = GetNodeByVisibleIndex(row);
        if (col != 0 || !n) {
            *pResult = CDRF_DODEFAULT;
            return;
        }
        // å…¶ä»–列默认绘制
        *pResult = CDRF_DODEFAULT;
        // é¦–列自绘(树模式)
        CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc);
        COLORREF bk, txt;
        computeColorsForRow(row, txt, bk);
        CBrush bkBrush(bk);
        pDC->FillRect(rc, &bkBrush);
        if (!n->children.empty())
        {
            CRect box = expanderRectForRow(row);
            const int ROFFSET = 2;
            const int WIDE = max(9, min(min(box.Width(), box.Height()), 13));
            const int WIDE2 = WIDE / 2;
            const int EXPANDED_WIDE = WIDE;
            box.DeflateRect(1, 1);
            POINT pt[3];
            if (n->expanded) {
                int nBottomOffset = (box.Height() - EXPANDED_WIDE) / 2;
                pt[0].x = box.right - ROFFSET - EXPANDED_WIDE;
                pt[0].y = box.bottom - nBottomOffset;
                pt[1].x = box.right - ROFFSET;
                pt[1].y = box.bottom - nBottomOffset;
                pt[2].x = box.right - ROFFSET;
                pt[2].y = box.bottom - nBottomOffset - EXPANDED_WIDE;
            }
            else {
                int nBottomOffset = (box.Height() - WIDE) / 2;
                pt[0].x = box.right - ROFFSET - WIDE2;
                pt[0].y = box.bottom - nBottomOffset - WIDE;
                pt[1].x = box.right - ROFFSET - WIDE2;
                pt[1].y = box.bottom - nBottomOffset;
                pt[2].x = box.right - ROFFSET;
                pt[2].y = box.bottom - nBottomOffset - WIDE2;
            }
            HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(NULL_PEN));
            HBRUSH   hBrush = CreateSolidBrush(txt);
            HGDIOBJ oldBrush = pDC->SelectObject(hBrush);
            pDC->Polygon(pt, 3);
            pDC->SelectObject(oldPen);
            pDC->SelectObject(oldBrush);
            DeleteObject(hBrush);
        }
        const int indentPx = n->level * 14;
        const int baseLeft = rc.left + m_expanderPadding + indentPx;
        CRect textRc = rc;
        if (!n->children.empty()) {
            textRc.left = baseLeft + m_expanderSize + m_textGap;
        }
        else {
            constexpr int kLeafGap = 2;
            textRc.left = baseLeft + kLeafGap;
        }
        pDC->SetBkMode(TRANSPARENT);
        pDC->SetTextColor(txt);
        CString txt0 = n->cols.empty() ? _T("") : n->cols[0];
        pDC->DrawText(txt0, textRc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS);
        if (GetExtendedStyle() & LVS_EX_GRIDLINES)
        {
            CRect rcRow; GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
            const int y = rcRow.bottom - 1;
            CPen pen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT));
            CPen* oldPen = pDC->SelectObject(&pen);
            pDC->MoveTo(rcRow.left, y);
            pDC->LineTo(rcRow.right, y);
            pDC->SelectObject(oldPen);
        }
        *pResult = CDRF_SKIPDEFAULT;
        return;
    }
    }
    *pResult = CDRF_DODEFAULT;
}
// å…¼å®¹è¡Œä¸ºï¼šåŒæ­¥ SetItemText åˆ° Node->cols;维护行号颜色数组的插入/删除
LRESULT CExpandableListCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // åŒæ­¥ SetItemText åˆ° Node(A/W å…¼å®¹ï¼‰
    if (message == LVM_SETITEMTEXT
#ifdef LVM_SETITEMTEXTA
        || message == LVM_SETITEMTEXTA
#endif
#ifdef LVM_SETITEMTEXTW
        || message == LVM_SETITEMTEXTW
#endif
        )
    {
        int row = static_cast<int>(wParam);
        LVITEM* p = reinterpret_cast<LVITEM*>(lParam);
        if (p) {
            Node* n = GetNodeByVisibleIndex(row);
            if (n) {
                int sub = p->iSubItem;
                if (sub >= (int)n->cols.size())
                    n->cols.resize(sub + 1);
                CString newText = p->pszText ? p->pszText : _T("");
                n->cols[sub] = newText;
            }
        }
        // ç»§ç»­äº¤ç»™åŸºç±»å¤„理
    }
    LRESULT nRet = CListCtrl::WindowProc(message, wParam, lParam);
    // ç»´æŠ¤è¡Œå·é¢œè‰²æ•°ç»„(兼容旧 SetItemColor)
    if (message == LVM_INSERTITEM) {
        if (nRet != -1) {
            LVITEM* p = (LVITEM*)lParam;
            int pos = p ? p->iItem : (int)nRet;
            if (pos < 0) pos = (int)nRet;
            if (pos > (int)m_rowColors.size()) pos = (int)m_rowColors.size();
            m_rowColors.insert(m_rowColors.begin() + pos, RowColor{}); // é»˜è®¤è‰²
        }
    }
    else if (message == LVM_DELETEITEM) {
        if (nRet != 0) {
            int pos = (int)wParam;
            if (pos >= 0 && pos < (int)m_rowColors.size())
                m_rowColors.erase(m_rowColors.begin() + pos);
        }
    }
    else if (message == LVM_DELETEALLITEMS) {
        if (nRet != 0) {
            m_rowColors.clear();
        }
    }
    return nRet;
}
// CExpandableListCtrl.cpp é‡Œ
void CExpandableListCtrl::ClearTree()
{
    // æ¸…数据
    m_roots.clear();
    m_visible.clear();
    // æ¸…可见项(务必!否则旧页的行会残留)
    SetRedraw(FALSE);
    DeleteAllItems();
    SetRedraw(TRUE);
    Invalidate();
}
void CExpandableListCtrl::SetPopupFullTextColumns(const std::vector<int>& cols)
{
    m_popupCols.clear();
    for (int c : cols) m_popupCols.insert(c);
}
bool CExpandableListCtrl::_IsCellTruncated(int row, int col, const CString& text) const
{
    if (text.IsEmpty()) return false;
    // å•元格显示区域宽度
    CRect rcCell;
    if (!const_cast<CExpandableListCtrl*>(this)->GetSubItemRect(row, col, LVIR_BOUNDS, rcCell))
        return false;
    // ç”¨æŽ§ä»¶å­—体测量文本像素宽
    CClientDC dc(const_cast<CExpandableListCtrl*>(this));
    CFont* pOld = dc.SelectObject(const_cast<CExpandableListCtrl*>(this)->GetFont());
    CSize sz = dc.GetTextExtent(text);
    dc.SelectObject(pOld);
    const int kPadding = 8; // é¢„留一点边距/省略号余量
    return sz.cx > (rcCell.Width() - kPadding);
}
SourceCode/Bond/Servo/CExpandableListCtrl.h
@@ -1,6 +1,20 @@
#pragma once
#include <vector>
#include <memory>
#include <unordered_map>
#include <set>
// ===== è‡ªå®šä¹‰é€šçŸ¥ï¼šç‚¹å‡»éœ€è¦å¼¹å‡ºå…¨æ–‡çš„单元格 =====
#ifndef ELCN_SHOWFULLTEXT
#define ELCN_SHOWFULLTEXT (NM_FIRST - 201)
struct NMC_ELC_SHOWFULLTEXT {
    NMHDR   hdr;       // hwndFrom / idFrom / code = ELCN_SHOWFULLTEXT
    int     iItem;     // è¡Œ
    int     iSubItem;  // åˆ—(0-based)
    CString text;      // å®Œæ•´æ–‡æœ¬
};
#endif
class CExpandableListCtrl : public CListCtrl
{
@@ -13,39 +27,56 @@
        std::vector<CString> cols; // å„列文本
        bool expanded = false;
        int level = 0; // ç¼©è¿›å±‚级
        Node(int nCols = 1) : cols(nCols) {}
    };
    CExpandableListCtrl();
    virtual ~CExpandableListCtrl();
    // æ•°æ®æž„建
    // ===== æ ‘数据构建(需要折叠功能时使用;纯平列表可忽略) =====
    Node* InsertRoot(const std::vector<CString>& cols);
    Node* InsertChild(Node* parent, const std::vector<CString>& cols);
    // å±•å¼€/折叠
    void Expand(Node* n);
    void Collapse(Node* n);
    void Toggle(Node* n);
    // åˆ·æ–°å¯è§åˆ—表
    void RebuildVisible();
    // ä¾¿æ·ï¼šé€šè¿‡å¯è§è¡Œå·å– Node*
    void   Expand(Node* n);
    void   Collapse(Node* n);
    void   Toggle(Node* n);
    void   RebuildVisible();
    Node* GetNodeByVisibleIndex(int i) const;
    // ===== è¡Œé…è‰² API =====
    // A) æŒ‰ Node* ç€è‰²ï¼ˆç”¨äºŽæ ‘/可折叠场景,索引变化不受影响)
    void SetNodeColor(Node* n, COLORREF text, COLORREF bk);
    void ClearNodeColor(Node* n);
    void ClearAllColors();
    // B) å…¼å®¹æ—§æŽ¥å£ï¼šæŒ‰â€œå¯è§è¡Œå·â€ç€è‰²ï¼ˆä¸Žä½ å½“前页面代码一致)
    void SetItemColor(DWORD_PTR iItem, COLORREF TextColor, COLORREF TextBkColor); // å…¼å®¹ ListCtrlEx
    void SetItemColorByVisibleIndex(int row, COLORREF text, COLORREF bk);         // åŒä¸Šåˆ«å
    // é€‰ä¸­è¡Œæ˜¯å¦ä½¿ç”¨ç³»ç»Ÿé«˜äº®ï¼ˆTRUE,默认);
    // è‹¥ä¸º FALSE,则选中时仍显示自定义颜色
    void SetPreserveSelectionHighlight(BOOL b) { m_preserveSelHighlight = b; }
    // æ¸…除树
    void ClearTree();
    // è®¾ç½®å“ªäº›åˆ—需要“被截断则通知父窗口显示全文”(0-based列号)
    void SetPopupFullTextColumns(const std::vector<int>& cols);
    std::set<int> m_popupCols; // éœ€è¦é€šçŸ¥çš„列集合
    bool _IsCellTruncated(int row, int col, const CString& text) const;
protected:
    virtual void PreSubclassWindow();
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
    virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam) override;
    DECLARE_MESSAGE_MAP()
private:
    void appendVisible(Node* n);
    CRect expanderRectForRow(int row) const;        // é¦–列展开按钮区域
    virtual void PreSubclassWindow();
protected:
    // æ¶ˆæ¯
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
    DECLARE_MESSAGE_MAP()
    void computeColorsForRow(int row, COLORREF& outText, COLORREF& outBk) const;
private:
    std::vector<std::unique_ptr<Node>> m_roots;     // é¡¶å±‚节点
@@ -53,6 +84,17 @@
    int  m_expanderPadding = 6;                     // é¦–列内侧边距
    int  m_expanderSize = 10;                       // å°ä¸‰è§’/方块大小
    int  m_textGap = 6;
    struct RowColor {
        COLORREF text = CLR_DEFAULT;
        COLORREF bk = CLR_DEFAULT;
        bool hasText = false;
        bool hasBk = false;
    };
    // é¢œè‰²ï¼ˆä¸¤è·¯æ¥æºï¼Œä¼˜å…ˆçº§ï¼šNode* > è¡Œå·ï¼‰
    std::unordered_map<Node*, RowColor> m_colorByNode; // æ ‘节点颜色
    std::vector<RowColor>               m_rowColors;   // æŒ‰å½“前可见行号的颜色(兼容旧代码)
    BOOL m_preserveSelHighlight = TRUE; // TRUE=选中时优先系统高亮
};
SourceCode/Bond/Servo/CFliper.cpp
@@ -32,7 +32,7 @@
    void CFliper::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CFliper>initPins");
        LOGD("<CFliper>initPins");
        addPin(SERVO::PinType::INPUT, _T("In"));
        addPin(SERVO::PinType::OUTPUT, _T("Out1"));
        addPin(SERVO::PinType::OUTPUT, _T("Out2"));
SourceCode/Bond/Servo/CGlass.cpp
@@ -31,6 +31,7 @@
        if (m_pBuddy != nullptr) {
            m_pBuddy->release();
            m_pBuddy = nullptr;
        }
    }
@@ -50,7 +51,7 @@
        return strText;
    }
    MaterialsType CGlass::getType()
    MaterialsType CGlass::getType() const
    {
        return m_type;
    }
@@ -65,7 +66,7 @@
        m_strID = pszID;
    }
    std::string& CGlass::getID()
    const std::string& CGlass::getID() const
    {
        return m_strID;
    }
@@ -107,7 +108,26 @@
        return m_pPath;
    }
    CPath* CGlass::getPathWithEq(unsigned int nEqId, unsigned int nUnit)
    std::string CGlass::getPathDescription() const
    {
        std::string strOut, strPath;
        char szBuffer[256];
        CPath* pTemp = m_pPath;
        while (pTemp != nullptr) {
            pTemp->getSimpleDescription(strPath);
            if (strPath.compare("ARM1") != 0 && strPath.compare("ARM2") != 0) {
                if (!strOut.empty()) strOut.append(" -> ");
                strOut.append(strPath);
            }
            pTemp = pTemp->getNext();
        }
        return strOut;
    }
    CPath* CGlass::getPathWithEq(unsigned int nEqId, unsigned int nUnit) const
    {
        CPath* pTemp = m_pPath;
        while (pTemp != nullptr) {
@@ -121,9 +141,9 @@
        return nullptr;
    }
    void CGlass::addPath(unsigned int nEqId, unsigned int nUnit)
    void CGlass::addPath(unsigned int nEqId, unsigned int nUnit, unsigned int slot)
    {
        CPath* pPath = new CPath(nEqId, nUnit);
        CPath* pPath = new CPath(nEqId, nUnit, slot);
        if (m_pPath == nullptr) {
            m_pPath = pPath;
        }
@@ -219,9 +239,14 @@
        return m_pBuddy;
    }
    std::string& CGlass::getBuddyId()
    const std::string& CGlass::getBuddyId() const
    {
        return m_strBuddyId;
    }
    void CGlass::setBuddyId(std::string& strId)
    {
        m_strBuddyId = strId;
    }
    int CGlass::processEnd(unsigned int nEqId, unsigned int nUnit)
@@ -250,12 +275,17 @@
        return 0;
    }
    InspResult CGlass::getInspResult(unsigned int nEqId, unsigned int nUnit)
    InspResult CGlass::getInspResult(unsigned int nEqId, unsigned int nUnit) const
    {
        CPath* pPath = getPathWithEq(nEqId, nUnit);
        if (pPath == nullptr) return InspResult::NotInspected;
        return pPath->getInspResult();
    }
    InspResult CGlass::getAOIInspResult() const
    {
        return getInspResult(EQ_ID_MEASUREMENT, 0);
    }
    std::string CGlass::getStateText()
@@ -362,11 +392,13 @@
    void CGlass::markStart()
    {
        m_state = GlsState::InProcess;
        m_tStart = std::chrono::system_clock::now();
    }
    void CGlass::markEnd()
    {
        m_state = GlsState::Completed;
        m_tEnd = std::chrono::system_clock::now();
    }
@@ -379,4 +411,23 @@
    {
        return m_params;
    }
    std::string CGlass::getParamsDescription() const
    {
        std::string strOut;
        char szBuffer[256];
        for (auto p : m_params) {
            if (!strOut.empty()) strOut.append(",");
            if (p.getValueType() == PVT_INT) {
                sprintf_s(szBuffer, 256, "%s:%d", p.getName().c_str(), p.getIntValue());
            }
            else if (p.getValueType() == PVT_DOUBLE) {
                sprintf_s(szBuffer, 256, "%s:%f", p.getName().c_str(), p.getDoubleValue());
            }
            strOut.append(szBuffer);
        }
        return strOut;
    }
}
SourceCode/Bond/Servo/CGlass.h
@@ -33,21 +33,23 @@
    public:
        virtual std::string& getClassName();
        virtual std::string toString();
        short getCassetteSequenceNo() { return m_jobDataS.getCassetteSequenceNo(); }
        short getJobSequenceNo() { return m_jobDataS.getJobSequenceNo(); }
        MaterialsType getType();
        short getCassetteSequenceNo() const { return m_jobDataS.getCassetteSequenceNo(); }
        short getJobSequenceNo() const { return m_jobDataS.getJobSequenceNo(); }
        MaterialsType getType() const;
        void setType(MaterialsType type);
        void setID(const char* pszID);
        std::string& getID();
        const std::string& getID() const;
        void setOriginPort(int port, int slot);
        void getOrginPort(int& port, int& slot);
        BOOL isScheduledForProcessing();
        void setScheduledForProcessing(BOOL bProcessing);
        CProcessJob* getProcessJob();
        void setProcessJob(CProcessJob* pProcessJob);
        CPath* getPathWithEq(unsigned int nEqId, unsigned int nUnit);
        CPath* getPathWithEq(unsigned int nEqId, unsigned int nUnit) const;
        CPath* getPath();
        void addPath(unsigned int nEqId, unsigned int nUnit);
        void addPath(unsigned int nEqId, unsigned int nUnit, unsigned int slot);
        std::string getPathDescription() const;
        std::string getParamsDescription() const;
        void serialize(CArchive& ar);
        void setJobDataS(CJobDataS* pJobDataS);
        void updateJobDataS(CJobDataS* pJobDataS);
@@ -55,11 +57,13 @@
        BOOL setBuddy(CGlass* pGlass);
        BOOL forceSetBuddy(CGlass* pGlass);
        CGlass* getBuddy();
        std::string& getBuddyId();
        const std::string& getBuddyId() const;
        void setBuddyId(std::string& strId);
        int processEnd(unsigned int nEqId, unsigned int nUnit);
        BOOL isProcessed(unsigned int nEqId, unsigned int nUnit);
        int setInspResult(unsigned int nEqId, unsigned int nUnit, InspResult result);
        InspResult getInspResult(unsigned int nEqId, unsigned int nUnit);
        InspResult getInspResult(unsigned int nEqId, unsigned int nUnit) const;
        InspResult getAOIInspResult() const;
    public:
        // æ–°å¢žçŠ¶æ€
SourceCode/Bond/Servo/CJobDataS.cpp
@@ -143,7 +143,7 @@
        m_pOwner = pOwner;
    }
    int CJobDataS::getCassetteSequenceNo()
    int CJobDataS::getCassetteSequenceNo() const
    {
        return m_nCassetteSequenceNo;
    }
@@ -153,7 +153,7 @@
        m_nCassetteSequenceNo = no;
    }
    int CJobDataS::getJobSequenceNo()
    int CJobDataS::getJobSequenceNo() const
    {
        return m_nJobSequenceNo;
    }
SourceCode/Bond/Servo/CJobDataS.h
@@ -18,9 +18,9 @@
        void copy(CJobDataS* pScr);
        void update(CJobDataS* pScr);
        CJobDataB& getJobDataB(CJobDataB& jobDataB);
        int getCassetteSequenceNo();
        int getCassetteSequenceNo() const;
        void setCassetteSequenceNo(int no);
        int getJobSequenceNo();
        int getJobSequenceNo() const;
        void setJobSequenceNo(int no);
        std::string& getLotId();
        void setLotId(const char* pszId);
SourceCode/Bond/Servo/CLoadPort.cpp
@@ -44,7 +44,7 @@
    void CLoadPort::initPins()
    {
        // åŠ å…¥Pin初始化代码
        LOGI("<CLoadPort>initPins");
        LOGD("<CLoadPort>initPins");
        addPin(SERVO::PinType::INPUT, _T("In"));
        addPin(SERVO::PinType::OUTPUT, _T("Out"));
    }
@@ -488,6 +488,19 @@
        }
        return (m_nIndex + 1) * 1000 + m_nNextCassetteSequenceNo;
    }
    int CLoadPort::getPortCassetteSnSeed()
    {
        return m_nNextCassetteSequenceNo;
    }
    void CLoadPort::setPortCassetteSnSeed(int seed)
    {
        m_nNextCassetteSequenceNo = seed;
        if (m_nNextCassetteSequenceNo >= 1000) {
            m_nNextCassetteSequenceNo = 0;
        }
    }
    void CLoadPort::setIndex(unsigned int index)
@@ -983,7 +996,7 @@
                LOGI("<CLoadPort-%d>设置Port type成功.", m_nIndex);
            }
            else {
                LOGI("<CLoadPort-%d>设置Port type失败,code:%d", m_nIndex, code);
                LOGE("<CLoadPort-%d>设置Port type失败,code:%d", m_nIndex, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1013,7 +1026,7 @@
                LOGI("<CLoadPort-%d>%s Port成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
            }
            else {
                LOGI("<CLoadPort-%d>%s  Port失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
                LOGE("<CLoadPort-%d>%s  Port失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1042,7 +1055,7 @@
                LOGI("<CLoadPort-%d>设置Port mode成功.", m_nIndex);
            }
            else {
                LOGI("<CLoadPort-%d>设置Port mode失败,code:%d", m_nIndex, code);
                LOGE("<CLoadPort-%d>设置Port mode失败,code:%d", m_nIndex, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1071,7 +1084,7 @@
                LOGI("<CLoadPort-%d>设置Cassette Type成功.", m_nIndex);
            }
            else {
                LOGI("<CLoadPort-%d>设置Cassette Type失败,code:%d", m_nIndex, code);
                LOGE("<CLoadPort-%d>设置Cassette Type失败,code:%d", m_nIndex, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1100,7 +1113,7 @@
                LOGI("<CLoadPort-%d>设置Transfer mode成功.", m_nIndex + 1);
            }
            else {
                LOGI("<CLoadPort-%d>设置Transfer mode失败,code:%d", m_nIndex + 1, code);
                LOGE("<CLoadPort-%d>设置Transfer mode失败,code:%d", m_nIndex + 1, code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1129,7 +1142,7 @@
                LOGI("<CLoadPort-%d>%s Auto Change成功.", m_nIndex, bEnable ? _T("启用") : _T("禁用"));
            }
            else {
                LOGI("<CLoadPort-%d>%s  Auto Change失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
                LOGE("<CLoadPort-%d>%s  Auto Change失败,code:%d", m_nIndex, bEnable ? _T("启用") : _T("禁用"), code);
            }
            if (onWritedBlock != nullptr) {
                return onWritedBlock(code);
@@ -1209,7 +1222,7 @@
            CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
            pGlass->setOriginPort(m_nIndex, i);
            pGlass->addPath(m_nID, 0);
            pGlass->addPath(m_nID, 0, i + 1);
            pGlass->processEnd(m_nID, 0);
            pGlass->setID(szBuffer);
            pGlass->setType(type);
@@ -1249,7 +1262,7 @@
            CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
            pGlass->setOriginPort(m_nIndex, i);
            pGlass->setScheduledForProcessing(i % 2 == 1);
            pGlass->addPath(m_nID, 0);
            pGlass->addPath(m_nID, 0, i + 1);
            pGlass->processEnd(m_nID, 0);
            pGlass->setID(szBuffer);
            pGlass->setType(m_cassetteType);
@@ -1289,7 +1302,7 @@
            CGlass* pGlass = theApp.m_model.m_glassPool.allocaGlass();
            pGlass->setOriginPort(m_nIndex, nSlotIndex);
            pGlass->addPath(m_nID, 0);
            pGlass->addPath(m_nID, 0, slot.nSlotID);
            pGlass->processEnd(m_nID, 0);
            pGlass->setID(szBuffer);
            pGlass->setType(static_cast<SERVO::MaterialsType>(config.nMaterialType));
SourceCode/Bond/Servo/CLoadPort.h
@@ -42,6 +42,8 @@
    public:
        short getNextCassetteSequenceNo();
        int getPortCassetteSnSeed();
        void setPortCassetteSnSeed(int seed);
        void setIndex(unsigned int index);
        unsigned int getIndex();
        BOOL isEnable();
SourceCode/Bond/Servo/CMaster.cpp
@@ -53,7 +53,7 @@
        m_ullRunTime = 0;
        m_state = MASTERSTATE::READY;
        m_pActiveRobotTask = nullptr;
        m_nLastError = 0;
        m_nLastError = ER_CODE_NOERROR;
        m_isCompareMapsBeforeProceeding = FALSE;
        m_bJobMode = FALSE;
        m_bEnableEventReport = true;
@@ -131,7 +131,7 @@
            BoardVersion version{};
            int nRet = m_cclink.GetBoardVersion(version);
            if (nRet == 0) {
                LOGI("版本信息:%s.", version.toString().c_str());
                LOGD("版本信息:%s.", version.toString().c_str());
            }
            else {
                LOGE("获取CC-Link版本信息失败.");
@@ -140,7 +140,7 @@
            BoardStatus status;
            nRet = m_cclink.GetBoardStatus(status);
            if (nRet == 0) {
                LOGI("状态:%s.", status.toString().c_str());
                LOGD("状态:%s.", status.toString().c_str());
            }
            else {
                LOGE("获取CC-Link状态失败.");
@@ -307,7 +307,7 @@
        return 0;
    }
    int CMaster::stop()
    int CMaster::stop(int nErCode/* = ER_CODE_NOERROR*/)
    {
        // è¿è¡Œæ—¶é—´ä¸ºç´¯åŠ ç»“æžœï¼Œæœ¬æ¬¡åœæ­¢æ—¶åˆ·æ–°ï¼›
        lock();
@@ -321,6 +321,7 @@
        // æ›´æ–°çŠ¶æ€
        m_nLastError = nErCode;
        setState(MASTERSTATE::STOPPING);
@@ -422,7 +423,8 @@
                        TRACE("a0001\n", writeCode, retCode);
                    });
                if (nRet != 0) {
                    LOGI("<Master>EFEM切换Start状态失败");
                    LOGE("<Master>EFEM切换Start状态失败");
                    m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
                    m_strLastError = "EFEM切换Start状态失败.";
                    goto WAIT;
                }
@@ -435,7 +437,8 @@
                        TRACE("a0002\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>Bonder1切换Start状态失败");
                    LOGE("<Master>Bonder1切换Start状态失败");
                    m_nLastError = ER_CODE_BONDER_OPERATION_MODE_FAIL;
                    m_strLastError = "Bonder1切换Start状态失败.";
                    goto WAIT;
                }
@@ -448,7 +451,8 @@
                        TRACE("a0003\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>Bonder2切换Start状态失败");
                    LOGE("<Master>Bonder2切换Start状态失败");
                    m_nLastError = ER_CODE_BONDER_OPERATION_MODE_FAIL;
                    m_strLastError = "Bonder2切换Start状态失败.";
                    goto WAIT;
                }
@@ -461,7 +465,8 @@
                        TRACE("a0004\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>BakeCooling切换Start状态失败");
                    LOGE("<Master>BakeCooling切换Start状态失败");
                    m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
                    m_strLastError = "BakeCooling切换Start状态失败.";
                    goto WAIT;
                }
@@ -474,7 +479,8 @@
                        TRACE("a0005\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>VacuumBake切换Start状态失败");
                    LOGE("<Master>VacuumBake切换Start状态失败");
                    m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
                    m_strLastError = "VacuumBake切换Start状态失败.";
                    goto WAIT;
                }
@@ -487,7 +493,8 @@
                        TRACE("a0006\n");
                    });
                if (nRet != 0) {
                    LOGI("<Master>Measurement切换Start状态失败");
                    LOGE("<Master>Measurement切换Start状态失败");
                    m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
                    m_strLastError = "Measurement切换Start状态失败.";
                    goto WAIT;
                }
@@ -501,7 +508,7 @@
                for (int i = 0; i < 6; i++) {
                    if (!bIomcOk[i]) {
                        bIomcOk[6] = FALSE;
                        LOGI("<Master>%s切换Start状态失败", pEq[i]->getName().c_str());
                        LOGE("<Master>%s切换Start状态失败", pEq[i]->getName().c_str());
                    }
                }
                
@@ -545,7 +552,8 @@
                            TRACE("s000%d: ret=%d\n", i + 1, retCode);
                        });
                    if (nRet != 0) {
                        LOGI("<Master>%s切换Stop状态发送失败", pEq[i]->getName().c_str());
                        LOGE("<Master>%s切换Stop状态发送失败", pEq[i]->getName().c_str());
                        m_nLastError = ER_CODE_OPERATION_MODE_FAIL;
                        m_strLastError = pEq[i]->getName() + "切换Stop状态发送失败.";
                        bIomcOk[i] = FALSE;
                        promises[i].set_value(); // é¿å… wait é˜»å¡ž
@@ -560,7 +568,7 @@
                for (int i = 0; i < 6; ++i) {
                    if (!bIomcOk[i]) {
                        bIomcOk[6] = FALSE;
                        LOGI("<Master>%s切换Stop状态失败", pEq[i]->getName().c_str());
                        LOGE("<Master>%s切换Stop状态失败", pEq[i]->getName().c_str());
                    }
                }
@@ -570,7 +578,11 @@
                }
                LOGI("<Master>所有设备成功切换到 Stop æ¨¡å¼");
                setState(MASTERSTATE::READY);
                if(m_nLastError == ER_CODE_NOERROR)
                    setState(MASTERSTATE::READY);
                else
                    setState(MASTERSTATE::ATHERERROR);
                continue;
            }
@@ -734,7 +746,7 @@
                if (!rmd.armState[0]) {
                    // m_nTestFlag = 1;
                    if (m_nTestFlag == 1) LOGI("createTransferTask 004df %d, %d", MaterialsType::G1, secondaryType);
                    if (m_nTestFlag == 1) LOGD("createTransferTask 004df %d, %d", MaterialsType::G1, secondaryType);
                    m_pActiveRobotTask = createTransferTask(pAligner, pVacuumBake, MaterialsType::G1, secondaryType);
                    CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
                    m_nTestFlag = 0;
@@ -774,6 +786,8 @@
                                continue;
                            }
                            pGlass->queue();
                            pGlass->start();
                            pEFEM->setContext(m_pActiveRobotTask->getContext());
                            goto PORT_GET;
                        }
@@ -798,7 +812,7 @@
                CJState state = m_pControlJob->state();
                if (state == CJState::Completed || state == CJState::Aborted || state == CJState::Failed) {
                    // ConrolJpb已完成
                    LOGI("<Master>ControlJob已经完成或失败中断");
                    LOGE("<Master>ControlJob已经完成或失败中断");
                    unlock();
                    continue;
                }
@@ -835,7 +849,7 @@
                    }
                }
                if (m_inProcesJobs.empty()) {
                    LOGI("<Master>选择当前ProcessJob失败!");
                    LOGE("<Master>选择当前ProcessJob失败!");
                    unlock();
                    continue;
                }
@@ -887,7 +901,7 @@
                // Measurement -> LoadPort
                if (rmd.armState[0] || rmd.armState[1]) {
                    LOGI("Arm1 %s, Arm2 %s.", rmd.armState[0] ? _T("不可用") : _T("可用"),
                    LOGD("Arm1 %s, Arm2 %s.", rmd.armState[0] ? _T("不可用") : _T("可用"),
                        rmd.armState[1] ? _T("不可用") : _T("可用"));
                }
                for (int s = 0; s < 4; s++) {
@@ -1312,22 +1326,22 @@
            BOOL bOk = FALSE;
            lock();
            if (m_pActiveRobotTask != nullptr) {
                LOGI("<CMaster>onPreFethedOutJob 0001.");
                LOGD("<CMaster>onPreFethedOutJob 0001.");
                if (m_pActiveRobotTask->getSrcPosition() == p->getID()) {
                    LOGI("<CMaster>onPreFethedOutJob 0002.");
                    LOGD("<CMaster>onPreFethedOutJob 0002.");
                    CGlass* pGlass = p->getGlassFromSlot(m_pActiveRobotTask->getSrcSlot());
                    if (pGlass != nullptr) {
                        LOGI("<CMaster>onPreFethedOutJob 0003.");
                        LOGD("<CMaster>onPreFethedOutJob 0003.");
                        CJobDataS* pJobDataS = pGlass->getJobDataS();
                        if (pJobDataS != nullptr
                            && pJobDataS->getCassetteSequenceNo() == pJobDataB->getCassetteSequenceNo()
                            && pJobDataS->getJobSequenceNo() == pJobDataB->getJobSequenceNo()) {
                            bOk = TRUE;
                            LOGI("<CMaster>onPreFethedOutJob, å·²æ ¡éªŒæ•°æ®ä¸€è‡´æ€§.");
                            LOGD("<CMaster>onPreFethedOutJob, å·²æ ¡éªŒæ•°æ®ä¸€è‡´æ€§.");
                        }
                        LOGI("<CMaster>onPreFethedOutJob 0004.");
                        LOGD("<CMaster>onPreFethedOutJob 0004.");
                        if (pJobDataS != nullptr) {
                            LOGI("<CMaster>onPreFethedOutJob 0005. %d,%d,%d,%d",
                            LOGD("<CMaster>onPreFethedOutJob 0005. %d,%d,%d,%d",
                                pJobDataS->getCassetteSequenceNo(),
                                pJobDataB->getCassetteSequenceNo(),
                                pJobDataS->getJobSequenceNo(),
@@ -1449,6 +1463,8 @@
                    if (m_pActiveRobotTask->getSrcPosition() == EQ_ID_MEASUREMENT) {
                        CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext();
                        pGlass->complete();
                        CGlass* pBuddy = pGlass->getBuddy();
                        if (pBuddy != nullptr) pBuddy->complete();
                        this->saveState();
                        bool bMoved = glassFromInPorcessToComplete(pGlass);
                        if (bMoved) {
@@ -1570,7 +1586,26 @@
                }
                strOut.append(szBuffer);
            }
            LOGI("<CMaster-%s>SVDataReport:%s", ((CEquipment*)pEquipment)->getName().c_str(), strOut.c_str());
            LOGD("<CMaster-%s>SVDataReport:%s", ((CEquipment*)pEquipment)->getName().c_str(), strOut.c_str());
        };
        listener.onPanelDataReport = [&](void* pEquipment, void* pContext) {
            LOGD("<CMaster-%s>onPanelDataReport", ((CEquipment*)pEquipment)->getName().c_str());
            CEquipment* pEq = (CEquipment*)pEquipment;
            CGlass* pGlass = (CGlass*)pContext;
            // å¦‚æžœAOI检测失败,要停机
            if (pEq->getID() == EQ_ID_MEASUREMENT) {
                LOGD("<CMaster-%s>onPanelDataReport 01", ((CEquipment*)pEquipment)->getName().c_str());
                if (pGlass->getAOIInspResult() == InspResult::Fail) {
                    LOGD("<CMaster-%s>onPanelDataReport 02", ((CEquipment*)pEquipment)->getName().c_str());
                    if (stop() == 0) {
                        m_nLastError = ER_CODE_AOI_NG;
                        m_strLastError = "AOI检测未通过.";
                    }
                }
            }
        };
        pEquipment->setListener(listener);
        pEquipment->setCcLink(&m_cclink);
@@ -1832,6 +1867,8 @@
            if (!m_inProcesGlasses.empty()) {
                CGlass* pGlass = m_inProcesGlasses.front();
                pGlass->complete();
                CGlass* pBuddy = pGlass->getBuddy();
                if (pBuddy != nullptr) pBuddy->complete();
                glassFromInPorcessToComplete(pGlass);
                this->saveState();
@@ -2038,12 +2075,12 @@
        pSrcEq->m_nTestFlag = m_nTestFlag;
        pTarSlot = pTarEq->getAvailableSlotForGlass(primaryType);
        pSrcSlot = pSrcEq->getProcessedSlot(primaryType, bJobMode);
        if (m_nTestFlag == 1) LOGI("createTransferTask 003 %x, %x", pTarSlot, pSrcSlot);
        if (m_nTestFlag == 1) LOGD("createTransferTask 003 %x, %x", pTarSlot, pSrcSlot);
        if (pSrcSlot == nullptr || nullptr == pTarSlot && secondaryType != SERVO::MaterialsType::G0) {
            pTarSlot = pTarEq->getAvailableSlotForGlass(secondaryType);
            pSrcSlot = pSrcEq->getProcessedSlot(secondaryType, bJobMode);
        }
        if (m_nTestFlag == 1) LOGI("createTransferTask 004 %x, %x", pTarSlot, pSrcSlot);
        if (m_nTestFlag == 1) LOGD("createTransferTask 004 %x, %x", pTarSlot, pSrcSlot);
        if (pSrcSlot != nullptr && nullptr != pTarSlot) {
            pTask = new CRobotTask();
@@ -2276,6 +2313,22 @@
        int eqid[] = { EQ_ID_LOADPORT1, EQ_ID_LOADPORT2, EQ_ID_LOADPORT3, EQ_ID_LOADPORT4 };
        CLoadPort* pPort = (CLoadPort*)getEquipment(eqid[index]);
        pPort->localEanblePort(bEnable);
    }
    int CMaster::getPortCassetteSnSeed(int port)
    {
        ASSERT(1 <= port && port <= 4);
        int eqid[] = { EQ_ID_LOADPORT1, EQ_ID_LOADPORT2, EQ_ID_LOADPORT3, EQ_ID_LOADPORT4 };
        CLoadPort* pPort = (CLoadPort*)getEquipment(eqid[port - 1]);
        return pPort->getPortCassetteSnSeed();
    }
    void CMaster::setPortCassetteSnSeed(int port, int seed)
    {
        ASSERT(1 <= port && port <= 4);
        int eqid[] = { EQ_ID_LOADPORT1, EQ_ID_LOADPORT2, EQ_ID_LOADPORT3, EQ_ID_LOADPORT4 };
        CLoadPort* pPort = (CLoadPort*)getEquipment(eqid[port - 1]);
        return pPort->setPortCassetteSnSeed(seed);
    }
    void CMaster::setCompareMapsBeforeProceeding(BOOL bCompare)
@@ -2698,4 +2751,35 @@
        return nullptr;
    }
    int CMaster::getWipGlasses(std::vector<CGlass*>& glasses)
    {
        for (auto eq : m_listEquipment) {
            auto p = dynamic_cast<CLoadPort*>(eq);
            if (p == nullptr) {
                eq->getAllGlass(glasses);
            }
        }
        return (int)glasses.size();
    }
    int CMaster::getLastError()
    {
        return m_nLastError;
    }
    std::string& CMaster::getLastErrorText()
    {
        return m_strLastError;
    }
    void CMaster::test()
    {
        if (stop() == 0) {
            m_nLastError = ER_CODE_AOI_NG;
            m_strLastError = "AOI检测未通过.";
        }
    }
}
SourceCode/Bond/Servo/CMaster.h
@@ -33,6 +33,10 @@
#define CTStep_begin                    CTStep_LoadPort_Aligner
#define CTStep_end                      CTStep_Measurement_LoadPort
#define ER_CODE_NOERROR                0
#define ER_CODE_OPERATION_MODE_FAIL    -1
#define ER_CODE_AOI_NG                 -2
namespace SERVO {
    enum class MASTERSTATE {
        READY = 0,
@@ -41,7 +45,8 @@
        RUNNING_CONTINUOUS_TRANSFER,
        RUNNING_BATCH,
        STOPPING,
        MSERROR
        MSERROR,
        ATHERERROR
    };
    typedef std::function<void(void* pMaster, MASTERSTATE state)> ONMASTERSTATECHANGED;
@@ -88,7 +93,7 @@
        int start();
        int startContinuousTransfer();
        int startBatch();
        int stop();
        int stop(int nErCode = ER_CODE_NOERROR);
        void clearError();
        ULONGLONG getRunTime();
        MASTERSTATE getState();
@@ -124,6 +129,10 @@
        CLoadPort* getPortWithCarrierId(const std::string& carrierId) const;
        bool saveState() const;
        bool loadState(const std::string& path);
        int getWipGlasses(std::vector<CGlass*>& glasses);
        void test();
        int getPortCassetteSnSeed(int port);
        void setPortCassetteSnSeed(int port, int seed);
    private:
        inline void lock() { EnterCriticalSection(&m_criticalSection); }
@@ -164,6 +173,10 @@
        bool ceidDefined(uint32_t ceid) const override;
    public:
        int getLastError();
        std::string& getLastErrorText();
    public:
        // æ–°å¢žå‡½æ•°
        CProcessJob* acquireNextProcessJob();
        CGlass* acquireNextGlass();
SourceCode/Bond/Servo/CMeasurement.cpp
@@ -455,6 +455,16 @@
        params.push_back(CParam("检测速度", "", this->getName().c_str(), v * 0.001));
        i += 4;
        // 3.检测速度
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("检测速度", "", this->getName().c_str(), v * 0.001));
        i += 4;
        // 4.检测2数据
        v = (pszData[i] & 0xff) | (pszData[i + 1] & 0xff) << 8 | (pszData[i + 2] & 0xff) << 16 | (pszData[i + 3] & 0xff) << 24;
        params.push_back(CParam("检测速度", "", this->getName().c_str(), v * 0.001));
        i += 4;
        return (int)params.size();
    }
SourceCode/Bond/Servo/CPageCassetteCtrlCmd.cpp
@@ -119,7 +119,7 @@
                LOGI("sendCassetteCtrlCmd æˆåŠŸ.");
            }
            else {
                LOGI("sendCassetteCtrlCmd å¤±è´¥.");
                LOGE("sendCassetteCtrlCmd å¤±è´¥.");
            }
            return 0;
SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -6,568 +6,1392 @@
#include "CPageGlassList.h"
#include "afxdialogex.h"
#include "GlassJson.h"
#include "CServoUtilsTool.h"
#include "ToolUnits.h"
#include <optional>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
#define PAGE_SIZE                       50
#define PAGE_BACKGROUND_COLOR           RGB(252, 252, 255)
// WIP é¢œè‰²ï¼šçˆ¶ï¼ˆæ ¹/无 buddy)= åŸºç¡€ç»¿ï¼›å­ï¼ˆbuddy)= æ›´æµ…
static const COLORREF kWipText = RGB(0, 0, 0);
static const COLORREF kWipParentBk = RGB(201, 228, 180); // åŸºç¡€ç»¿
static const COLORREF kWipChildBk = RGB(221, 241, 208); // æ›´æµ…一点
// ===== æ”¾åœ¨ CPageGlassList.cpp é¡¶éƒ¨çš„匿名工具(文件内静态) =====
// æŠŠå½“前“已展开”的父行,用它们的 classId(第4列文本)做 key è®°å½•下来
static std::unordered_set<std::string> SnapshotExpandedKeys(CExpandableListCtrl& lv) {
    std::unordered_set<std::string> keys;
    for (int i = 0; i < lv.GetItemCount(); ++i) {
        auto* n = lv.GetNodeByVisibleIndex(i);
        if (!n || n->children.empty() || !n->expanded) continue;
        if ((int)n->cols.size() > 4) {
#ifdef _UNICODE
            keys.insert(CT2A(n->cols[4]));
#else
            keys.insert(n->cols[4].GetString());
#endif
        }
    }
    return keys;
}
// æ ¹æ®å¿«ç…§æ¢å¤å±•开状态(在你新建完每个“父节点”时调用一次)
static void MaybeRestoreExpandByKey(CExpandableListCtrl::Node* n,
    const std::unordered_set<std::string>& keys) {
    if (!n || (int)n->cols.size() <= 4) return;
#ifdef _UNICODE
    std::string k = CT2A(n->cols[4]);
#else
    std::string k = n->cols[4].GetString();
#endif
    if (keys.find(k) != keys.end())
        n->expanded = true;
}
static void CaptureUiState(CExpandableListCtrl& lv,
    std::vector<CExpandableListCtrl::Node*>& outSel,
    CExpandableListCtrl::Node*& outTopNode)
{
    outSel.clear(); outTopNode = nullptr;
    const int top = lv.GetTopIndex();
    if (top >= 0 && top < lv.GetItemCount())
        outTopNode = lv.GetNodeByVisibleIndex(top);
    for (int i = 0; i < lv.GetItemCount(); ++i) {
        if ((lv.GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) != 0) {
            auto* n = lv.GetNodeByVisibleIndex(i);
            if (n) outSel.push_back(n);
        }
    }
}
static void RestoreUiState(CExpandableListCtrl& lv,
    const std::vector<CExpandableListCtrl::Node*>& sel,
    CExpandableListCtrl::Node* topNode)
{
    // æ¸…掉现有选择
    for (int i = 0; i < lv.GetItemCount(); ++i)
        lv.SetItemState(i, 0, LVIS_SELECTED);
    // æ¢å¤é€‰æ‹©
    for (auto* n : sel) {
        for (int i = 0; i < lv.GetItemCount(); ++i) {
            if (lv.GetNodeByVisibleIndex(i) == n) {
                lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
                break;
            }
        }
    }
    // å°½é‡æŠŠä¹‹å‰çš„顶行滚回可见
    if (topNode) {
        for (int i = 0; i < lv.GetItemCount(); ++i) {
            if (lv.GetNodeByVisibleIndex(i) == topNode) {
                lv.EnsureVisible(i, FALSE);
                break;
            }
        }
    }
}
// ====== å¼€å…³ï¼š1=启用假数据(只替换 DB æŸ¥è¯¢ï¼‰ï¼›0=用真实 DB ======
#define USE_FAKE_DB_DEMO 0
#if USE_FAKE_DB_DEMO
#include <ctime>
#include <atlconv.h>   // CStringA
#include <initializer_list>
#include <string>
#include <vector>
// ---- æ¨¡æ‹Ÿè®°å½•/分页结构(字段与你现有代码一致)----
struct FakeDbRecord {
    int         id;
    int         cassetteSeqNo;
    int         jobSeqNo;
    std::string classId;
    int         materialType;
    int         state;
    std::string tStart;
    std::string tEnd;
    std::string buddyId;
    int         aoiResult;
    std::string path;
    std::string params;
};
struct FakeDbPage { std::vector<FakeDbRecord> items; };
// ---- CString -> std::string(ANSI,本地代码页;仅用于测试模拟)----
static std::string toAnsi(const CString& s) {
#ifdef _UNICODE
    CStringA a(s);
    return std::string(a.GetString());
#else
    return std::string(s.GetString());
#endif
}
// ---- å®‰å…¨æ‹¼æŽ¥å·¥å…·ï¼šä¸ä½¿ç”¨è¿ç®—符 +(避免重载/转换问题)----
static std::string sjoin(std::initializer_list<std::string> parts) {
    std::string out;
    size_t total = 0; for (const auto& p : parts) total += p.size();
    out.reserve(total);
    for (const auto& p : parts) out.append(p);
    return out;
}
// ---- æ—¶é—´æ ¼å¼å·¥å…·ï¼šnow + days/minutes åç§» ----
static std::string _fmt_time(int daysOff, int minutesOff) {
    using namespace std::chrono;
    auto now = system_clock::now() + hours(24 * daysOff) + minutes(minutesOff);
    std::time_t tt = system_clock::to_time_t(now);
    std::tm tm{};
#ifdef _WIN32
    localtime_s(&tm, &tt);
#else
    tm = *std::localtime(&tt);
#endif
    char buf[32];
    std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    return std::string(buf);
}
// ---- é€ å…¨é‡å‡æ•°æ®ï¼ˆå«åŒé¡µé…å¯¹/跨页配对/单条)----
static void _make_all_fake(std::vector<FakeDbRecord>& outAll) {
    outAll.clear();
    int id = 1000;
    // ===== å…ˆé€ å‡ æ¡â€œæ²¡æœ‰ Buddy”的记录(保证出现在第 1 é¡µå¼€å¤´ï¼‰=====
    for (int n = 1; n <= 4; ++n) {
        CString cid; cid.Format(_T("NB%03d"), n);
        outAll.push_back(FakeDbRecord{
            id++, 700 + n, 800 + n, toAnsi(cid),
            n % 3, n % 5,
            _fmt_time(0, -n * 5),           // å¼€å§‹æ—¶é—´ç¨æ—©ä¸€ç‚¹
            _fmt_time(0, -n * 5 + 1),
            std::string(),                // buddyId ä¸ºç©º
            n % 4,
            sjoin({ "path/", toAnsi(cid) }),
            sjoin({ "{\"noBuddy\":", std::to_string(n), "}" })
            });
    }
    // ===== é€ é…å¯¹æ•°æ®çš„工具 =====
    auto mkPair = [&](int k, bool crossPage) {
        // äº’为 buddy çš„两条
        CString a; a.Format(_T("G%04dA"), k);
        CString b; b.Format(_T("G%04dB"), k);
        FakeDbRecord A{
            id++, 100 + k, 200 + k, toAnsi(a),
            k % 3, k % 5, _fmt_time(0, k * 3), _fmt_time(0, k * 3 + 2),
            toAnsi(b), k % 4,
            sjoin({ "path/", toAnsi(a) }),
            sjoin({ "{\"k\":\"", toAnsi(a), "\"}" })
        };
        FakeDbRecord B{
            id++, 110 + k, 210 + k, toAnsi(b),
            (k + 1) % 3, (k + 2) % 5, _fmt_time(0, k * 3 + 1), _fmt_time(0, k * 3 + 4),
            toAnsi(a), (k + 1) % 4,
            sjoin({ "path/", toAnsi(b) }),
            sjoin({ "{\"k\":\"", toAnsi(b), "\"}" })
        };
        if (crossPage) {
            // å…ˆæ”¾ A,再插 3 æ¡â€œå•条”把 B æŒ¤åˆ°åŽé¡µï¼Œæœ€åŽæ”¾ B
            outAll.push_back(A);
            for (int s = 0; s < 3; ++s) {
                CString sid; sid.Format(_T("S%04d_%d"), k, s);
                outAll.push_back(FakeDbRecord{
                    id++, 300 + k * 10 + s, 400 + k * 10 + s, toAnsi(sid),
                    (k + s) % 3, (k + s) % 5, _fmt_time(0, k * 2 + s), _fmt_time(0, k * 2 + s + 1),
                    std::string(), (k + s) % 4,
                    sjoin({ "path/", toAnsi(sid) }),
                    sjoin({ "{\"single\":", std::to_string(s), "}" })
                    });
            }
            outAll.push_back(B);
        }
        else {
            // åŒé¡µç´§æŒ¨ç€
            outAll.push_back(A);
            outAll.push_back(B);
        }
    };
    // ===== ç„¶åŽæŒ‰åŽŸé€»è¾‘è¿½åŠ ï¼šåŒé¡µé…å¯¹ / è·¨é¡µé…å¯¹ / å•条 =====
    // 6 ç»„同页配对(12 æ¡ï¼‰
    for (int k = 1; k <= 6; ++k) mkPair(k, false);
    // 4 ç»„跨页配对(每组中间插 3 æ¡â€œå•条”)
    for (int k = 101; k <= 104; ++k) mkPair(k, true);
    // è‹¥å¹²â€œå•条”
    for (int u = 201; u < 806; ++u) {
        CString cid; cid.Format(_T("U%04d"), u);
        outAll.push_back(FakeDbRecord{
            id++, 500 + u, 600 + u, toAnsi(cid),
            u % 3, u % 5, _fmt_time(0, u % 17), _fmt_time(0, (u % 17) + 1),
            std::string(), u % 4,
            sjoin({ "path/", toAnsi(cid) }),
            sjoin({ "{\"u\":", std::to_string(u), "}" })
            });
    }
}
#define PAGE_SIZE                        100
#define PAGE_BACKGROUND_COLOR            RGB(252, 252, 255)
// ---- åšåˆ†é¡µåˆ‡ç‰‡ï¼ˆå¯æŒ‰éœ€åŠ æ›´ä¸¥æ ¼çš„ filters)----
static FakeDbPage _make_page_fake(const GlassLogDb::Filters& /*f*/, int pageSize, int offset) {
    std::vector<FakeDbRecord> all;
    _make_all_fake(all);
    FakeDbPage page;
    int n = (int)all.size();
    int beg = min(max(0, offset), n);
    int end = min(beg + max(0, pageSize), n);
    page.items.insert(page.items.end(), all.begin() + beg, all.begin() + end);
    return page;
}
static int _fake_total_count() {
    std::vector<FakeDbRecord> all;
    _make_all_fake(all);
    return (int)all.size();
}
#endif // USE_FAKE_DB_DEMO
// åˆ¤æ–­æŸ parent ä¸‹æ˜¯å¦å·²å­˜åœ¨ classId == cid çš„子节点(忽略大小写)
static bool NodeHasChildWithClassId(CExpandableListCtrl::Node* parent, const CString& cid)
{
    if (!parent) return false;
    for (auto& ch : parent->children) {
        if (ch && ch->cols.size() > 4) {
            if (ch->cols[4].CompareNoCase(cid) == 0)
                return true;
        }
    }
    return false;
}
// æŠŠ cols å†™å›žåˆ°æŸä¸€è¡Œï¼ˆä»Žç¬¬1列开始;第0列我们没用)
static void ApplyColsToRow(CListCtrl& lv, int row, const std::vector<CString>& cols) {
    int colCount = 0;
    if (auto* hdr = lv.GetHeaderCtrl()) colCount = hdr->GetItemCount();
    colCount = min(colCount, (int)cols.size());
    for (int c = 1; c < colCount; ++c) {
        lv.SetItemText(row, c, cols[c]);
    }
}
// é€‰ä¸­è¡Œçš„ ClassID å¿«ç…§ï¼ˆç”¨äºŽé‡å»ºåŽæ¢å¤ï¼‰
static std::unordered_set<std::string> SnapshotSelectedKeys(CListCtrl& lv) {
    std::unordered_set<std::string> keys;
    int n = lv.GetItemCount();
    for (int i = 0; i < n; ++i) {
        if ((lv.GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) == 0) continue;
        CString cls = lv.GetItemText(i, 4);
#ifdef _UNICODE
        keys.insert(CT2A(cls));
#else
        keys.insert(cls.GetString());
#endif
    }
    return keys;
}
// é¡¶è¡Œï¼ˆTopIndex)对应的 ClassID
static std::optional<std::string> SnapshotTopKey(CListCtrl& lv) {
    int top = lv.GetTopIndex();
    if (top < 0 || top >= lv.GetItemCount()) return std::nullopt;
    CString cls = lv.GetItemText(top, 4);
#ifdef _UNICODE
    return std::optional<std::string>(CT2A(cls));
#else
    return std::optional<std::string>(cls.GetString());
#endif
}
// ç”¨ ClassID é›†åˆæ¢å¤é€‰ä¸­
static void RestoreSelectionByKeys(CListCtrl& lv, const std::unordered_set<std::string>& keys) {
    int n = lv.GetItemCount();
    for (int i = 0; i < n; ++i) lv.SetItemState(i, 0, LVIS_SELECTED);
    for (int i = 0; i < n; ++i) {
        CString cls = lv.GetItemText(i, 4);
#ifdef _UNICODE
        if (keys.count(CT2A(cls))) lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
#else
        if (keys.count(cls.GetString())) lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
#endif
    }
}
// å°½é‡æŠŠæŸä¸ª ClassID æ»šå›žå¯è§
static void RestoreTopByKey(CListCtrl& lv, const std::optional<std::string>& key) {
    if (!key) return;
    int n = lv.GetItemCount();
    for (int i = 0; i < n; ++i) {
        CString cls = lv.GetItemText(i, 4);
#ifdef _UNICODE
        if (CT2A(cls) == *key) { lv.EnsureVisible(i, FALSE); break; }
#else
        if (cls.GetString() == *key) { lv.EnsureVisible(i, FALSE); break; }
#endif
    }
}
bool CopyUtf8ToClipboard(const std::string& utf8)
{
    // 1) UTF-8 -> UTF-16 é•¿åº¦ï¼ˆå«ç»“å°¾ '\0')
    int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
    if (wlen <= 0) return false;
    // 2) ä¸ºå‰ªè´´æ¿åˆ†é…å…¨å±€å¯ç§»åŠ¨å†…å­˜ï¼ˆå¿…é¡» GMEM_MOVEABLE)
    SIZE_T bytes = static_cast<SIZE_T>(wlen) * sizeof(wchar_t);
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, bytes);
    if (!hMem) return false;
    // 3) å¡«å…… UTF-16 æ–‡æœ¬
    wchar_t* wbuf = static_cast<wchar_t*>(GlobalLock(hMem));
    if (!wbuf) { GlobalFree(hMem); return false; }
    MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, wbuf, wlen);
    GlobalUnlock(hMem);
    // 4) æ‰“开剪贴板并设置数据(CF_UNICODETEXT)
    if (!OpenClipboard(nullptr)) { GlobalFree(hMem); return false; }
    if (!EmptyClipboard()) { CloseClipboard(); GlobalFree(hMem); return false; }
    // æˆåŠŸåŽï¼Œå†…å­˜æ‰€æœ‰æƒäº¤ç»™å‰ªè´´æ¿ï¼Œä¸èƒ½å† GlobalFree
    if (!SetClipboardData(CF_UNICODETEXT, hMem)) {
        CloseClipboard();
        GlobalFree(hMem);
        return false;
    }
    CloseClipboard();
    return true;
}
// CPageGlassList å¯¹è¯æ¡†
IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx)
CPageGlassList::CPageGlassList(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_PAGE_GLASS_LIST, pParent)
    : CDialogEx(IDD_PAGE_GLASS_LIST, pParent)
{
    m_crBkgnd = PAGE_BACKGROUND_COLOR;
    m_hbrBkgnd = nullptr;
    m_pObserver = nullptr;
    m_crBkgnd = PAGE_BACKGROUND_COLOR;
    m_hbrBkgnd = nullptr;
    m_pObserver = nullptr;
    m_strStatus = "";
    m_strKeyword = "";
    m_nCurPage = 0;
    m_nTotalPages = 0;
    m_strStatus = "";
    m_nCurPage = 1;
    m_nTotalPages = 1;
    memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
    memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
    m_szTimeStart[0] = '\0';
    m_szTimeEnd[0] = '\0';
    memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
    memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
    m_szTimeStart[0] = '\0';
    m_szTimeEnd[0] = '\0';
}
CPageGlassList::~CPageGlassList()
{
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
    }
    if (m_pObserver != nullptr) {
        m_pObserver->unsubscribe();
        m_pObserver = nullptr;
    }
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
        m_hbrBkgnd = nullptr;
    }
    if (m_pObserver != nullptr) {
        m_pObserver->unsubscribe();
        m_pObserver = nullptr;
    }
}
void CPageGlassList::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_DATETIMEPICKER_START, m_dateTimeStart);
    DDX_Control(pDX, IDC_DATETIMEPICKER_END, m_dateTimeEnd);
    DDX_Control(pDX, IDC_LIST_ALARM, m_listCtrl);
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_DATETIMEPICKER_START, m_dateTimeStart);
    DDX_Control(pDX, IDC_DATETIMEPICKER_END, m_dateTimeEnd);
    DDX_Control(pDX, IDC_LIST_ALARM, m_listCtrl);
}
BEGIN_MESSAGE_MAP(CPageGlassList, CDialogEx)
    ON_WM_CTLCOLOR()
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_WM_TIMER()
    ON_CBN_SELCHANGE(IDC_COMBO_DATETIME, &CPageGlassList::OnCbnSelchangeComboDatetime)
    ON_CBN_SELCHANGE(IDC_COMBO_STATUS_FILTER, &CPageGlassList::OnCbnSelchangeComboStatusFilter)
    ON_BN_CLICKED(IDC_BUTTON_SEARCH, &CPageGlassList::OnBnClickedButtonSearch)
    ON_BN_CLICKED(IDC_BUTTON_EXPORT, &CPageGlassList::OnBnClickedButtonExport)
    ON_BN_CLICKED(IDC_BUTTON_PREV_PAGE, &CPageGlassList::OnBnClickedButtonPrevPage)
    ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CPageGlassList::OnBnClickedButtonNextPage)
    ON_WM_CTLCOLOR()
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_WM_TIMER()
    ON_CBN_SELCHANGE(IDC_COMBO_DATETIME, &CPageGlassList::OnCbnSelchangeComboDatetime)
    ON_CBN_SELCHANGE(IDC_COMBO_STATUS_FILTER, &CPageGlassList::OnCbnSelchangeComboStatusFilter)
    ON_BN_CLICKED(IDC_BUTTON_SEARCH, &CPageGlassList::OnBnClickedButtonSearch)
    ON_BN_CLICKED(IDC_BUTTON_EXPORT, &CPageGlassList::OnBnClickedButtonExport)
    ON_BN_CLICKED(IDC_BUTTON_PREV_PAGE, &CPageGlassList::OnBnClickedButtonPrevPage)
    ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CPageGlassList::OnBnClickedButtonNextPage)
    ON_NOTIFY(ELCN_SHOWFULLTEXT, IDC_LIST_ALARM, &CPageGlassList::OnShowFullText)
END_MESSAGE_MAP()
// ===== ç§æœ‰å°å·¥å…· =====
static int GetColumnCount(CListCtrl& lv)
{
    CHeaderCtrl* pHdr = lv.GetHeaderCtrl();
    return pHdr ? pHdr->GetItemCount() : 0;
}
// CPageGlassList æ¶ˆæ¯å¤„理程序
// ===== CPageGlassList æ¶ˆæ¯å¤„理程序 =====
void CPageGlassList::InitRxWindow()
{
    /* code */
    // è®¢é˜…数据
    IRxWindows* pRxWindows = RX_GetRxWindows();
    pRxWindows->enableLog(5);
    if (m_pObserver == NULL) {
        m_pObserver = pRxWindows->allocObserver([&](IAny* pAny) -> void {
            // onNext
            pAny->addRef();
            int code = pAny->getCode();
    // è®¢é˜…数据
    IRxWindows* pRxWindows = RX_GetRxWindows();
    pRxWindows->enableLog(5);
    if (m_pObserver == NULL) {
        m_pObserver = pRxWindows->allocObserver([&](IAny* pAny) -> void {
            // onNext
            pAny->addRef();
            int code = pAny->getCode();
            if (RX_CODE_EQ_ROBOT_TASK == code) {
                UpdatePageData();
            }
            if (RX_CODE_EQ_ROBOT_TASK == code) {
                UpdateWipData();   // åªæ›´æ–°ï¼Œä¸é‡å»ºï¼Œä¸æ”¹å˜å±•å¼€/选择
            }
            pAny->release();
            }, [&]() -> void {
                // onComplete
            }, [&](IThrowable* pThrowable) -> void {
                // onErrorm
                pThrowable->printf();
            });
            pAny->release();
            }, [&]() -> void {
                // onComplete
            }, [&](IThrowable* pThrowable) -> void {
                // onError
                pThrowable->printf();
            });
        theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())->subscribe(m_pObserver);
    }
        theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())->subscribe(m_pObserver);
    }
}
void CPageGlassList::Resize()
{
    CRect rcClient;
    GetClientRect(&rcClient);
    CRect rcClient;
    GetClientRect(&rcClient);
    // ===== å¸¸é‡å®šä¹‰ =====
    const int nLeft = 12;
    const int nRight = 12;
    const int nTop = 58;
    const int nButtonHeight = 28;
    const int nButtonMarginBottom = 12;
    const int nSpacing = 8;
    const int nButtonWidth = 80;
    const int nLabelWidth = 100;
    // ===== å¸¸é‡å®šä¹‰ =====
    const int nLeft = 12;
    const int nRight = 12;
    const int nTop = 58;
    const int nButtonHeight = 28;
    const int nButtonMarginBottom = 12;
    const int nSpacing = 8;
    const int nButtonWidth = 80;
    const int nLabelWidth = 100;
    // ===== åˆ†é¡µæŽ§ä»¶å¸ƒå±€ =====
    int yBottom = rcClient.bottom - nButtonMarginBottom - nButtonHeight;
    int xRight = rcClient.Width() - nRight;
    // ===== åˆ†é¡µæŽ§ä»¶å¸ƒå±€ =====
    int yBottom = rcClient.bottom - nButtonMarginBottom - nButtonHeight;
    int xRight = rcClient.Width() - nRight;
    CWnd* pBtnNext = GetDlgItem(IDC_BUTTON_NEXT_PAGE);
    CWnd* pBtnPrev = GetDlgItem(IDC_BUTTON_PREV_PAGE);
    CWnd* pLabelPage = GetDlgItem(IDC_LABEL_PAGE_NUMBER);
    CWnd* pBtnNext = GetDlgItem(IDC_BUTTON_NEXT_PAGE);
    CWnd* pBtnPrev = GetDlgItem(IDC_BUTTON_PREV_PAGE);
    CWnd* pLabelPage = GetDlgItem(IDC_LABEL_PAGE_NUMBER);
    if (pBtnNext && pBtnPrev && pLabelPage) {
        // èŽ·å–åˆ†é¡µæ–‡æœ¬å®½åº¦ä¼°ç®—
        //CString strLabel;
        //GetDlgItemText(IDC_LABEL_PAGE_NUMBER, strLabel);
        //if (strLabel.IsEmpty()) {
        //    strLabel = _T("第 1 / 1 é¡µ");
        //}
        //int nCharWidth = 8;
        //int nLabelWidth = strLabel.GetLength() * nCharWidth + 20;
    if (pBtnNext && pBtnPrev && pLabelPage) {
        pBtnNext->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight);
        xRight -= nButtonWidth + nSpacing;
        // è®¾ç½®æŒ‰é’®å’Œæ ‡ç­¾ä½ç½®
        pBtnNext->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight);
        xRight -= nButtonWidth + nSpacing;
        pLabelPage->MoveWindow(xRight - nLabelWidth, yBottom, nLabelWidth, nButtonHeight);
        xRight -= nLabelWidth + nSpacing;
        pLabelPage->MoveWindow(xRight - nLabelWidth, yBottom, nLabelWidth, nButtonHeight);
        xRight -= nLabelWidth + nSpacing;
        pBtnPrev->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight);
    }
        pBtnPrev->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight);
    }
    // ===== è¡¨æ ¼åŒºåŸŸå¸ƒå±€ =====
    if (nullptr != m_listCtrl.m_hWnd) {
        int listHeight = yBottom - nTop - nSpacing;
        m_listCtrl.MoveWindow(nLeft, nTop, rcClient.Width() - nLeft - nRight, listHeight);
    }
    // ===== è¡¨æ ¼åŒºåŸŸå¸ƒå±€ =====
    if (nullptr != m_listCtrl.m_hWnd) {
        int listHeight = yBottom - nTop - nSpacing;
        m_listCtrl.MoveWindow(nLeft, nTop, rcClient.Width() - nLeft - nRight, listHeight);
    }
}
void CPageGlassList::InitStatusCombo()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_STATUS_FILTER);
    if (nullptr != pComboBox) {
        pComboBox->ResetContent();
        pComboBox->AddString(_T("全部"));
        pComboBox->AddString(_T("Ready"));
        pComboBox->AddString(_T("Running"));
        pComboBox->AddString(_T("Error"));
        pComboBox->AddString(_T("Abort"));
        pComboBox->AddString(_T("Completed"));
        pComboBox->SetCurSel(0);
    }
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_STATUS_FILTER);
    if (nullptr != pComboBox) {
        pComboBox->ResetContent();
        pComboBox->AddString(_T("全部"));
        pComboBox->AddString(_T("Ready"));
        pComboBox->AddString(_T("Running"));
        pComboBox->AddString(_T("Error"));
        pComboBox->AddString(_T("Abort"));
        pComboBox->AddString(_T("Completed"));
        pComboBox->SetCurSel(0);
    }
}
void CPageGlassList::InitTimeRangeCombo()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    if (nullptr != pComboBox) {
        pComboBox->ResetContent();
        pComboBox->AddString(_T("不限"));
        pComboBox->AddString(_T("今天"));
        pComboBox->AddString(_T("七天内"));
        pComboBox->AddString(_T("本月"));
        pComboBox->AddString(_T("今年"));
        pComboBox->AddString(_T("自定义"));
        pComboBox->SetCurSel(0);
    }
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    if (nullptr != pComboBox) {
        pComboBox->ResetContent();
        pComboBox->AddString(_T("不限"));
        pComboBox->AddString(_T("今天"));
        pComboBox->AddString(_T("七天内"));
        pComboBox->AddString(_T("本月"));
        pComboBox->AddString(_T("今年"));
        pComboBox->AddString(_T("自定义"));
        pComboBox->SetCurSel(0);
    }
}
void CPageGlassList::InitDateTimeControls()
{
    if (m_dateTimeStart.m_hWnd == nullptr || m_dateTimeEnd.m_hWnd == nullptr) {
        return;
    }
    // ç¦ç”¨åˆå§‹çŠ¶æ€
    m_dateTimeStart.EnableWindow(FALSE);
    m_dateTimeEnd.EnableWindow(FALSE);
    // è®¾ç½®æ ¼å¼ï¼šæ˜¾ç¤ºæ—¥æœŸ + æ—¶é—´
    //m_dateTimeStart.SetFormat(_T("yyyy/MM/dd HH:mm:ss"));
    //m_dateTimeEnd.SetFormat(_T("yyyy/MM/dd HH:mm:ss"));
    // ä¿®æ”¹æ ·å¼ä»¥æ”¯æŒæ—¶é—´æ ¼å¼
    //DWORD dwStyleStart = m_dateTimeStart.GetStyle();
    //DWORD dwStyleEnd = m_dateTimeEnd.GetStyle();
    //m_dateTimeStart.ModifyStyle(0, DTS_TIMEFORMAT | DTS_UPDOWN);
    //m_dateTimeEnd.ModifyStyle(0, DTS_TIMEFORMAT);
    if (m_dateTimeStart.m_hWnd == nullptr || m_dateTimeEnd.m_hWnd == nullptr) {
        return;
    }
    // è‡ªå®šä¹‰èŒƒå›´æ—¶æ‰å¯ç¼–辑
    m_dateTimeStart.EnableWindow(FALSE);
    m_dateTimeEnd.EnableWindow(FALSE);
}
void CPageGlassList::LoadTransfers()
void CPageGlassList::LoadData()
{
    m_nCurPage = 1;
    UpdatePageData();
    m_nCurPage = 1;
    UpdatePageData();
}
void CPageGlassList::UpdatePageData()
{
    /*
    TransferData filter;
    filter.strStatus = m_strStatus;
    filter.strDescription = m_strKeyword;
    filter.strCreateTime = m_szTimeStart;
    filter.strEndTime = m_szTimeEnd;
    auto vecData = TransferManager::getInstance().getTransfers(filter, m_nCurPage, PAGE_SIZE);
    FillDataToListCtrl(vecData);
    m_rebuilding = true;
    int nTotalRecords = TransferManager::getInstance().getFilteredTransferCount(filter);
    m_nTotalPages = (nTotalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
    UpdatePageControls();
    */
    // æ”¾åœ¨ä»»ä½•清空/重建动作之前:记录展开的父节点 key(ClassID)
    auto expandedKeys = SnapshotExpandedKeys(m_listCtrl);
    // â€”— åŒä¿é™©ï¼šå…ˆæ¸…掉可见项,再清树结构 â€”—
    m_listCtrl.SetRedraw(FALSE);
    m_listCtrl.DeleteAllItems();
    m_listCtrl.SetRedraw(TRUE);
    // â€”— æ¸…空树(依赖 CExpandableListCtrl::ClearTree())——
    m_listCtrl.ClearTree();
    const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
    if (colCount <= 0) { m_rebuilding = false; return; }
    // ==================== 1) WIP:仅第 1 é¡µæž„建,且放在最顶部 ====================
    if (m_nCurPage == 1) {
        std::vector<SERVO::CGlass*> wipGlasses;
        theApp.m_model.m_master.getWipGlasses(wipGlasses);
        std::vector<SERVO::CGlass*> tempGlasses = wipGlasses; // å¾…释放
        auto glassHit = [&](SERVO::CGlass* g) -> bool {
            return g && GlassMatchesFilters(*g, m_filters);
        };
        std::unordered_set<SERVO::CGlass*> usedWip;
        for (auto* g : wipGlasses) {
            if (!glassHit(g) || usedWip.count(g)) continue;
            SERVO::CGlass* b = g->getBuddy();
            if (b) {
                // æŒ‰ä½ çš„约定:g æ˜¯çˆ¶ï¼Œbuddy æ˜¯å­
                SERVO::CGlass* parent = g;
                SERVO::CGlass* child = b;
                // parent
                std::vector<CString> pcols(colCount);
                pcols[1] = _T("");
                pcols[2] = std::to_string(parent->getCassetteSequenceNo()).c_str();
                pcols[3] = std::to_string(parent->getJobSequenceNo()).c_str();
                pcols[4] = parent->getID().c_str();
                pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(parent->getType()).c_str();
                pcols[6] = SERVO::CServoUtilsTool::getGlassStateText(parent->state()).c_str();
                pcols[7] = CToolUnits::TimePointToLocalString(parent->tStart()).c_str();
                pcols[8] = CToolUnits::TimePointToLocalString(parent->tEnd()).c_str();
                pcols[9] = parent->getBuddyId().c_str();
                pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)parent->getAOIInspResult()).c_str();
                pcols[11] = parent->getPathDescription().c_str();
                pcols[12] = parent->getParamsDescription().c_str();
                auto* nParent = m_listCtrl.InsertRoot(pcols);
                MaybeRestoreExpandByKey(nParent, expandedKeys);
                m_listCtrl.SetNodeColor(nParent, kWipText, kWipParentBk);   // çˆ¶ï¼šåŸºç¡€ç»¿
                // child
                std::vector<CString> ccols(colCount);
                ccols[1] = _T("");
                ccols[2] = std::to_string(child->getCassetteSequenceNo()).c_str();
                ccols[3] = std::to_string(child->getJobSequenceNo()).c_str();
                ccols[4] = child->getID().c_str();
                ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(child->getType()).c_str();
                ccols[6] = SERVO::CServoUtilsTool::getGlassStateText(child->state()).c_str();
                ccols[7] = CToolUnits::TimePointToLocalString(child->tStart()).c_str();
                ccols[8] = CToolUnits::TimePointToLocalString(child->tEnd()).c_str();
                ccols[9] = child->getBuddyId().c_str();
                ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)child->getAOIInspResult()).c_str();
                ccols[11] = child->getPathDescription().c_str();
                ccols[12] = child->getParamsDescription().c_str();
                auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
                m_listCtrl.SetNodeColor(nChild, kWipText, kWipChildBk);     // å­ï¼šæ›´æµ…
                usedWip.insert(parent);
                usedWip.insert(child);
            }
            else {
                std::vector<CString> cols(colCount);
                cols[1] = _T("");
                cols[2] = std::to_string(g->getCassetteSequenceNo()).c_str();
                cols[3] = std::to_string(g->getJobSequenceNo()).c_str();
                cols[4] = g->getID().c_str();
                cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str();
                cols[6] = SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str();
                cols[7] = CToolUnits::TimePointToLocalString(g->tStart()).c_str();
                cols[8] = CToolUnits::TimePointToLocalString(g->tEnd()).c_str();
                cols[9] = g->getBuddyId().c_str();
                cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str();
                cols[11] = g->getPathDescription().c_str();
                cols[12] = g->getParamsDescription().c_str();
                auto* n = m_listCtrl.InsertRoot(cols);
                m_listCtrl.SetNodeColor(n, kWipText, kWipParentBk);         // ä»ç”¨åŸºç¡€ç»¿
                usedWip.insert(g);
            }
        }
        for (auto* item : tempGlasses) item->release();
    }
    // ==================== 2) DB å½“前页(两阶段构建,处理单向 buddy) ====================
    const int rawLimit = PAGE_SIZE + 1;
    const int rawOffset = PAGE_SIZE * (m_nCurPage - 1);
#if USE_FAKE_DB_DEMO
    auto page = _make_page_fake(m_filters, rawLimit, rawOffset);
#else
    auto& db = GlassLogDb::Instance();
    auto pageFull = db.queryPaged(m_filters, rawLimit, rawOffset);
#endif
    // å¦‚果多出一条,看看它是否是“本页最后一条”的 buddy
    std::optional<decltype(pageFull.items)::value_type> lookahead; // é¢„读记录(若与最后一条配对)
    auto iEquals = [](const std::string& a, const std::string& b) {
#ifdef _WIN32
        return _stricmp(a.c_str(), b.c_str()) == 0;
#else
        return strcasecmp(a.c_str(), b.c_str()) == 0;
#endif
    };
    if (pageFull.items.size() == rawLimit) {
        const auto& last = pageFull.items[PAGE_SIZE - 1];
        const auto& extra = pageFull.items[PAGE_SIZE];
        bool pair =
            (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) ||
            (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId));
        if (pair) {
            lookahead = extra;           // æŠŠé¢„读保存下来,稍后补成子行
        }
        // æ— è®ºæ˜¯å¦é…å¯¹ï¼Œåˆ—表都缩回 PAGE_SIZE æ¡ï¼ˆé¢„读不算入本页数据集)
        pageFull.items.pop_back();
    }
    // ä¹‹åŽæ­£å¸¸æŒ‰ page æž„建
    auto& page = pageFull; // ä¸ºäº†å¤ç”¨ä½ åŽŸæœ‰å˜é‡å
    // å»ºç´¢å¼•:classId -> index
    std::unordered_map<std::string, size_t> idxById;
    idxById.reserve(page.items.size());
    for (size_t i = 0; i < page.items.size(); ++i) {
        idxById[page.items[i].classId] = i;
    }
    // å·²æ¶ˆè´¹ï¼ˆå·²æ’入为父或子)
    std::unordered_set<std::string> consumed;
    int zebra = 0;
    auto zebraBk = [&](int z) -> COLORREF {
        return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
    };
    // -------- Phase 1: å…ˆå¤„理“有 buddyId çš„记录”(能配就配;单向也配) ----------
    for (size_t i = 0; i < page.items.size(); ++i) {
        const auto& r = page.items[i];
        // CopyUtf8ToClipboard(r.pretty);
        if (consumed.count(r.classId)) continue;
        if (r.buddyId.empty()) continue;
        COLORREF bk = zebraBk(zebra);
        auto it = idxById.find(r.buddyId);
        if (it != idxById.end()) {
            const auto& br = page.items[it->second];
            if (!consumed.count(br.classId)) {
                // â€”— ä»¥â€œæœ‰ buddyId çš„这条 r”为父,buddy ä½œä¸ºå­ï¼ˆå•向也能配)——
                std::vector<CString> pcols(colCount);
                pcols[1] = std::to_string(r.id).c_str();
                pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
                pcols[3] = std::to_string(r.jobSeqNo).c_str();
                pcols[4] = r.classId.c_str();
                pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
                pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
                pcols[7] = r.tStart.c_str();
                pcols[8] = r.tEnd.c_str();
                pcols[9] = r.buddyId.c_str();
                pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
                pcols[11] = r.path.c_str();
                pcols[12] = r.params.c_str();
                auto* nParent = m_listCtrl.InsertRoot(pcols);
                MaybeRestoreExpandByKey(nParent, expandedKeys);
                m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
                std::vector<CString> ccols(colCount);
                ccols[1] = std::to_string(br.id).c_str();
                ccols[2] = std::to_string(br.cassetteSeqNo).c_str();
                ccols[3] = std::to_string(br.jobSeqNo).c_str();
                ccols[4] = br.classId.c_str();
                ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str();
                ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str();
                ccols[7] = br.tStart.c_str();
                ccols[8] = br.tEnd.c_str();
                ccols[9] = br.buddyId.c_str();
                ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str();
                ccols[11] = br.path.c_str();
                ccols[12] = br.params.c_str();
                auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
                m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
                consumed.insert(r.classId);
                consumed.insert(br.classId);
                ++zebra;
                continue;
            }
        }
        // åŒé¡µæ²¡æ‰¾åˆ° buddy(或已被消费)→ æ’占位子行
        std::vector<CString> pcols(colCount);
        pcols[1] = std::to_string(r.id).c_str();
        pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
        pcols[3] = std::to_string(r.jobSeqNo).c_str();
        pcols[4] = r.classId.c_str();
        pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
        pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
        pcols[7] = r.tStart.c_str();
        pcols[8] = r.tEnd.c_str();
        pcols[9] = r.buddyId.c_str();
        pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
        pcols[11] = r.path.c_str();
        pcols[12] = r.params.c_str();
        auto* nParent = m_listCtrl.InsertRoot(pcols);
        MaybeRestoreExpandByKey(nParent, expandedKeys);
        m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
        std::vector<CString> ccols(colCount); // å ä½åªå†™ ClassID
        ccols[4] = r.buddyId.c_str();
        auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
        m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
        consumed.insert(r.classId);
        ++zebra;
    }
    // -------- Phase 2: å‰©ä½™æœªæ¶ˆè´¹çš„,作为“单条根行” ----------
    for (size_t i = 0; i < page.items.size(); ++i) {
        const auto& r = page.items[i];
        if (consumed.count(r.classId)) continue;
        COLORREF bk = zebraBk(zebra);
        std::vector<CString> cols(colCount);
        cols[1] = std::to_string(r.id).c_str();
        cols[2] = std::to_string(r.cassetteSeqNo).c_str();
        cols[3] = std::to_string(r.jobSeqNo).c_str();
        cols[4] = r.classId.c_str();
        cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
        cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
        cols[7] = r.tStart.c_str();
        cols[8] = r.tEnd.c_str();
        cols[9] = r.buddyId.c_str();
        cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
        cols[11] = r.path.c_str();
        cols[12] = r.params.c_str();
        auto* n = m_listCtrl.InsertRoot(cols);
        m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
        consumed.insert(r.classId);
        ++zebra;
    }
    // ä¸€æ¬¡æ€§é‡ç»˜
    m_listCtrl.RebuildVisible();
    // ä¸Šä¸€é¡µ / ä¸‹ä¸€é¡µ
    UpdatePageControls();
    m_rebuilding = false;
}
void CPageGlassList::UpdatePageControls()
{
    CString strPage;
    strPage.Format(_T("第 %d / %d é¡µ"), m_nCurPage, m_nTotalPages);
    SetDlgItemText(IDC_LABEL_PAGE_NUMBER, strPage);
    GetDlgItem(IDC_BUTTON_PREV_PAGE)->EnableWindow(m_nCurPage > 1);
    GetDlgItem(IDC_BUTTON_NEXT_PAGE)->EnableWindow(m_nCurPage < m_nTotalPages);
    Resize();
    CString strPage;
    strPage.Format(_T("第 %d / %d é¡µ"), m_nCurPage, m_nTotalPages);
    SetDlgItemText(IDC_LABEL_PAGE_NUMBER, strPage);
    GetDlgItem(IDC_BUTTON_PREV_PAGE)->EnableWindow(m_nCurPage > 1);
    GetDlgItem(IDC_BUTTON_NEXT_PAGE)->EnableWindow(m_nCurPage < m_nTotalPages);
}
void CPageGlassList::UpdateDateFilter()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    if (nullptr != pComboBox) {
        int nIndex = pComboBox->GetCurSel();
        if (nIndex == 0) {
            memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
            memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
            m_szTimeStart[0] = '\0';
            m_szTimeEnd[0] = '\0';
        }
        else {
            CTime time = CTime::GetCurrentTime();
            if (nIndex == 1) {
                sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", time.GetYear(), time.GetMonth(), time.GetDay());
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
            }
            else if (nIndex == 2) {
                CTime time2 = time - CTimeSpan(7, 0, 0, 0);
                sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", time2.GetYear(), time2.GetMonth(), time2.GetDay());
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
            }
            else if (nIndex == 3) {
                sprintf_s(m_szTimeStart, 64, "%d-%02d-01 00:00:00", time.GetYear(), time.GetMonth());
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
            }
            else if (nIndex == 4) {
                sprintf_s(m_szTimeStart, 64, "%d-01-01 00:00:00", time.GetYear());
                sprintf_s(m_szTimeEnd, 64, "%d-12-31 23:59:59", time.GetYear());
            }
            else if (nIndex == 5) {
                SYSTEMTIME t1, t2;
                m_dateTimeStart.GetTime(&t1);
                m_dateTimeEnd.GetTime(&t2);
                //sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d %02d:%02d:%02d", t1.wYear, t1.wMonth, t1.wDay, t1.wHour, t1.wMinute, t1.wSecond);
                //sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d %02d:%02d:%02d", t2.wYear, t2.wMonth, t2.wDay, t2.wHour, t2.wMinute, t2.wSecond);
                sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", t1.wYear, t1.wMonth, t1.wDay);
                sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", t2.wYear, t2.wMonth, t2.wDay);
            }
        }
    }
}
/*
void CPageGlassList::FillDataToListCtrl(const std::vector<TransferData>& vecData)
{
    if (m_listCtrl.m_hWnd == nullptr) {
        return;
    }
    m_listCtrl.DeleteAllItems();
    for (const auto& item : vecData) {
        InsertTransferData(item);
    }
}
void CPageGlassList::InsertTransferData(const TransferData& data)
{
    if (m_listCtrl.m_hWnd == nullptr) {
        return;
    }
    int nIndex = m_listCtrl.GetItemCount();
    if (nIndex < 0) {
        return;
    }
    int nItem = m_listCtrl.InsertItem(nIndex, _T(""));
    CString str;
    str.Format(_T("%d"), data.nRecordId);
    m_listCtrl.SetItemText(nItem, 1, str);
    m_listCtrl.SetItemText(nItem, 2, CString(data.strStatus.c_str()));
    m_listCtrl.SetItemText(nItem, 3, CString(data.strClassID.c_str()));
    m_listCtrl.SetItemText(nItem, 4, CString(data.strCreateTime.c_str()));
    m_listCtrl.SetItemText(nItem, 5, CString(data.strPickTime.c_str()));
    m_listCtrl.SetItemText(nItem, 6, CString(data.strPlaceTime.c_str()));
    m_listCtrl.SetItemText(nItem, 7, CString(data.strEndTime.c_str()));
    m_listCtrl.SetItemText(nItem, 8, CString(data.strDescription.c_str()));
}
*/
// CPageTransferLog æ¶ˆæ¯å¤„理程序
BOOL CPageGlassList::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    CDialogEx::OnInitDialog();
    // TODO:  åœ¨æ­¤æ·»åŠ é¢å¤–çš„åˆå§‹åŒ–
    SetTimer(1, 3000, nullptr);
    // å®šæ—¶å™¨ï¼š1=初始化订阅,2=周期刷新(只增量)
    SetTimer(1, 3000, nullptr);
    SetTimer(2, 2000, nullptr);
    // ä¸‹æ‹‰æ¡†æŽ§ä»¶
    InitStatusCombo();
    InitTimeRangeCombo();
    // ä¸‹æ‹‰æ¡†æŽ§ä»¶
    InitStatusCombo();
    InitTimeRangeCombo();
    // æ—¥æœŸæŽ§ä»¶
    InitDateTimeControls();
    // æ—¥æœŸæŽ§ä»¶
    InitDateTimeControls();
    // æŠ¥è¡¨æŽ§ä»¶
    CString strIniFile, strItem;
    strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
    // æŠ¥è¡¨æŽ§ä»¶
    CString strIniFile, strItem;
    strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
    DWORD dwStyle = m_listCtrl.GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    dwStyle |= LVS_EX_GRIDLINES;
    m_listCtrl.SetExtendedStyle(dwStyle);
    DWORD dwStyle = m_listCtrl.GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    dwStyle |= LVS_EX_GRIDLINES;
    dwStyle |= LVS_EX_DOUBLEBUFFER;
    m_listCtrl.SetExtendedStyle(dwStyle);
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
    ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
    ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
    CString headers[] = {
        _T(""),
        _T("Cassette Sequence No"),
        _T("Job Sequence No"),
        _T("Class ID"),
        _T("物料类型"),
        _T("状态"),
        _T("工艺开始时间"),
        _T("工艺结束时间"),
        _T("邦定Glass ID"),
        _T("AOI检测结果"),
        _T("路径"),
        _T("工艺参数")
    };
    int widths[] = { 0, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 };
    for (int i = 0; i < _countof(headers); ++i) {
        strItem.Format(_T("Col_%d_Width"), i);
        widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, widths[i], strIniFile);
        m_listCtrl.InsertColumn(i, headers[i], LVCFMT_LEFT, widths[i]);
    }
    m_listCtrl.SetColumnWidth(10, LVSCW_AUTOSIZE_USEHEADER);
    CString headers[] = {
        _T(""),
        _T("id"),
        _T("Cassette SN"),
        _T("Job SN"),
        _T("Class ID"),
        _T("物料类型"),
        _T("状态"),
        _T("工艺开始时间"),
        _T("工艺结束时间"),
        _T("邦定Glass ID"),
        _T("AOI检测结果"),
        _T("路径"),
        _T("工艺参数")
    };
    int widths[] = { 24, 80, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 };
    for (int i = 0; i < _countof(headers); ++i) {
        strItem.Format(_T("Col_%d_Width"), i);
        int def = widths[i];
        widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, def, strIniFile);
        if (i == 0 && widths[i] < 16) widths[i] = 24; // è®©ä¸‰è§’图标有空间展示
        m_listCtrl.InsertColumn(i, headers[i], i == 0 ? LVCFMT_RIGHT : LVCFMT_LEFT, widths[i]);
    }
    // äºŒæ¬¡å…œåº•,防止 ini å†™è¿›äº† 0
    if (m_listCtrl.GetColumnWidth(0) < 16) m_listCtrl.SetColumnWidth(0, 24);
    m_listCtrl.SetPopupFullTextColumns({ 11, 12 });
    Resize();
    OnBnClickedButtonSearch(); // è§¦å‘一次查询与首屏填充
    // è®¡ç®—总页数
    /*
    int nTotalRecords = TransferManager::getInstance().getTotalTransferCountAll();
    m_nTotalPages = (nTotalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
    m_nCurPage = 1;
    */
    Resize();
    LoadTransfers();
    return TRUE;  // return TRUE unless you set the focus to a control
    // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
    return TRUE;  // return TRUE unless you set the focus to a control
}
HBRUSH CPageGlassList::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    if (nCtlColor == CTLCOLOR_STATIC) {
        pDC->SetBkColor(m_crBkgnd);
    }
    if (m_hbrBkgnd == nullptr) {
        m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
    }
    return m_hbrBkgnd;
    if (nCtlColor == CTLCOLOR_STATIC) {
        pDC->SetBkColor(m_crBkgnd);
    }
    if (m_hbrBkgnd == nullptr) {
        m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
    }
    return m_hbrBkgnd;
}
void CPageGlassList::OnDestroy()
{
    CDialogEx::OnDestroy();
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
        m_hbrBkgnd = nullptr;
    }
    if (m_pObserver != nullptr) {
        m_pObserver->unsubscribe();
        m_pObserver = nullptr;
    }
    CDialogEx::OnDestroy();
    if (m_hbrBkgnd != nullptr) {
        ::DeleteObject(m_hbrBkgnd);
        m_hbrBkgnd = nullptr;
    }
    if (m_pObserver != nullptr) {
        m_pObserver->unsubscribe();
        m_pObserver = nullptr;
    }
    // ä¿å­˜åˆ—宽
    CString strIniFile, strItem, strTemp;
    strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
    CHeaderCtrl* pHeader = m_listCtrl.GetHeaderCtrl();
    for (int i = 0; i < pHeader->GetItemCount(); i++) {
        RECT rect;
        pHeader->GetItemRect(i, &rect);
        strItem.Format(_T("Col_%d_Width"), i);
        strTemp.Format(_T("%d"), rect.right - rect.left);
        WritePrivateProfileString("GlassListCtrl", strItem, strTemp, strIniFile);
    }
    // ä¿å­˜åˆ—宽(首列兜底,避免把 0 å†™å›žåŽ»ï¼‰
    CString strIniFile, strItem, strTemp;
    strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
    CHeaderCtrl* pHeader = m_listCtrl.GetHeaderCtrl();
    if (pHeader) {
        for (int i = 0; i < pHeader->GetItemCount(); i++) {
            RECT rect;
            pHeader->GetItemRect(i, &rect);
            strItem.Format(_T("Col_%d_Width"), i);
            int w = rect.right - rect.left;
            if (i == 0 && w < 16) w = 24;
            strTemp.Format(_T("%d"), w);
            WritePrivateProfileString("GlassListCtrl", strItem, strTemp, strIniFile);
        }
    }
}
void CPageGlassList::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    Resize();
    CDialogEx::OnSize(nType, cx, cy);
    Resize();
}
void CPageGlassList::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == 1) {
        KillTimer(1);
        InitRxWindow();
    }
    CDialogEx::OnTimer(nIDEvent);
    if (nIDEvent == 1) {
        KillTimer(1);
        InitRxWindow();
    }
    else if (nIDEvent == 2) {
        UpdateWipData();  // åªåšå¢žé‡ï¼Œä¸é‡å»º
    }
    CDialogEx::OnTimer(nIDEvent);
}
void CPageGlassList::OnCbnSelchangeComboDatetime()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    int nIndex = pComboBox->GetCurSel();
    int nCount = pComboBox->GetCount();
    m_dateTimeStart.EnableWindow(nIndex == nCount - 1);
    m_dateTimeEnd.EnableWindow(nIndex == nCount - 1);
    // æ›´æ–°æ—¥æœŸè¿‡æ»¤å™¨å’Œé¡µé¢æ•°æ®
    UpdateDateFilter();
    LoadTransfers();
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    int nIndex = pComboBox->GetCurSel();
    int nCount = pComboBox->GetCount();
    m_dateTimeStart.EnableWindow(nIndex == nCount - 1);
    m_dateTimeEnd.EnableWindow(nIndex == nCount - 1);
}
void CPageGlassList::OnCbnSelchangeComboStatusFilter()
{
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_STATUS_FILTER);
    int nIndex = pComboBox->GetCurSel();
    if (nIndex == 0) {
        m_strStatus.clear();
    }
    else {
        CString cstrText;
        pComboBox->GetLBText(nIndex, cstrText);
        m_strStatus = CT2A(cstrText);
    }
    LoadTransfers();
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_STATUS_FILTER);
    int nIndex = pComboBox->GetCurSel();
    if (nIndex == 0) {
        m_strStatus.clear();
    }
    else {
        CString cstrText;
        pComboBox->GetLBText(nIndex, cstrText);
        m_strStatus = CT2A(cstrText);
    }
}
void CPageGlassList::OnBnClickedButtonSearch()
{
    // èŽ·å–å…³é”®å­—è¾“å…¥æ¡†å†…å®¹
    CString strKeyword;
    GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword);
    m_strKeyword = CT2A(strKeyword);
    // èŽ·å–å…³é”®å­—è¾“å…¥æ¡†å†…å®¹
    CString strKeyword;
    GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword);
    m_filters.keyword = CT2A(strKeyword);
    // æ›´æ–°æ—¥æœŸè¿‡æ»¤å™¨å’Œé¡µé¢æ•°æ®
    UpdateDateFilter();
    LoadTransfers();
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
    int index = pComboBox->GetCurSel();
    if (index == 0) {
        // ä¸é™
        m_filters.tStartFrom = std::nullopt;
        m_filters.tStartTo = std::nullopt;
    }
    else if (index == 1) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Today);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 2) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Last7Days);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 3) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisMonth);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 4) {
        auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisYear);
        m_filters.tStartFrom = fromUtc;
        m_filters.tStartTo = toUtc;
    }
    else if (index == 5) {
        // è‡ªå®šä¹‰
        std::chrono::system_clock::time_point tp;
        if (CToolUnits::GetCtrlDateRangeUtc_StartOfDay(m_dateTimeStart, tp)) m_filters.tStartFrom = tp;
        if (CToolUnits::GetCtrlDateRangeUtc_EndOfDay(m_dateTimeEnd, tp))   m_filters.tStartTo = tp;
    }
#if USE_FAKE_DB_DEMO
    long long total = _fake_total_count();
#else
    auto& db = GlassLogDb::Instance();
    long long total = db.count(m_filters);
#endif
    m_nTotalPages = (PAGE_SIZE > 0) ? int((total + PAGE_SIZE - 1) / PAGE_SIZE) : 1;
    LoadData();
}
void CPageGlassList::OnBnClickedButtonExport()
{
    CFileDialog fileDialog(FALSE, _T("csv"), NULL, OFN_HIDEREADONLY, _T("CSV Files (*.csv)|*.csv||"));
    if (fileDialog.DoModal() != IDOK) {
        return;
    }
    CFileDialog fileDialog(FALSE, _T("csv"), NULL, OFN_HIDEREADONLY, _T("CSV Files (*.csv)|*.csv||"));
    if (fileDialog.DoModal() != IDOK) {
        return;
    }
    CStdioFile file;
    if (!file.Open(fileDialog.GetPathName(), CFile::modeCreate | CFile::modeWrite | CFile::typeText)) {
        AfxMessageBox(_T("创建文件失败!"));
        return;
    }
    CString strHeader = _T("任务ID,状态,ClassID,创建时间,取片时间,放片时间,结束时间,描述\n");
    file.WriteString(strHeader);
    for (int i = 0; i < m_listCtrl.GetItemCount(); ++i) {
        CString row;
        for (int j = 1; j <= 8; ++j) {
            row += m_listCtrl.GetItemText(i, j);
            if (j != 8) {
                row += ",";
            }
        }
        row += "\n";
        file.WriteString(row);
    }
    file.Close();
    // å¯¼å‡º CSV:导出符合 filters çš„“全部记录”(不受分页限制)
    auto& db = GlassLogDb::Instance();
    std::string csvPath((LPTSTR)(LPCTSTR)fileDialog.GetPathName());
    if (db.exportCsv(csvPath, m_filters) > 0) {
        AfxMessageBox("导出CSV成功!");
    }
}
void CPageGlassList::OnBnClickedButtonPrevPage()
{
    SERVO::CGlass g;
    g.setID("GLS-001");
    g.setType(SERVO::MaterialsType::G1);
    g.setOriginPort(1, 5);
    g.setScheduledForProcessing(TRUE);
    g.m_failReason = "none";
    g.markQueued();
    g.markStart();
    // æ·»åŠ å‚æ•°
    CParam p1("校正对位延时", "P001", "ms", 123);
    CParam p2("温度", "P002", "degC", 25.5);
    g.getParams().push_back(p1);
    g.getParams().push_back(p2);
    // è®¾ç½® JobDataS
    SERVO::CJobDataS* js = g.getJobDataS();
    js->setCassetteSequenceNo(10);
    js->setJobSequenceNo(20);
    js->setLotId("LOT-ABC");
    js->setGlass1Id("GLS-001");
    // æ·»åŠ  Path
    g.addPath(100, 1);
    SERVO::CPath* tail = g.getPath()->getTailPath();
    tail->setInTime(111111);
    tail->setOutTime(222222);
    tail->setInspResult(SERVO::InspResult::Pass);
    tail->processEnd();
    return;
    // 2. è½¬ä¸º JSON
    std::string jsonText = GlassJson::ToPrettyString(g);
    TRACE("序列化结果:\n%s\n\n", jsonText.c_str());
    // 3. ååºåˆ—化
    SERVO::CGlass g2;
    std::string err;
    if (!GlassJson::FromString(jsonText, g2, &err)) {
        TRACE("解析失败: %s\n", err.c_str());
        return;
    }
    // 4. æ‰“印验证
    TRACE("反序列化后的ID: %s\n", g2.getID().c_str());
    TRACE("反序列化后的参数数量: %d\n", (int)g2.getParams().size());
    if (!g2.getParams().empty()) {
        TRACE("第一个参数名: %s å€¼=%d\n",
            g2.getParams()[0].getName().c_str(),
            g2.getParams()[0].getIntValue());
    }
    if (m_nCurPage > 1) {
        m_nCurPage--;
        UpdatePageData();
    }
    if (m_nCurPage > 1) {
        m_nCurPage--;
        UpdatePageData();
    }
}
void CPageGlassList::OnBnClickedButtonNextPage()
{
    if (m_nCurPage < m_nTotalPages) {
        m_nCurPage++;
        UpdatePageData();
    }
}
    if (m_nCurPage < m_nTotalPages) {
        m_nCurPage++;
        UpdatePageData();
    }
}
void CPageGlassList::OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult)
{
    auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR);
    // è¿™é‡Œæš‚时用消息框显示;后续可换成你的详情页
    CString strNewMsg = p->text;
    strNewMsg.Replace(_T(","), _T("\n"));
    MessageBox(strNewMsg, _T("详细信息"), MB_OK | MB_ICONINFORMATION);
    *pResult = 0;
}
void CPageGlassList::UpdateWipData()
{
    // åªåœ¨ç¬¬ 1 é¡µåˆ·æ–° WIP;其它页不动
    if (m_nCurPage != 1) return;
    // è‹¥åˆšå¥½åœ¨ UpdatePageData() é‡å»ºæœŸé—´ï¼Œè·³è¿‡è¿™è½®å¢žé‡ï¼Œé¿å…äº’相干扰
    if (m_rebuilding) return;
    const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
    if (colCount <= 0) return;
    // 1) æ”¶é›†å½“前可见里的“WIP è¡Œâ€ï¼ˆç¬¬1列 id ä¸ºç©ºï¼‰
    //    a) wipRowById:classId -> (row, node*),收集根+子,便于判断“buddy æ˜¯å¦å·²åœ¨å¯è§è¡¨ä¸­â€
    //    b) wipRootById:classId -> node*,仅收集根节点,便于只对“根”补子项
    std::unordered_map<std::string, std::pair<int, CExpandableListCtrl::Node*>> wipRowById;
    std::unordered_map<std::string, CExpandableListCtrl::Node*>                 wipRootById;
    for (int row = 0; row < m_listCtrl.GetItemCount(); ++row) {
        CString idDb = m_listCtrl.GetItemText(row, 1); // ç¬¬1列是 DB id
        if (!idDb.IsEmpty()) continue;                 // æœ‰ id çš„æ˜¯ DB è¡Œï¼Œè·³è¿‡
        auto* node = m_listCtrl.GetNodeByVisibleIndex(row);
        CString cls = m_listCtrl.GetItemText(row, 4);  // ç¬¬4列 Class ID
#ifdef _UNICODE
        std::string key = CT2A(cls);
#else
        std::string key = cls.GetString();
#endif
        wipRowById[key] = { row, node };
        if (node && node->parent == nullptr) {
            wipRootById[key] = node;                   // ä»…根节点进入这个表
        }
    }
    // 2) æ‹‰å½“前 WIP åˆ—表
    std::vector<SERVO::CGlass*> wipGlasses;
    theApp.m_model.m_master.getWipGlasses(wipGlasses);
    std::vector<SERVO::CGlass*> tempRetain = wipGlasses; // ç¨åŽç»Ÿä¸€ release
    auto makeColsFromWip = [&](SERVO::CGlass* g) {
        std::vector<CString> cols(colCount);
        cols[1] = _T(""); // WIP æ²¡ DB id
        cols[2] = std::to_string(g->getCassetteSequenceNo()).c_str();
        cols[3] = std::to_string(g->getJobSequenceNo()).c_str();
        cols[4] = g->getID().c_str();
        cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str();
        cols[6] = SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str();
        cols[7] = CToolUnits::TimePointToLocalString(g->tStart()).c_str();
        cols[8] = CToolUnits::TimePointToLocalString(g->tEnd()).c_str();
        cols[9] = g->getBuddyId().c_str();
        cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str();
        cols[11] = g->getPathDescription().c_str();
        cols[12] = g->getParamsDescription().c_str();
        return cols;
    };
    // 2.1 æž„建“最新 WIP é”®é›†â€ï¼ˆå« buddy,且命中过滤),用于检测“缺失/删除”
    std::unordered_set<std::string> newWipKeys;
    for (auto* g : wipGlasses) {
        if (!GlassMatchesFilters(*g, m_filters)) continue;
#ifdef _UNICODE
        newWipKeys.insert(CT2A(CString(g->getID().c_str())));
#else
        newWipKeys.insert(g->getID());
#endif
        if (auto* b = g->getBuddy()) {
            if (GlassMatchesFilters(*b, m_filters)) {
#ifdef _UNICODE
                newWipKeys.insert(CT2A(CString(b->getID().c_str())));
#else
                newWipKeys.insert(b->getID());
#endif
            }
        }
    }
    bool needRebuildRemoval = false; // WIP å˜å°‘/清空:需要整页重建
    bool needRebuildChildren = false; // ç»“构变化:新增子
    bool needRebuildAllForNewRoot = false; // æ–°å¢žæ ¹ï¼ˆä¿è¯ WIP ä»åœ¨é¡¶éƒ¨ï¼‰
    std::vector<int> rowsToRedraw;         // ä»…文本变化
    // å¯è§é›†ä¸­æœ‰ä½†æ–°æ•°æ®é‡Œæ²¡æœ‰ -> è§¦å‘“删除/减少”的整页重建
    for (const auto& kv : wipRowById) {
        if (newWipKeys.find(kv.first) == newWipKeys.end()) { needRebuildRemoval = true; break; }
    }
    // UI çŠ¶æ€ï¼ˆå½“éœ€è¦é‡å»ºæ—¶ä½¿ç”¨ï¼‰
    std::vector<CExpandableListCtrl::Node*> savedSel;
    CExpandableListCtrl::Node* savedTop = nullptr;
    // 3) é€ä¸ªå¤„理 WIP:已存在 -> å°±åœ°æ›´æ–°ï¼›å¿…要时“只对根补子项”
    //                 ä¸å­˜åœ¨ -> ä¼˜å…ˆæŒ‚到 buddy å®¹å™¨ï¼›å¦åˆ™è§¦å‘整页重建(新根保持顶部)
    for (auto* g : wipGlasses) {
        if (!GlassMatchesFilters(*g, m_filters)) continue;
#ifdef _UNICODE
        std::string cid = CT2A(CString(g->getID().c_str()));
#else
        std::string cid = g->getID();
#endif
        auto itAny = wipRowById.find(cid);
        if (itAny != wipRowById.end()) {
            // (A) å·²å­˜åœ¨ï¼šä»…更新文案 & é‡ç»˜è¯¥è¡Œ
            int row = itAny->second.first;
            m_listCtrl.SetItemText(row, 2, std::to_string(g->getCassetteSequenceNo()).c_str());
            m_listCtrl.SetItemText(row, 3, std::to_string(g->getJobSequenceNo()).c_str());
            m_listCtrl.SetItemText(row, 4, g->getID().c_str());
            m_listCtrl.SetItemText(row, 5, SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str());
            m_listCtrl.SetItemText(row, 6, SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str());
            m_listCtrl.SetItemText(row, 7, CToolUnits::TimePointToLocalString(g->tStart()).c_str());
            m_listCtrl.SetItemText(row, 8, CToolUnits::TimePointToLocalString(g->tEnd()).c_str());
            m_listCtrl.SetItemText(row, 9, g->getBuddyId().c_str());
            m_listCtrl.SetItemText(row, 10, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str());
            m_listCtrl.SetItemText(row, 11, g->getPathDescription().c_str());
            m_listCtrl.SetItemText(row, 12, g->getParamsDescription().c_str());
            rowsToRedraw.push_back(row);
            // â€”— é¡ºå¸¦åˆ·æ–° buddy å­è¡Œï¼ˆå¦‚果它已在可见表里)——
            if (SERVO::CGlass* b = g->getBuddy()) {
#ifdef _UNICODE
                std::string bid = CT2A(CString(b->getID().c_str()));
#else
                std::string bid = b->getID();
#endif
                auto itChildAny = wipRowById.find(bid);
                if (itChildAny != wipRowById.end()) {
                    int crow = itChildAny->second.first;
                    auto bcols = makeColsFromWip(b);
                    ApplyColsToRow(m_listCtrl, crow, bcols);
                    rowsToRedraw.push_back(crow);
                }
            }
            // â€”— åªå¯¹â€œæ ¹èŠ‚ç‚¹â€è¡¥å­é¡¹ï¼Œä¸”ä»…å½“ buddy å°šæœªå‡ºçŽ°åœ¨å¯è§è¡¨ï¼Œä¸”æ ¹ä¸‹ä¹Ÿæ²¡æœ‰è¯¥ buddy â€”—
            SERVO::CGlass* b = g->getBuddy();
            if (b) {
                auto itRoot = wipRootById.find(cid);
                if (itRoot != wipRootById.end()) {
                    CExpandableListCtrl::Node* container = itRoot->second;
                    CString newBuddyCid = b->getID().c_str();
#ifdef _UNICODE
                    std::string newBid = CT2A(newBuddyCid);
#else
                    std::string newBid = newBuddyCid.GetString();
#endif
                    // çŽ°æœ‰å®¹å™¨ä¸‹çš„â€œç¬¬ä¸€ä¸ªå­ classId”(如果有的话)
                    CString oldChildCid;
                    if (!container->children.empty() && container->children[0] && container->children[0]->cols.size() > 4)
                        oldChildCid = container->children[0]->cols[4];
                    bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end());
                    bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid);
                    // å…³ç³»æ˜¯å¦å‘生变化?(oldChildCid ä¸Ž newBuddyCid ä¸åŒï¼Œæˆ–有子但现在没 buddy)
                    bool relationChanged =
                        (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) ||
                        (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) ||
                        (!oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty() &&
                            oldChildCid.CompareNoCase(newBuddyCid) != 0);
                    if (relationChanged) {
                        needRebuildAllForNewRoot = true; // é¿å…é‡å¤æˆ–反向挂载
                    }
                    else {
                        // å…³ç³»æœªå˜ï¼šè‹¥ buddy ä¸åœ¨å¯è§è¡¨ä¸”容器下也没有,则补子
                        if (!buddyExistsAnywhere && !hasChildAlready) {
                            if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); }
                            needRebuildChildren = true;
                            auto cols = makeColsFromWip(b);
                            auto* ch = m_listCtrl.InsertChild(container, cols);
                            m_listCtrl.SetNodeColor(ch, kWipText, kWipChildBk);  // å­ï¼šæµ…色
                            m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk); // çˆ¶ï¼šåŸºç¡€ç»¿
                        }
                        // è‹¥å·²æœ‰å­ï¼šåŒæ­¥åˆ·æ–°å­è¡Œæ–‡æœ¬ä¸Žé¢œè‰²
                        else if (hasChildAlready) {
                            for (auto& ch : container->children) {
                                if (ch && ch->cols.size() > 4 && ch->cols[4].CompareNoCase(newBuddyCid) == 0) {
                                    auto cols = makeColsFromWip(b);
                                    ch->cols = cols; // æ›´æ–°åº•层数据
                                    // å¯è§è¡Œåˆ·æ–°
                                    for (int r = 0; r < m_listCtrl.GetItemCount(); ++r) {
                                        if (m_listCtrl.GetNodeByVisibleIndex(r) == ch.get()) {
                                            for (int c = 1; c < (int)cols.size(); ++c)
                                                m_listCtrl.SetItemText(r, c, cols[c]);
                                            rowsToRedraw.push_back(r);
                                            break;
                                        }
                                    }
                                    m_listCtrl.SetNodeColor(ch.get(), kWipText, kWipChildBk);
                                    m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk);
                                    break;
                                }
                            }
                        }
                    }
                }
                // è‹¥å½“前是“子节点”,不在这里调整父子关系;让“关系变化”走全量重建
            }
            else {
                // æ²¡æœ‰ buddy:如果容器下现在有子,也算关系变化,触发重建
                auto itRoot = wipRootById.find(cid);
                if (itRoot != wipRootById.end()) {
                    CExpandableListCtrl::Node* container = itRoot->second;
                    if (!container->children.empty())
                        needRebuildAllForNewRoot = true;
                }
            }
        }
        else {
            // (B) ä¸å­˜åœ¨ï¼šæ–°å¢ž
            //   å…ˆå°è¯•“挂到 buddy çš„容器根”下面;
            //   è‹¥ buddy ä¸åœ¨å½“前可见表,则触发全量重建(保证 WIP é¡¶éƒ¨ï¼‰ã€‚
            SERVO::CGlass* b = g->getBuddy();
            CExpandableListCtrl::Node* container = nullptr;
            if (b) {
#ifdef _UNICODE
                std::string bid = CT2A(CString(b->getID().c_str()));
#else
                std::string bid = b->getID();
#endif
                auto itB = wipRowById.find(bid);
                if (itB != wipRowById.end()) {
                    CExpandableListCtrl::Node* buddyNode = itB->second.second;
                    container = buddyNode ? (buddyNode->parent ? buddyNode->parent : buddyNode) : nullptr;
                }
            }
            if (container) {
                // buddy å®¹å™¨å­˜åœ¨ï¼šæŠŠ g ä½œä¸ºâ€œå­è¡Œâ€æŒ‚上去(避免重复)
                CString cidCs = g->getID().c_str();
                if (!NodeHasChildWithClassId(container, cidCs)) {
                    if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); }
                    needRebuildChildren = true;
                    auto cols = makeColsFromWip(g);
                    auto* ch = m_listCtrl.InsertChild(container, cols);
                    // å­ï¼šæ›´æµ…;父:基础绿(兜底)
                    m_listCtrl.SetNodeColor(ch, kWipText, kWipChildBk);
                    m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk);
                }
            }
            else {
                // buddy ä¸åœ¨å¯è§è¡¨ï¼šä¸ºäº†ä¿æŒâ€œWIP æ°¸è¿œåœ¨é¡¶éƒ¨â€ï¼Œè§¦å‘一次全量重建
                needRebuildAllForNewRoot = true;
            }
        }
    }
    // 4) åº”用 UI æ›´æ–° â€”— æŠŠâ€œåˆ é™¤/减少”的情况并入全量重建分支
    if (needRebuildAllForNewRoot || needRebuildRemoval) {
        auto selKeys = SnapshotSelectedKeys(m_listCtrl);
        auto topKey = SnapshotTopKey(m_listCtrl);
        UpdatePageData();                      // å…¨é‡é‡å»ºï¼ˆWIP é¡¶éƒ¨ & åˆ é™¤æ— æ•ˆé¡¹ï¼‰
        RestoreSelectionByKeys(m_listCtrl, selKeys);
        RestoreTopByKey(m_listCtrl, topKey);
    }
    else if (needRebuildChildren) {
        auto selKeys = SnapshotSelectedKeys(m_listCtrl);
        auto topKey = SnapshotTopKey(m_listCtrl);
        m_listCtrl.RebuildVisible();           // ä»…结构变化(加子)
        RestoreSelectionByKeys(m_listCtrl, selKeys);
        RestoreTopByKey(m_listCtrl, topKey);
    }
    else {
        for (int row : rowsToRedraw)           // ä»…文本变化
            m_listCtrl.RedrawItems(row, row);
    }
    // 5) é‡Šæ”¾ retain
    for (auto* g : tempRetain) g->release();
}
void CPageGlassList::InsertWipRow(SERVO::CGlass* /*pGlass*/)
{
    // ä¸å†ä½¿ç”¨
}
void CPageGlassList::UpdateWipRow(unsigned int /*index*/, SERVO::CGlass* /*pGlass*/)
{
    // ä¸å†ä½¿ç”¨
}
bool CPageGlassList::eraseGlassInVector(SERVO::CGlass* /*pGlass*/, std::vector<SERVO::CGlass*>& /*glasses*/)
{
    return false;
}
// ===== è¿‡æ»¤é€»è¾‘(原样保留) =====
// æ ¸å¿ƒï¼šWIP çš„ CGlass æ˜¯å¦å‘½ä¸­å½“前 Filters
// useEndTime=true æ—¶ç”¨ tEnd åˆ¤æ—¶é—´ï¼ˆæ¯”如“完成列表”用 t_end),默认按 tStart。
bool CPageGlassList::GlassMatchesFilters(const SERVO::CGlass& g,
    const GlassLogDb::Filters& f,
    bool useEndTime/* = false*/)
{
    // 1) ç²¾ç¡®å­—段
    if (f.classId && g.getID() != *f.classId)      return false;
    if (f.cassetteSeqNo && g.getCassetteSequenceNo() != *f.cassetteSeqNo) return false;
    if (f.jobSeqNo && g.getJobSequenceNo() != *f.jobSeqNo)     return false;
    // 2) å…³é”®å­—(与 DB ä¿æŒä¸€è‡´ï¼šclass_id / buddy_id / path / params / pretty)
    if (f.keyword) {
        const std::string& kw = *f.keyword;
        if (!(CToolUnits::containsCI(g.getID(), kw)
            || CToolUnits::containsCI(g.getBuddyId(), kw)
            || CToolUnits::containsCI(g.getPathDescription(), kw)
            || CToolUnits::containsCI(g.getParamsDescription(), kw)))
            return false;
    }
    // 3) æ—¶é—´ï¼ˆä¸Ž DB ä¿æŒä¸€è‡´ï¼šé»˜è®¤æŒ‰ t_start è¿‡æ»¤ï¼›éœ€è¦å¯åˆ‡åˆ° t_end)
    if (f.tStartFrom || f.tStartTo) {
        std::optional<std::chrono::system_clock::time_point> tp = useEndTime ? g.tEnd() : g.tStart();
        // çº¦å®šï¼šè‹¥æ²¡æœ‰å¯¹åº”时间戳,则视为不命中(与 DB ç›¸åŒï¼šNULL ä¸ä¼šå‘½ä¸­èŒƒå›´ï¼‰
        if (!tp) return false;
        if (f.tStartFrom && *tp < *f.tStartFrom) return false;
        if (f.tStartTo && *tp > *f.tStartTo)   return false;
    }
    return true;
}
SourceCode/Bond/Servo/CPageGlassList.h
@@ -1,5 +1,6 @@
#pragma once
#include "ListCtrlEx.h"
#include "CExpandableListCtrl.h"
#include "GlassLogDb.h"
// CPageGlassList å¯¹è¯æ¡†
@@ -18,8 +19,8 @@
    IObserver* m_pObserver;
    // æœç´¢å…³é”®å­—
    GlassLogDb::Filters m_filters;
    std::string m_strStatus;
    std::string m_strKeyword;
    // é¡µç 
    int m_nCurPage;
@@ -32,7 +33,11 @@
    // æŽ§ä»¶
    CDateTimeCtrl m_dateTimeStart;
    CDateTimeCtrl m_dateTimeEnd;
    CListCtrlEx m_listCtrl;
    CExpandableListCtrl m_listCtrl;
private:
    int  m_nColCount = 0;
    bool m_rebuilding = false;
private:
    void InitRxWindow();
@@ -40,14 +45,16 @@
    void InitStatusCombo();
    void InitTimeRangeCombo();
    void InitDateTimeControls();
    void LoadTransfers();
    void LoadData();
    void UpdatePageData();
    void UpdatePageControls();
    void UpdateDateFilter();
    /*
    void FillDataToListCtrl(const std::vector<TransferData>& vecData);
    void InsertTransferData(const TransferData& data);
    */
    void InsertWipRow(SERVO::CGlass* pGlass);
    static bool GlassMatchesFilters(const SERVO::CGlass& g,
        const GlassLogDb::Filters& f,
        bool useEndTime = false);
    void UpdateWipData();
    bool eraseGlassInVector(SERVO::CGlass* pGlass, std::vector<SERVO::CGlass*>& glasses);
    void UpdateWipRow(unsigned int index, SERVO::CGlass* pGlass);
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
@@ -67,5 +74,6 @@
    afx_msg void OnBnClickedButtonExport();
    afx_msg void OnBnClickedButtonPrevPage();
    afx_msg void OnBnClickedButtonNextPage();
    afx_msg void OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult);
    DECLARE_MESSAGE_MAP()
};
SourceCode/Bond/Servo/CPath.cpp
@@ -1,6 +1,7 @@
#include "stdafx.h"
#include "CPath.h"
#include "ToolUnits.h"
#include "CServoUtilsTool.h"
namespace SERVO {
@@ -15,10 +16,11 @@
        m_pNext = nullptr;
    }
    CPath::CPath(unsigned int nEqId, unsigned int nUnit)
    CPath::CPath(unsigned int nEqId, unsigned int nUnit, unsigned int nSlot)
    {
        m_nEqID = nEqId;
        m_nUnit = nUnit;
        m_nSlot = nSlot;
        m_timeOut = 0;
        m_timeIn = CToolUnits::getTimestamp();
        m_bProcessed = FALSE;
@@ -46,11 +48,17 @@
        strOut = strOut + ">";
    }
    void CPath::getSimpleDescription(std::string& strOut)
    {
        strOut = CServoUtilsTool::getEqUnitName(m_nEqID, m_nUnit, m_nSlot);
    }
    void CPath::serialize(CArchive& ar)
    {
        if (ar.IsStoring()) {
            ar << m_nEqID;
            ar << m_nUnit;
            ar << m_nSlot;
            ar << m_timeIn;
            ar << m_timeOut;
            ar << m_bProcessed;
@@ -65,6 +73,7 @@
            ar >> m_nEqID;
            ar >> m_nUnit;
            ar >> m_nSlot;
            ar >> m_timeIn;
            ar >> m_timeOut;
            ar >> m_bProcessed;
@@ -90,6 +99,11 @@
        return m_nUnit;
    }
    unsigned int CPath::getSlot()
    {
        return m_nSlot;
    }
    void CPath::setInTime(ULONGLONG time)
    {
        m_timeIn = time;
SourceCode/Bond/Servo/CPath.h
@@ -7,11 +7,12 @@
    {
    public:
        CPath();
        CPath(unsigned int nEqId, unsigned int nUnit);
        CPath(unsigned int nEqId, unsigned int nUnit, unsigned int nSlot);
        virtual ~CPath();
    public:
        void getDescription(std::string& strOut);
        void getSimpleDescription(std::string& strOut);
        void serialize(CArchive& ar);
        CPath* getPrev();
        CPath* getNext();
@@ -20,6 +21,7 @@
        CPath* getHeadPath();
        unsigned int getEqID();
        unsigned int getUnit();
        unsigned int getSlot();
        void setInTime(ULONGLONG time);
        ULONGLONG getInTime();
        void setOutTime(ULONGLONG time);
@@ -32,6 +34,7 @@
    private:    
        unsigned int m_nEqID;
        unsigned int m_nUnit;
        unsigned int m_nSlot;
        ULONGLONG m_timeIn;
        ULONGLONG m_timeOut;
        BOOL m_bProcessed;
SourceCode/Bond/Servo/CRobotTask.cpp
@@ -220,7 +220,7 @@
                    LOGI(_T("RobotTask已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask下发失败"));
                    LOGE(_T("RobotTask下发失败"));
                }
                return 0;
@@ -238,7 +238,7 @@
                    LOGI(_T("RobotTask/get已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask/get已下发失败"));
                    LOGE(_T("RobotTask/get已下发失败"));
                }
                return 0;
@@ -261,7 +261,7 @@
                    LOGI(_T("RobotTask/put已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask/put已下发失败"));
                    LOGE(_T("RobotTask/put已下发失败"));
                }
                return 0;
@@ -279,7 +279,7 @@
                    LOGI(_T("RobotTask/restore-put已下发到EFEM"));
                }
                else {
                    LOGI(_T("RobotTask/restore-put已下发失败"));
                    LOGE(_T("RobotTask/restore-put已下发失败"));
                }
                return 0;
SourceCode/Bond/Servo/CSVData.cpp
@@ -44,12 +44,12 @@
        int index = 0;
        CSVData svData;
        CToolUnits::convertString(&pszBuffer[index], 8, m_strTime);
        index += 8;
        CToolUnits::convertString(&pszBuffer[index], 8 * 2, m_strTime);
        index += 8 * 2;
        m_svRawData.clear();
        m_svRawData.insert(m_svRawData.end(), (uint8_t*)(pszBuffer), (uint8_t*)(pszBuffer)+125);
        index += 125;
        m_svRawData.insert(m_svRawData.end(), (uint8_t*)(&pszBuffer[index]), (uint8_t*)(pszBuffer)+(125 * 2));
        index += 125 * 2;
        return 133;
    }
SourceCode/Bond/Servo/CServoUtilsTool.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
#include "stdafx.h"
#include "CServoUtilsTool.h"
#include "Common.h"
namespace SERVO {
    CServoUtilsTool::CServoUtilsTool()
    {
    }
    CServoUtilsTool::~CServoUtilsTool()
    {
    }
    std::string CServoUtilsTool::getEqUnitName(int eqid, int unit)
    {
        char szBuffer[256];
        if (eqid == EQ_ID_LOADPORT1
            || eqid == EQ_ID_LOADPORT2
            || eqid == EQ_ID_LOADPORT3
            || eqid == EQ_ID_LOADPORT4
            ) {
            sprintf_s(szBuffer, 256, "Port%d(Slot%d)", eqid - EQ_ID_LOADPORT1 + 1, unit + 1);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_ALIGNER) {
            return "Aligner";
        }
        if (eqid == EQ_ID_FLIPER) {
            return "Fliper";
        }
        if (eqid == EQ_ID_VACUUMBAKE) {
            if (unit == 0) return "烘烤A腔";
            if (unit == 1) return "烘烤B腔";
        }
        if (eqid == EQ_ID_VACUUMBAKE) {
            if (unit == 0) return "烘烤A腔";
            if (unit == 1) return "烘烤B腔";
        }
        if (eqid == EQ_ID_Bonder1) {
            return "Bonder1";
        }
        if (eqid == EQ_ID_Bonder2) {
            return "Bonder2";
        }
        if (eqid == EQ_ID_BAKE_COOLING) {
            if (unit == 0) return "后烘烤A腔";
            if (unit == 1) return "冷却A";
            if (unit == 0) return "后烘烤B腔";
            if (unit == 1) return "冷却B";
        }
        if (eqid == EQ_ID_MEASUREMENT) {
            return "AOI";
        }
        if (eqid == EQ_ID_ARM_TRAY1) {
            return "ARM1";
        }
        if (eqid == EQ_ID_ARM_TRAY2) {
            return "ARM2";
        }
        return "";
    }
    std::string CServoUtilsTool::getEqUnitName(int eqid, int unit, int slot)
    {
        char szBuffer[256];
        if (eqid == EQ_ID_LOADPORT1
            || eqid == EQ_ID_LOADPORT2
            || eqid == EQ_ID_LOADPORT3
            || eqid == EQ_ID_LOADPORT4
            ) {
            sprintf_s(szBuffer, 256, "Port%d(Slot%d)", eqid - EQ_ID_LOADPORT1 + 1, slot);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_ALIGNER) {
            return "Aligner";
        }
        if (eqid == EQ_ID_FLIPER) {
            return "Fliper";
        }
        if (eqid == EQ_ID_VACUUMBAKE) {
            if (unit == 0) {
                sprintf_s(szBuffer, 256, "烘烤A腔(Slot%d)", slot);
                return std::string(szBuffer);
            }
            if (unit == 1) {
                sprintf_s(szBuffer, 256, "烘烤B腔(Slot%d)", slot);
                return std::string(szBuffer);
            }
        }
        if (eqid == EQ_ID_Bonder1) {
            sprintf_s(szBuffer, 256, "Bonder1(Slot%d)", slot);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_Bonder2) {
            sprintf_s(szBuffer, 256, "Bonder2(Slot%d)", slot);
            return std::string(szBuffer);
        }
        if (eqid == EQ_ID_BAKE_COOLING) {
            if (slot == 0) return "后烘烤A腔";
            if (slot == 1) return "冷却A";
            if (slot == 2) return "后烘烤B腔";
            if (slot == 3) return "冷却B";
        }
        if (eqid == EQ_ID_MEASUREMENT) {
            return "AOI";
        }
        if (eqid == EQ_ID_ARM_TRAY1) {
            return "ARM1";
        }
        if (eqid == EQ_ID_ARM_TRAY2) {
            return "ARM2";
        }
        return "";
    }
    std::string CServoUtilsTool::getMaterialsTypeText(MaterialsType type)
    {
        if (type == MaterialsType::G1) {
            return "G1";
        }
        if (type == MaterialsType::G2) {
            return "G2";
        }
        return "";
    }
    std::string CServoUtilsTool::getGlassStateText(SERVO::GlsState state)
    {
        switch (state)
        {
        case SERVO::GlsState::NoState:
            return "NoState";
            break;
        case SERVO::GlsState::Queued:
            return "Queued";
            break;
        case SERVO::GlsState::InProcess:
            return "InProcess";
            break;
        case SERVO::GlsState::Paused:
            return "Paused";
            break;
        case SERVO::GlsState::Completed:
            return "Completed";
            break;
        case SERVO::GlsState::Aborted:
            return "Aborted";
            break;
        case SERVO::GlsState::Failed:
            return "Failed";
            break;
        default:
            return "";
            break;
        }
    }
    std::string CServoUtilsTool::getInspResultText(SERVO::InspResult result)
    {
        switch (result)
        {
        case SERVO::InspResult::NotInspected:
            return "";
            break;
        case SERVO::InspResult::Pass:
            return "Pass";
            break;
        case SERVO::InspResult::Fail:
            return "Fail";
            break;
        default:
            return "";
            break;
        }
    }
}
SourceCode/Bond/Servo/CServoUtilsTool.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
#pragma once
#include "ServoCommo.h"
#include "CGlass.h"
namespace SERVO {
    class CServoUtilsTool
    {
    public:
        CServoUtilsTool();
        virtual ~CServoUtilsTool();
    public:
        static std::string getEqUnitName(int eqid, int unit);
        static std::string getEqUnitName(int eqid, int unit, int slot);
        static std::string getMaterialsTypeText(MaterialsType type);
        static std::string getGlassStateText(SERVO::GlsState state);
        static std::string getInspResultText(SERVO::InspResult result);
    };
}
SourceCode/Bond/Servo/Configuration.cpp
@@ -176,3 +176,17 @@
    return GetPrivateProfileInt(_T("Master"), _T("CTRound"), 0, m_strFilepath);
}
char* pszPortSection[] = { "Port1", "Port2", "Port3", "Port4" };
int CConfiguration::getPortCassetteSnSeed(int port)
{
    ASSERT(1 <= port && port <= 4);
    return GetPrivateProfileInt(pszPortSection[port-1], _T("CassetteSnSeed"), 0, m_strFilepath);
}
void CConfiguration::setPortCassetteSnSeed(int port, int seed)
{
    ASSERT(1 <= port && port <= 4);
    WritePrivateProfileString(pszPortSection[port - 1], _T("CassetteSnSeed"),
        std::to_string(seed).c_str(), m_strFilepath);
}
SourceCode/Bond/Servo/Configuration.h
@@ -30,6 +30,8 @@
    BOOL isJobMode();
    void setContinuousTransferCount(int round);
    int getContinuousTransferCount();
    int getPortCassetteSnSeed(int port);
    void setPortCassetteSnSeed(int port, int seed);
public:
    void setP2RemoteEqReconnectInterval(int second);
SourceCode/Bond/Servo/DevicePropertyDlg.cpp
@@ -41,7 +41,7 @@
    // TODO:  åœ¨æ­¤æ·»åŠ é¢å¤–çš„åˆå§‹åŒ–
    SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(m_nDeviceID);
    if (nullptr == pEquipment) {
        LOGI("<设备ID:%d>获取设备属性失败。", m_nDeviceID);
        LOGE("<设备ID:%d>获取设备属性失败。", m_nDeviceID);
        return FALSE;
    }
SourceCode/Bond/Servo/GlassJson.cpp
@@ -178,6 +178,7 @@
                Json::Value n(Json::objectValue);
                n["eq_id"] = p->getEqID();
                n["unit"] = p->getUnit();
                n["slot"] = p->getUnit();
                put_ull_as_str(n, "time_in", static_cast<unsigned long long>(p->getInTime()));
                put_ull_as_str(n, "time_out", static_cast<unsigned long long>(p->getOutTime()));
                n["processed"] = p->isProcessEnd() ? true : false;
@@ -199,7 +200,7 @@
    // åŸºæœ¬
    g.setID(JStr(root, "id").c_str());
    g.setType(static_cast<MaterialsType>(JInt(root, "materials", 0)));
    g.getBuddyId() = JStr(root, "buddy_id");
    g.setBuddyId(JStr(root, "buddy_id"));
    g.setScheduledForProcessing(JBool(root, "scheduled") ? TRUE : FALSE);
    g.m_failReason = JStr(root, "fail_reason");
    g.setOriginPort(JInt(root, "origin_port", 0), JInt(root, "origin_slot", 0));
@@ -293,7 +294,8 @@
        for (const auto& n : root["path"]) {
            unsigned eq = JUInt(n, "eq_id", 0);
            unsigned unit = JUInt(n, "unit", 0);
            g.addPath(eq, unit);
            unsigned slot = JUInt(n, "slot", 0);
            g.addPath(eq, unit, slot);
            CPath* tail = nullptr;
            if (auto head = g.getPath()) tail = head->getTailPath();
SourceCode/Bond/Servo/GlassLogDb.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,526 @@
// GlassLogDb.cpp - å•例封装:SQLite å†™å…¥/查询/分页/统计/CSV å¯¼å‡ºï¼ˆå·²å¯¹æŽ¥ SERVO::CGlass)
#include "stdafx.h"
#include "GlassLogDb.h"
#include "sqlite3.h"
#include <stdexcept>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <fstream>
#include "GlassJson.h"
using namespace SERVO;
#ifndef GLASS_LOG_TABLE
#define GLASS_LOG_TABLE "glass_log"
#endif
// ================== å·¥å…·å‡½æ•° ==================
static void throwIf(int rc, sqlite3* db, const char* msg) {
    if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW) {
        std::ostringstream oss;
        oss << msg << " (rc=" << rc << "): " << (db ? sqlite3_errmsg(db) : "null db");
        throw std::runtime_error(oss.str());
    }
}
static inline const char* safe_text(sqlite3_stmt* s, int col) {
    const unsigned char* p = sqlite3_column_text(s, col);
    return p ? reinterpret_cast<const char*>(p) : "";
}
static std::string csvEscape(const std::string& s) {
    bool needQuote = s.find_first_of(",\"\n\r") != std::string::npos;
    if (!needQuote) return s;
    std::string out; out.reserve(s.size() + 2);
    out.push_back('"');
    for (char c : s) out += (c == '"') ? "\"\"" : std::string(1, c);
    out.push_back('"');
    return out;
}
static std::string toIso8601String(const std::optional<std::chrono::system_clock::time_point>& tp) {
    if (!tp.has_value()) return "";
    using namespace std::chrono;
    auto t = system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
    return oss.str();
}
// ================== å•例静态成员 ==================
std::unique_ptr<GlassLogDb> GlassLogDb::s_inst;
std::mutex GlassLogDb::s_instMtx;
// ================== å•例接口实现 ==================
void GlassLogDb::Init(const std::string& dbPath) {
    std::lock_guard<std::mutex> g(s_instMtx);
    if (!s_inst) {
        s_inst.reset(new GlassLogDb(dbPath));
    }
    else if (s_inst->dbPath_ != dbPath) {
        s_inst->reopenInternal(dbPath);
    }
}
GlassLogDb& GlassLogDb::Instance() {
    std::lock_guard<std::mutex> g(s_instMtx);
    if (!s_inst) throw std::runtime_error("GlassLogDb::Instance() called before Init()");
    return *s_inst;
}
bool GlassLogDb::IsInitialized() noexcept {
    std::lock_guard<std::mutex> g(s_instMtx);
    return static_cast<bool>(s_inst);
}
void GlassLogDb::Reopen(const std::string& dbPath) {
    std::lock_guard<std::mutex> g(s_instMtx);
    if (!s_inst) {
        s_inst.reset(new GlassLogDb(dbPath));
    }
    else {
        s_inst->reopenInternal(dbPath);
    }
}
std::string GlassLogDb::CurrentPath() {
    std::lock_guard<std::mutex> g(s_instMtx);
    return s_inst ? s_inst->dbPath_ : std::string();
}
// ================== æž„造/打开/关闭/重开 ==================
GlassLogDb::GlassLogDb(const std::string& dbPath) {
    openDb(dbPath);
}
void GlassLogDb::openDb(const std::string& dbPath) {
    int rc = sqlite3_open_v2(dbPath.c_str(), &db_,
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
        nullptr);
    if (rc != SQLITE_OK) {
        std::string err = db_ ? sqlite3_errmsg(db_) : "open failed";
        if (db_) sqlite3_close(db_);
        db_ = nullptr;
        throw std::runtime_error("Failed to open sqlite DB: " + err + " | path=" + dbPath);
    }
    dbPath_ = dbPath;
    ensureSchema();
    prepareStatements();
}
void GlassLogDb::closeDb() noexcept {
    finalizeStatements();
    if (db_) {
        sqlite3_close(db_);
        db_ = nullptr;
    }
}
void GlassLogDb::reopenInternal(const std::string& dbPath) {
    std::lock_guard<std::mutex> lk(mtx_);
    if (dbPath == dbPath_) return;
    closeDb();
    openDb(dbPath);
}
// ================== æžæž„ ==================
GlassLogDb::~GlassLogDb() {
    closeDb();
}
// ================== DDL & é¢„编译 ==================
void GlassLogDb::ensureSchema() {
    const char* ddl =
        "CREATE TABLE IF NOT EXISTS " GLASS_LOG_TABLE " ("
        "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
        "  cassette_seq_no INTEGER,"
        "  job_seq_no INTEGER,"
        "  class_id TEXT,"
        "  material_type INTEGER,"
        "  state INTEGER,"
        "  t_start TEXT,"      // ISO8601 UTC(可为 NULL)
        "  t_end TEXT,"        // ISO8601 UTC(可为 NULL)
        "  buddy_id TEXT,"
        "  aoi_result INTEGER,"
        "  path TEXT,"
        "  params TEXT,"
        "  pretty TEXT"
        ");"
        "CREATE INDEX IF NOT EXISTS idx_glass_key "
        "  ON " GLASS_LOG_TABLE " (cassette_seq_no, job_seq_no);"
        "CREATE INDEX IF NOT EXISTS idx_class_id "
        "  ON " GLASS_LOG_TABLE " (class_id);"
        "CREATE INDEX IF NOT EXISTS idx_t_start "
        "  ON " GLASS_LOG_TABLE " (t_start);";
    char* errMsg = nullptr;
    int rc = sqlite3_exec(db_, ddl, nullptr, nullptr, &errMsg);
    if (rc != SQLITE_OK) {
        std::string err = errMsg ? errMsg : "unknown";
        sqlite3_free(errMsg);
        throw std::runtime_error("Failed to create schema: " + err);
    }
}
void GlassLogDb::prepareStatements() {
    const char* sql =
        "INSERT INTO " GLASS_LOG_TABLE " ("
        " cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " t_start, t_end, buddy_id, aoi_result, path, params, pretty)"
        " VALUES (?,?,?,?,?,?,?,?,?,?,?,?);";
    int rc = sqlite3_prepare_v2(db_, sql, -1, &stmtInsert_, nullptr);
    throwIf(rc, db_, "prepare insert");
}
void GlassLogDb::finalizeStatements() noexcept {
    if (stmtInsert_) {
        sqlite3_finalize(stmtInsert_);
        stmtInsert_ = nullptr;
    }
}
// ================== äº‹åŠ¡ ==================
void GlassLogDb::beginTransaction() {
    std::lock_guard<std::mutex> lk(mtx_);
    char* err = nullptr;
    int rc = sqlite3_exec(db_, "BEGIN IMMEDIATE;", nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        std::string e = err ? err : "unknown";
        sqlite3_free(err);
        throw std::runtime_error("BEGIN failed: " + e);
    }
}
void GlassLogDb::commit() {
    std::lock_guard<std::mutex> lk(mtx_);
    char* err = nullptr;
    int rc = sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        std::string e = err ? err : "unknown";
        sqlite3_free(err);
        throw std::runtime_error("COMMIT failed: " + e);
    }
}
void GlassLogDb::rollback() {
    std::lock_guard<std::mutex> lk(mtx_);
    char* err = nullptr;
    sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, &err);
    if (err) sqlite3_free(err);
}
// ================== æ—¶é—´æ ¼å¼ ==================
std::string GlassLogDb::toIso8601Utc(std::chrono::system_clock::time_point tp) {
    using namespace std::chrono;
    auto t = system_clock::to_time_t(tp);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
    return oss.str();
}
// ================== æ’入实现 ==================
long long GlassLogDb::doInsert(
    int cassetteSeqNo,
    int jobSeqNo,
    const std::string& classId,
    int materialType,
    int state,
    std::optional<std::chrono::system_clock::time_point> tStart,
    std::optional<std::chrono::system_clock::time_point> tEnd,
    const std::string& buddyId,
    int aoiResult,
    const std::string& pathDesc,
    const std::string& paramsDesc,
    const std::string& prettyString)
{
    std::lock_guard<std::mutex> lk(mtx_);
    int idx = 1;
    throwIf(sqlite3_reset(stmtInsert_), db_, "reset insert");
    throwIf(sqlite3_clear_bindings(stmtInsert_), db_, "clear bindings");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, cassetteSeqNo), db_, "bind cassette");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, jobSeqNo), db_, "bind job");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, classId.c_str(), -1, SQLITE_TRANSIENT), db_, "bind class_id");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, materialType), db_, "bind material_type");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, state), db_, "bind state");
    if (tStart.has_value()) {
        auto s = toIso8601Utc(*tStart);
        throwIf(sqlite3_bind_text(stmtInsert_, idx++, s.c_str(), -1, SQLITE_TRANSIENT), db_, "bind t_start");
    }
    else {
        throwIf(sqlite3_bind_null(stmtInsert_, idx++), db_, "bind t_start null");
    }
    if (tEnd.has_value()) {
        auto e = toIso8601Utc(*tEnd);
        throwIf(sqlite3_bind_text(stmtInsert_, idx++, e.c_str(), -1, SQLITE_TRANSIENT), db_, "bind t_end");
    }
    else {
        throwIf(sqlite3_bind_null(stmtInsert_, idx++), db_, "bind t_end null");
    }
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, buddyId.c_str(), -1, SQLITE_TRANSIENT), db_, "bind buddy_id");
    throwIf(sqlite3_bind_int(stmtInsert_, idx++, aoiResult), db_, "bind aoi_result");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, pathDesc.c_str(), -1, SQLITE_TRANSIENT), db_, "bind path");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, paramsDesc.c_str(), -1, SQLITE_TRANSIENT), db_, "bind params");
    throwIf(sqlite3_bind_text(stmtInsert_, idx++, prettyString.c_str(), -1, SQLITE_TRANSIENT), db_, "bind pretty");
    int rc = sqlite3_step(stmtInsert_);
    throwIf(rc, db_, "insert step");
    return sqlite3_last_insert_rowid(db_);
}
long long GlassLogDb::insertFromCGlass(CGlass& g)
{
    const int cassette = static_cast<int>(g.getCassetteSequenceNo());
    const int job = static_cast<int>(g.getJobSequenceNo());
    const std::string& classId = g.getID();
    const int material = static_cast<int>(g.getType());
    const int state = static_cast<int>(g.state());
    const std::string& buddy = g.getBuddyId();
    const int aoiResult = static_cast<int>(g.getAOIInspResult());
    const std::string pathDesc = g.getPathDescription();
    const std::string paramsDesc = g.getParamsDescription();
    const std::string pretty = GlassJson::ToPrettyString(g);
    auto tStart = g.tStart();
    auto tEnd = g.tEnd();
    return doInsert(cassette, job, classId, material, state,
        tStart, tEnd, buddy, aoiResult, pathDesc, paramsDesc, pretty);
}
long long GlassLogDb::insertExplicit(
    int cassetteSeqNo,
    int jobSeqNo,
    const std::string& classId,
    int materialType,
    int state,
    std::optional<std::chrono::system_clock::time_point> tStart,
    std::optional<std::chrono::system_clock::time_point> tEnd,
    const std::string& buddyId,
    int aoiResult,
    const std::string& pathDesc,
    const std::string& paramsDesc,
    const std::string& prettyString)
{
    return doInsert(cassetteSeqNo, jobSeqNo, classId, materialType, state,
        tStart, tEnd, buddyId, aoiResult, pathDesc, paramsDesc, prettyString);
}
// ================== è¿‡æ»¤æž„造(where + bind) ==================
static std::string buildWhereSql(const GlassLogDb::Filters& f) {
    std::ostringstream where;
    bool first = true;
    auto add = [&](const std::string& cond) {
        if (first) { where << " WHERE " << cond; first = false; }
        else { where << " AND " << cond; }
    };
    if (f.classId)       add("class_id = ?");
    if (f.cassetteSeqNo) add("cassette_seq_no = ?");
    if (f.jobSeqNo)      add("job_seq_no = ?");
    if (f.keyword)       add("(class_id LIKE ? OR buddy_id LIKE ? OR path LIKE ? OR params LIKE ? OR pretty LIKE ?)");
    if (f.tStartFrom && f.tStartTo) add("(t_start >= ? AND t_start <= ?)");
    else if (f.tStartFrom)          add("t_start >= ?");
    else if (f.tStartTo)            add("t_start <= ?");
    return where.str();
}
static void bindFilters(sqlite3_stmt* stmt, sqlite3* db, int& idx, const GlassLogDb::Filters& f) {
    if (f.classId)       throwIf(sqlite3_bind_text(stmt, idx++, f.classId->c_str(), -1, SQLITE_TRANSIENT), db, "bind classId");
    if (f.cassetteSeqNo) throwIf(sqlite3_bind_int(stmt, idx++, *f.cassetteSeqNo), db, "bind cassette");
    if (f.jobSeqNo)      throwIf(sqlite3_bind_int(stmt, idx++, *f.jobSeqNo), db, "bind job");
    if (f.keyword) {
        std::string kw = "%" + *f.keyword + "%";
        for (int i = 0; i < 5; ++i)
            throwIf(sqlite3_bind_text(stmt, idx++, kw.c_str(), -1, SQLITE_TRANSIENT), db, "bind keyword");
    }
    if (f.tStartFrom && f.tStartTo) {
        std::string s = toIso8601String(f.tStartFrom);
        std::string e = toIso8601String(f.tStartTo);
        throwIf(sqlite3_bind_text(stmt, idx++, s.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartFrom");
        throwIf(sqlite3_bind_text(stmt, idx++, e.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartTo");
    }
    else if (f.tStartFrom) {
        std::string s = toIso8601String(f.tStartFrom);
        throwIf(sqlite3_bind_text(stmt, idx++, s.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartFrom");
    }
    else if (f.tStartTo) {
        std::string e = toIso8601String(f.tStartTo);
        throwIf(sqlite3_bind_text(stmt, idx++, e.c_str(), -1, SQLITE_TRANSIENT), db, "bind tStartTo");
    }
}
// ================== æŸ¥è¯¢ / ç»Ÿè®¡ / åˆ†é¡µ ==================
std::vector<GlassLogDb::Row> GlassLogDb::query(
    const Filters& filters,
    int limit,
    int offset)
{
    std::lock_guard<std::mutex> lk(mtx_);
    std::ostringstream sql;
    sql << "SELECT id, cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), ''),"
        " buddy_id, aoi_result, path, params, pretty"
        " FROM " GLASS_LOG_TABLE;
    std::string where = buildWhereSql(filters);
    sql << where
        << " ORDER BY id DESC"
        << " LIMIT " << (limit < 0 ? 0 : limit)
        << " OFFSET " << (offset < 0 ? 0 : offset);
    sqlite3_stmt* stmt = nullptr;
    throwIf(sqlite3_prepare_v2(db_, sql.str().c_str(), -1, &stmt, nullptr), db_, "prepare query");
    int idx = 1;
    bindFilters(stmt, db_, idx, filters);
    std::vector<Row> rows;
    for (;;) {
        int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            Row r;
            r.id = sqlite3_column_int64(stmt, 0);
            r.cassetteSeqNo = sqlite3_column_int(stmt, 1);
            r.jobSeqNo = sqlite3_column_int(stmt, 2);
            r.classId = safe_text(stmt, 3);
            r.materialType = sqlite3_column_int(stmt, 4);
            r.state = sqlite3_column_int(stmt, 5);
            r.tStart = safe_text(stmt, 6);
            r.tEnd = safe_text(stmt, 7);
            r.buddyId = safe_text(stmt, 8);
            r.aoiResult = sqlite3_column_int(stmt, 9);
            r.path = safe_text(stmt, 10);
            r.params = safe_text(stmt, 11);
            r.pretty = safe_text(stmt, 12);
            rows.push_back(std::move(r));
        }
        else if (rc == SQLITE_DONE) {
            break;
        }
        else {
            sqlite3_finalize(stmt);
            throwIf(rc, db_, "query step");
        }
    }
    sqlite3_finalize(stmt);
    return rows;
}
long long GlassLogDb::count(const Filters& filters) {
    std::lock_guard<std::mutex> lk(mtx_);
    std::ostringstream sql;
    sql << "SELECT COUNT(1) FROM " GLASS_LOG_TABLE;
    std::string where = buildWhereSql(filters);
    sql << where;
    sqlite3_stmt* stmt = nullptr;
    throwIf(sqlite3_prepare_v2(db_, sql.str().c_str(), -1, &stmt, nullptr), db_, "prepare count");
    int idx = 1;
    bindFilters(stmt, db_, idx, filters);
    long long total = 0;
    int rc = sqlite3_step(stmt);
    if (rc == SQLITE_ROW) {
        total = sqlite3_column_int64(stmt, 0);
    }
    else if (rc != SQLITE_DONE) {
        sqlite3_finalize(stmt);
        throwIf(rc, db_, "count step");
    }
    sqlite3_finalize(stmt);
    return total;
}
GlassLogDb::Page GlassLogDb::queryPaged(
    const Filters& filters,
    int limit,
    int offset)
{
    Page p;
    p.limit = (limit < 0 ? 0 : limit);
    p.offset = (offset < 0 ? 0 : offset);
    p.total = count(filters);
    p.items = query(filters, p.limit, p.offset);
    return p;
}
// ================== å¯¼å‡º CSV(全部符合条件) ==================
long long GlassLogDb::exportCsv(const std::string& csvPath, const Filters& filters) {
    std::lock_guard<std::mutex> lk(mtx_);
    std::ofstream ofs(csvPath, std::ios::binary);
    if (!ofs) {
        throw std::runtime_error("Failed to open csv for write: " + csvPath);
    }
    // è¡¨å¤´
    ofs << "id,cassette_seq_no,job_seq_no,class_id,material_type,state,t_start,t_end,"
        "buddy_id,aoi_result,path,params,pretty\n";
    std::ostringstream sql;
    sql << "SELECT id, cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), ''),"
        " buddy_id, aoi_result, path, params, pretty"
        " FROM " GLASS_LOG_TABLE;
    std::string where = buildWhereSql(filters);
    sql << where << " ORDER BY id DESC";
    sqlite3_stmt* stmt = nullptr;
    throwIf(sqlite3_prepare_v2(db_, sql.str().c_str(), -1, &stmt, nullptr), db_, "prepare export");
    int idx = 1;
    bindFilters(stmt, db_, idx, filters);
    long long rows = 0;
    for (;;) {
        int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            std::string id = std::to_string(sqlite3_column_int64(stmt, 0));
            std::string cassette = std::to_string(sqlite3_column_int(stmt, 1));
            std::string job = std::to_string(sqlite3_column_int(stmt, 2));
            std::string class_id = safe_text(stmt, 3);
            std::string material = std::to_string(sqlite3_column_int(stmt, 4));
            std::string state = std::to_string(sqlite3_column_int(stmt, 5));
            std::string t_start = safe_text(stmt, 6);
            std::string t_end = safe_text(stmt, 7);
            std::string buddy = safe_text(stmt, 8);
            std::string aoi = std::to_string(sqlite3_column_int(stmt, 9));
            std::string path = safe_text(stmt, 10);
            std::string params = safe_text(stmt, 11);
            std::string pretty = safe_text(stmt, 12);
            ofs << id << ','
                << cassette << ','
                << job << ','
                << csvEscape(class_id) << ','
                << material << ','
                << state << ','
                << t_start << ','
                << t_end << ','
                << csvEscape(buddy) << ','
                << aoi << ','
                << csvEscape(path) << ','
                << csvEscape(params) << ','
                << csvEscape(pretty) << '\n';
            ++rows;
        }
        else if (rc == SQLITE_DONE) {
            break;
        }
        else {
            sqlite3_finalize(stmt);
            throwIf(rc, db_, "export step");
        }
    }
    sqlite3_finalize(stmt);
    ofs.flush();
    return rows;
}
SourceCode/Bond/Servo/GlassLogDb.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,170 @@
#pragma once
// GlassLogDb.h - å•例封装:SQLite å†™å…¥/查询/分页/统计/CSV å¯¼å‡ºï¼ˆå·²å¯¹æŽ¥ SERVO::CGlass)
#include <string>
#include <vector>
#include <mutex>
#include <optional>
#include <chrono>
#include <cstdint>
#include <memory>   // std::unique_ptr
#include "CGlass.h"   // éœ€è¦ SERVO::CGlass
// å¯åœ¨ç¼–译命令或本头文件前自定义表名:#define GLASS_LOG_TABLE "your_table_name"
#ifndef GLASS_LOG_TABLE
#define GLASS_LOG_TABLE "glass_log"
#endif
// å‰ç½®å£°æ˜Žï¼Œé¿å…å¤´æ–‡ä»¶ä¾èµ– sqlite3.h
struct sqlite3;
struct sqlite3_stmt;
class GlassLogDb {
public:
    // ====== å•例接口 ======
    // åˆå§‹åŒ–(或切换)数据库文件路径;第一次调用会创建实例
    static void Init(const std::string& dbPath);
    // èŽ·å–å…¨å±€å®žä¾‹ï¼›è‹¥æœªåˆå§‹åŒ–ä¼šæŠ›å¼‚å¸¸
    static GlassLogDb& Instance();
    // æ˜¯å¦å·²åˆå§‹åŒ–
    static bool IsInitialized() noexcept;
    // è¿è¡Œä¸­åˆ‡æ¢åˆ°å¦ä¸€ä»½æ•°æ®åº“文件(等同 Init,不存在则创建)
    static void Reopen(const std::string& dbPath);
    // å½“前数据库文件路径(未初始化返回空串)
    static std::string CurrentPath();
    // ====== äº‹åŠ¡æŽ§åˆ¶ ======
    void beginTransaction();
    void commit();
    void rollback();
    // ====== å†™å…¥ ======
    // ç›´æŽ¥ä»Ž SERVO::CGlass å†™å…¥ï¼ˆæ³¨æ„ï¼šå‚数为 éž const å¼•用,因为你的若干 getter éž const)
    long long insertFromCGlass(SERVO::CGlass& g);
    // æ˜¾å¼å‚数插入(tStart/tEnd å¯ç©ºï¼›å‡å†™å…¥ UTC ISO8601 æ–‡æœ¬æˆ– NULL)
    long long insertExplicit(
        int cassetteSeqNo,
        int jobSeqNo,
        const std::string& classId,
        int materialType,
        int state,
        std::optional<std::chrono::system_clock::time_point> tStart,
        std::optional<std::chrono::system_clock::time_point> tEnd,
        const std::string& buddyId,
        int aoiResult,
        const std::string& pathDesc,
        const std::string& paramsDesc,
        const std::string& prettyString);
    // ====== æŸ¥è¯¢è¿”回结构 ======
    struct Row {
        long long id = 0;
        int cassetteSeqNo = 0;
        int jobSeqNo = 0;
        std::string classId;
        int materialType = 0;
        int state = 0;
        std::string tStart;   // ISO8601(库中为 NULL åˆ™ä¸ºç©ºä¸²ï¼‰
        std::string tEnd;     // ISO8601(库中为 NULL åˆ™ä¸ºç©ºä¸²ï¼‰
        std::string buddyId;
        int aoiResult = 0;
        std::string path;
        std::string params;
        std::string pretty;
    };
    // ====== è¿‡æ»¤æ¡ä»¶ ======
    struct Filters {
        std::optional<std::string> classId;       // ç²¾ç¡®åŒ¹é… class_id
        std::optional<int> cassetteSeqNo;
        std::optional<int> jobSeqNo;
        // å…³é”®å­—模糊:对 class_id / buddy_id / path / params / pretty åš OR LIKE
        std::optional<std::string> keyword;
        // æ—¶é—´èŒƒå›´ï¼šå¯¹ t_start è¿‡æ»¤ï¼Œå«è¾¹ç•Œï¼ˆISO8601 æ–‡æœ¬æ¯”较)
        std::optional<std::chrono::system_clock::time_point> tStartFrom;
        std::optional<std::chrono::system_clock::time_point> tStartTo;
    };
    // ====== æŸ¥è¯¢ / ç»Ÿè®¡ / åˆ†é¡µ ======
    // åˆ†é¡µæŸ¥è¯¢ï¼ˆlimit/offset),按 id DESC
    std::vector<Row> query(
        const Filters& filters = {},
        int limit = 50,
        int offset = 0);
    // ç»Ÿè®¡ä¸Ž filters ç›¸åŒæ¡ä»¶çš„æ€»æ•°
    long long count(const Filters& filters = {});
    struct Page {
        std::vector<Row> items;  // å½“前页数据
        long long total = 0;     // ç¬¦åˆæ¡ä»¶çš„æ€»è®°å½•æ•°
        int limit = 0;           // æœ¬æ¬¡æŸ¥è¯¢çš„页容量
        int offset = 0;          // æœ¬æ¬¡æŸ¥è¯¢çš„起始偏移
    };
    // ä¸€æ¬¡å–回列表 + æ€»æ•°
    Page queryPaged(
        const Filters& filters = {},
        int limit = 50,
        int offset = 0);
    // ====== å¯¼å‡º ======
    // å¯¼å‡ºæ»¡è¶³ filters çš„“全部记录”(不受分页限制)为 CSV(UTF-8)
    // è¿”回写出的数据行数(不含表头)
    long long exportCsv(const std::string& csvPath, const Filters& filters = {});
    // ====== æžæž„ / æ‹·è´æŽ§åˆ¶ ======
    ~GlassLogDb();
    GlassLogDb(const GlassLogDb&) = delete;
    GlassLogDb& operator=(const GlassLogDb&) = delete;
private:
    // ä»…允许通过单例接口构造
    explicit GlassLogDb(const std::string& dbPath);
    // å†…部打开/关闭/重开
    void openDb(const std::string& dbPath);
    void closeDb() noexcept;
    void reopenInternal(const std::string& dbPath);
    // å»ºè¡¨ / é¢„编译 / é‡Šæ”¾
    void ensureSchema();
    void prepareStatements();
    void finalizeStatements() noexcept;
    // å·¥å…·ï¼štime_point -> "YYYY-MM-DDTHH:MM:SSZ"(UTC)
    static std::string toIso8601Utc(std::chrono::system_clock::time_point tp);
    // å®žé™…插入执行(支持可空时间)
    long long doInsert(
        int cassetteSeqNo,
        int jobSeqNo,
        const std::string& classId,
        int materialType,
        int state,
        std::optional<std::chrono::system_clock::time_point> tStart,
        std::optional<std::chrono::system_clock::time_point> tEnd,
        const std::string& buddyId,
        int aoiResult,
        const std::string& pathDesc,
        const std::string& paramsDesc,
        const std::string& prettyString);
private:
    // SQLite å¥æŸ„
    sqlite3* db_ = nullptr;
    sqlite3_stmt* stmtInsert_ = nullptr;
    // å®žä¾‹å†…并发保护(同一连接上的写/读串行化)
    std::mutex mtx_;
    // å½“前数据库文件路径
    std::string dbPath_;
    // å•例静态对象与其互斥
    static std::unique_ptr<GlassLogDb> s_inst;
    static std::mutex s_instMtx;
};
SourceCode/Bond/Servo/ListCtrlEx.cpp
@@ -34,11 +34,12 @@
    {
        // æ ¹æ®åœ¨ SetItemColor(DWORD iItem, COLORREF color) è®¾ç½®çš„   
        // ITEM号和COLORREF åœ¨æ‘¸æ¿ä¸­æŸ¥æ‰¾ï¼Œç„¶åŽè¿›è¡Œé¢œè‰²èµ‹å€¼ã€‚
        //LISTITEMEX_9& itemex = m_listItemColor.GetAt(m_listItemColor.FindIndex(nmcd.dwItemSpec));
        //lplvdr->clrText = itemex.colText;
        //lplvdr->clrTextBk = itemex.colTextBk;
        //*pResult = CDRF_DODEFAULT;
        LISTITEMEX_9& itemex = m_listItemColor.GetAt(m_listItemColor.FindIndex(nmcd.dwItemSpec));
        lplvdr->clrText = itemex.colText;
        lplvdr->clrTextBk = itemex.colTextBk;
        *pResult = CDRF_DODEFAULT;
        /*
        if (nmcd.dwItemSpec % 2 == 0) {
            lplvdr->clrText = RGB(0, 0, 0);
            lplvdr->clrTextBk = RGB(235, 235, 235);
@@ -49,7 +50,7 @@
            lplvdr->clrTextBk = RGB(255, 255, 255);
            *pResult = CDRF_DODEFAULT;
        }
        */
        break;
    }
SourceCode/Bond/Servo/Model.cpp
@@ -8,6 +8,7 @@
#include "CGlassPool.h"
#include "TransferManager.h"
#include "RecipeManager.h"
#include "GlassLogDb.h"
CModel::CModel()
@@ -50,6 +51,9 @@
            cassetteType, transferMode, autoChangeEnable);
        m_master.setPortType(i, portEnable, portType, portMode, cassetteType,
            transferMode, autoChangeEnable);
        int seed = m_configuration.getPortCassetteSnSeed(i + 1);
        m_master.setPortCassetteSnSeed(i + 1, seed);
    }
}
@@ -427,13 +431,19 @@
        m_hsmsPassive.setVariableValue("PJEndID", ((SERVO::CProcessJob*)pj)->id().c_str());
        m_hsmsPassive.requestEventReportSend_PJ_End();
    };
    masterListener.onPanelStart = [&](void* pMaster, void* pj) {
        m_hsmsPassive.setVariableValue("PanelStartID", ((SERVO::CGlass*)pj)->getID().c_str());
    masterListener.onPanelStart = [&](void* pMaster, void* pPanel) {
        m_hsmsPassive.setVariableValue("PanelStartID", ((SERVO::CGlass*)pPanel)->getID().c_str());
        m_hsmsPassive.requestEventReportSend_Panel_Start();
    };
    masterListener.onPanelEnd = [&](void* pMaster, void* pj) {
        m_hsmsPassive.setVariableValue("PanelEndID", ((SERVO::CGlass*)pj)->getID().c_str());
    masterListener.onPanelEnd = [&](void* pMaster, void* pPanel) {
        m_hsmsPassive.setVariableValue("PanelEndID", ((SERVO::CGlass*)pPanel)->getID().c_str());
        m_hsmsPassive.requestEventReportSend_Panel_End();
        auto& db = GlassLogDb::Instance();
        db.insertFromCGlass((*(SERVO::CGlass*)pPanel));
        SERVO::CGlass* pBuddy = ((SERVO::CGlass*)pPanel)->getBuddy();
        if (pBuddy != nullptr) {
            db.insertFromCGlass(*pBuddy);
        }
    };
    m_master.setListener(masterListener);
    m_master.setContinuousTransferCount(m_configuration.getContinuousTransferCount());
@@ -461,6 +471,11 @@
    alarmManager.readAlarmFile(szBuffer);
    // Glass数据库
    strLogDir.Format(_T("%s\\db\\process.db"), (LPTSTR)(LPCTSTR)m_strWorkDir);
    std::string path((LPTSTR)(LPCTSTR)strLogDir);
    GlassLogDb::Init(path);
    return 0;
}
SourceCode/Bond/Servo/PageAlarm.cpp
@@ -140,7 +140,7 @@
void CPageAlarm::UpdatePageData()
{
    // æ ¹æ®è¿‡æ»¤æ¡ä»¶åŠ è½½æ•°æ®ï¼Œæä¾›æè¿°å’Œæ—¶é—´èŒƒå›´æŸ¥è¯¢
    auto vecData = AlarmManager::getInstance().getFilteredAlarms("", "", m_strDeviceName, m_strUnitName, m_strKeyword, m_szTimeStart, m_szTimeEnd, m_nCurPage, PAGE_SIZE);
    auto vecData = AlarmManager::getInstance().getFilteredAlarms(m_strKeyword, m_szTimeStart, m_szTimeEnd, m_nCurPage, PAGE_SIZE);
    // å¡«å……数据到控件
    CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST_ALARM);
@@ -297,7 +297,7 @@
    // è®¡ç®—总页数
    int totalRecords = AlarmManager::getInstance().getTotalAlarmCount("", "", m_strDeviceName, m_strUnitName, m_strKeyword, m_szTimeStart, m_szTimeEnd);
    int totalRecords = AlarmManager::getInstance().getTotalAlarmCount(m_strKeyword, m_szTimeStart, m_szTimeEnd);
    m_nTotalPages = (totalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
    m_nCurPage = 1;
@@ -426,7 +426,7 @@
    }
    // è®¡ç®—总页数
    int totalRecords = AlarmManager::getInstance().getTotalAlarmCount("", "", m_strDeviceName, m_strUnitName, m_strKeyword, m_szTimeStart, m_szTimeEnd);
    int totalRecords = AlarmManager::getInstance().getTotalAlarmCount(m_strKeyword, m_szTimeStart, m_szTimeEnd);
    m_nTotalPages = (totalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
    m_nCurPage = 1;
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/Servo.vcxproj
@@ -232,9 +232,11 @@
    <ClInclude Include="CPortStatusReport.h" />
    <ClInclude Include="CRobotTaskDlg.h" />
    <ClInclude Include="CSVData.h" />
    <ClInclude Include="CServoUtilsTool.h" />
    <ClInclude Include="CVariable.h" />
    <ClInclude Include="DeviceRecipeParamDlg.h" />
    <ClInclude Include="GlassJson.h" />
    <ClInclude Include="GlassLogDb.h" />
    <ClInclude Include="GridControl\CellRange.h" />
    <ClInclude Include="GridControl\GridCell.h" />
    <ClInclude Include="GridControl\GridCellBase.h" />
@@ -355,6 +357,8 @@
    <ClInclude Include="ServoDlg.h" />
    <ClInclude Include="ServoGraph.h" />
    <ClInclude Include="ServoMemDC.h" />
    <ClInclude Include="sqlite3.h" />
    <ClInclude Include="sqlite3ext.h" />
    <ClInclude Include="stdafx.h" />
    <ClInclude Include="SystemLogManager.h" />
    <ClInclude Include="SystemLogManagerDlg.h" />
@@ -403,9 +407,11 @@
    <ClCompile Include="CPortStatusReport.cpp" />
    <ClCompile Include="CRobotTaskDlg.cpp" />
    <ClCompile Include="CSVData.cpp" />
    <ClCompile Include="CServoUtilsTool.cpp" />
    <ClCompile Include="CVariable.cpp" />
    <ClCompile Include="DeviceRecipeParamDlg.cpp" />
    <ClCompile Include="GlassJson.cpp" />
    <ClCompile Include="GlassLogDb.cpp" />
    <ClCompile Include="GridControl\GridCell.cpp" />
    <ClCompile Include="GridControl\GridCellBase.cpp" />
    <ClCompile Include="GridControl\GridCellButton.cpp" />
@@ -520,6 +526,10 @@
    <ClCompile Include="ServoDlg.cpp" />
    <ClCompile Include="ServoGraph.cpp" />
    <ClCompile Include="ServoMemDC.cpp" />
    <ClCompile Include="sqlite3.c">
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
    </ClCompile>
    <ClCompile Include="stdafx.cpp">
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -194,6 +194,9 @@
    </ClCompile>
    <ClCompile Include="DeviceRecipeParamDlg.cpp" />
    <ClCompile Include="CSVData.cpp" />
    <ClCompile Include="CServoUtilsTool.cpp" />
    <ClCompile Include="GlassLogDb.cpp" />
    <ClCompile Include="sqlite3.c" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -412,6 +415,10 @@
    </ClInclude>
    <ClInclude Include="DeviceRecipeParamDlg.h" />
    <ClInclude Include="CSVData.h" />
    <ClInclude Include="CServoUtilsTool.h" />
    <ClInclude Include="GlassLogDb.h" />
    <ClInclude Include="sqlite3.h" />
    <ClInclude Include="sqlite3ext.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/ServoDlg.cpp
@@ -217,6 +217,20 @@
                    m_pMyStatusbar->setBackgroundColor(STATUSBAR_BK_ALARM);
                    m_pMyStatusbar->setForegroundColor(RGB(0, 0, 0));
                    m_pMyStatusbar->setRunTimeText("启动失败.");
                    m_pTopToolbar->GetBtn(IDC_BUTTON_ALARM)->EnableWindow(TRUE);
                }
                else if (state == SERVO::MASTERSTATE::ATHERERROR) {
                    m_pTopToolbar->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
                    m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_BATCH)->EnableWindow(TRUE);
                    m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_CT)->EnableWindow(TRUE);
                    m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
                    m_pMyStatusbar->setBackgroundColor(STATUSBAR_BK_ALARM);
                    m_pMyStatusbar->setForegroundColor(RGB(0, 0, 0));
                    m_pMyStatusbar->setRunTimeText(theApp.m_model.getMaster().getLastErrorText().c_str());
                    if (theApp.m_model.getMaster().getLastError() == ER_CODE_AOI_NG) {
                        AfxMessageBox(_T("AOI检测失败,请操作员介入解决问题!"));
                    }
                    m_pTopToolbar->GetBtn(IDC_BUTTON_ALARM)->EnableWindow(TRUE);
                }
                else if (state == SERVO::MASTERSTATE::RUNNING || state == SERVO::MASTERSTATE::RUNNING_CONTINUOUS_TRANSFER
                    || state == SERVO::MASTERSTATE::RUNNING_BATCH) {
@@ -645,6 +659,7 @@
void CServoDlg::OnMenuHelpAbout()
{
    theApp.m_model.getMaster().test();
    CAboutDlg dlgAbout;
    dlgAbout.DoModal();
}
SourceCode/Bond/Servo/ToolUnits.cpp
@@ -3,6 +3,7 @@
#include <chrono>
#include <memory>
#include <sstream>
#include <algorithm>
CToolUnits::CToolUnits()
@@ -336,4 +337,223 @@
    if (nLength > 0) {
        strOut = std::string(pszBuffer, nLength);
    }
}
}
// FILETIME(UTC) -> time_point
std::chrono::system_clock::time_point CToolUnits::FileTimeToTimePointUTC(const FILETIME& ftUtc) {
    ULARGE_INTEGER ull;
    ull.LowPart = ftUtc.dwLowDateTime;
    ull.HighPart = ftUtc.dwHighDateTime;
    // 1601-01-01 åˆ° 1970-01-01 çš„ 100ns tick
    constexpr unsigned long long EPOCH_DIFF_100NS = 116444736000000000ULL;
    unsigned long long t100 = ull.QuadPart - EPOCH_DIFF_100NS; // 1970以来 100ns
    // é¿å…æº¢å‡ºï¼š100ns -> us
    unsigned long long us = t100 / 10ULL;
    return std::chrono::system_clock::time_point(std::chrono::microseconds(us));
}
// ä»Ž CDateTimeCtrl å–本地时间并转为 UTC time_point
// è¿”回 false è¡¨ç¤ºæŽ§ä»¶æ˜¯â€œæ— å€¼â€(DTS_SHOWNONE / GDT_NONE) æˆ–转换失败
bool CToolUnits::GetCtrlTimeUtc(const CDateTimeCtrl& ctrl,
    std::chrono::system_clock::time_point& outUtc)
{
    SYSTEMTIME stLocal{};
    DWORD rc = const_cast<CDateTimeCtrl&>(ctrl).GetTime(&stLocal); // MFC åŽŸåž‹éœ€è¦éžconst
    if (rc != GDT_VALID) return false; // GDT_NONE
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &stLocal, &stUtc)) return false;
    FILETIME ftUtc{};
    if (!SystemTimeToFileTime(&stUtc, &ftUtc)) return false;
    outUtc = FileTimeToTimePointUTC(ftUtc);
    return true;
}
// è‹¥ä½ çš„æ—¥æœŸæŽ§ä»¶åªæ˜¾ç¤ºâ€œæ—¥æœŸä¸æ˜¾ç¤ºæ—¶é—´â€ï¼Œæƒ³æŠŠ From è°ƒæ•´åˆ° 00:00:00 / To è°ƒæ•´åˆ° 23:59:59.999:
bool CToolUnits::GetCtrlDateRangeUtc_StartOfDay(const CDateTimeCtrl& ctrl,
    std::chrono::system_clock::time_point& outUtc)
{
    SYSTEMTIME st{};
    DWORD rc = const_cast<CDateTimeCtrl&>(ctrl).GetTime(&st);
    if (rc != GDT_VALID) return false;
    st.wHour = 0; st.wMinute = 0; st.wSecond = 0; st.wMilliseconds = 0;
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &st, &stUtc)) return false;
    FILETIME ftUtc{}; if (!SystemTimeToFileTime(&stUtc, &ftUtc)) return false;
    outUtc = FileTimeToTimePointUTC(ftUtc);
    return true;
}
bool CToolUnits::GetCtrlDateRangeUtc_EndOfDay(const CDateTimeCtrl& ctrl,
    std::chrono::system_clock::time_point& outUtc)
{
    SYSTEMTIME st{};
    DWORD rc = const_cast<CDateTimeCtrl&>(ctrl).GetTime(&st);
    if (rc != GDT_VALID) return false;
    st.wHour = 23; st.wMinute = 59; st.wSecond = 59; st.wMilliseconds = 999;
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &st, &stUtc)) return false;
    FILETIME ftUtc{}; if (!SystemTimeToFileTime(&stUtc, &ftUtc)) return false;
    outUtc = FileTimeToTimePointUTC(ftUtc);
    return true;
}
// ---------- æœ¬åœ° SYSTEMTIME -> UTC time_point ----------
std::chrono::system_clock::time_point CToolUnits::LocalSTtoUtcTP(const SYSTEMTIME& stLocal) {
    SYSTEMTIME stUtc{};
    if (!TzSpecificLocalTimeToSystemTime(nullptr, &stLocal, &stUtc))
        throw std::runtime_error("TzSpecificLocalTimeToSystemTime failed");
    FILETIME ftUtc{};
    if (!SystemTimeToFileTime(&stUtc, &ftUtc))
        throw std::runtime_error("SystemTimeToFileTime failed");
    return FileTimeToTimePointUTC(ftUtc);
}
// ç”Ÿæˆæœ¬åœ° SYSTEMTIME
SYSTEMTIME CToolUnits::MakeLocalST(int y, int m, int d, int hh, int mi, int ss, int ms) {
    SYSTEMTIME st{};
    st.wYear = (WORD)y; st.wMonth = (WORD)m; st.wDay = (WORD)d;
    st.wHour = (WORD)hh; st.wMinute = (WORD)mi; st.wSecond = (WORD)ss; st.wMilliseconds = (WORD)ms;
    return st;
}
// é—°å¹´ / æœˆå¤©æ•°
bool CToolUnits::IsLeap(int y) {
    return ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0);
}
int CToolUnits::DaysInMonth(int y, int m) {
    static const int d[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    return (m == 2) ? d[m] + (IsLeap(y) ? 1 : 0) : d[m];
}
// æœ¬åœ°â€œä»Šå¤©â€çš„年月日
void CToolUnits::GetTodayYMD_Local(int& y, int& m, int& d) {
    SYSTEMTIME now{}; GetLocalTime(&now);
    y = now.wYear; m = now.wMonth; d = now.wDay;
}
// æœ¬åœ°æ—¥åŽ†ä¸Šå‡ n å¤©ï¼ˆä»æ˜¯æœ¬åœ° Y/M/D)
void CToolUnits::LocalCalendarMinusDays(int& y, int& m, int& d, int nDays) {
    std::tm t{};
    t.tm_year = y - 1900; t.tm_mon = m - 1; t.tm_mday = d;
    t.tm_hour = 0; t.tm_min = 0; t.tm_sec = 0;
    t.tm_mday -= nDays;
    (void)std::mktime(&t); // æŒ‰æœ¬åœ°æ—¶åŒºå½’一化
    y = t.tm_year + 1900; m = t.tm_mon + 1; d = t.tm_mday;
}
// ====== æ ¸å¿ƒï¼šè®¡ç®—预设区间的 UTC [from, to] ======
// è¯­ä¹‰ï¼š
//  - Today     : æœ¬åœ°â€œä»Šå¤©â€00:00:00.000 ï½ž ä»Šå¤© 23:59:59.999
//  - Last7Days : æœ¬åœ°â€œ6 å¤©å‰â€00:00:00.000 ï½ž ä»Šå¤© 23:59:59.999(共 7 ä¸ªè‡ªç„¶æ—¥ï¼Œå«ä»Šå¤©ï¼‰
//  - ThisMonth : æœ¬åœ°â€œå½“月 1 å·â€00:00:00.000 ï½ž æœ¬æœˆæœ« 23:59:59.999
//  - ThisYear  : æœ¬åœ°â€œå½“å¹´ 1/1”00:00:00.000 ï½ž å½“年末 23:59:59.999
std::pair<std::chrono::system_clock::time_point,
    std::chrono::system_clock::time_point>
    CToolUnits::CalcQuickRangeUtc(QuickRange r)
{
    int y, m, d;
    GetTodayYMD_Local(y, m, d);
    SYSTEMTIME stFromLocal{}, stToLocal{};
    switch (r) {
    case QuickRange::Today:
        stFromLocal = MakeLocalST(y, m, d, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, m, d, 23, 59, 59, 999);
        break;
    case QuickRange::Last7Days: {
        int y2 = y, m2 = m, d2 = d;
        LocalCalendarMinusDays(y2, m2, d2, 6);
        stFromLocal = MakeLocalST(y2, m2, d2, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, m, d, 23, 59, 59, 999);
        break;
    }
    case QuickRange::ThisMonth: {
        int lastDay = DaysInMonth(y, m);
        stFromLocal = MakeLocalST(y, m, 1, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, m, lastDay, 23, 59, 59, 999);
        break;
    }
    case QuickRange::ThisYear:
        stFromLocal = MakeLocalST(y, 1, 1, 0, 0, 0, 0);
        stToLocal = MakeLocalST(y, 12, DaysInMonth(y, 12), 23, 59, 59, 999);
        break;
    default:
        throw std::invalid_argument("CalcQuickRangeUtc: unsupported range");
    }
    auto fromUtc = LocalSTtoUtcTP(stFromLocal);
    auto toUtc = LocalSTtoUtcTP(stToLocal);
    return { fromUtc, toUtc };
}
// å°å·¥å…·ï¼šASCII ä¸åŒºåˆ†å¤§å°å†™åŒ…含(中文等非 ASCII ä¸å—影响,但也不需要大小写转换)
bool CToolUnits::containsCI(const std::string& hay, const std::string& needle) {
    if (needle.empty()) return true;
    auto toLower = [](std::string s) { std::transform(s.begin(), s.end(), s.begin(),
        [](unsigned char c) { return (char)std::tolower(c); }); return s; };
    std::string h = toLower(hay), n = toLower(needle);
    return h.find(n) != std::string::npos;
}
// ------- æœ¬åœ°æ—¶é—´ -------
std::string CToolUnits::TimePointToLocalString(const std::optional<TP>& tp,
    const char* fmt/* = "%Y-%m-%d %H:%M:%S"*/)
{
    if (!tp) return {};
    std::time_t t = std::chrono::system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);
#else
    localtime_r(&t, &tm);
#endif
    char buf[64]{};
    std::strftime(buf, sizeof(buf), fmt, &tm);
    return buf;
}
// ------- UTC æ—¶é—´ -------
std::string CToolUnits::TimePointToUtcString(const std::optional<TP>& tp,
    const char* fmt/* = "%Y-%m-%d %H:%M:%S"*/)
{
    if (!tp) return {};
    std::time_t t = std::chrono::system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    gmtime_s(&tm, &t);
#else
    gmtime_r(&t, &tm);
#endif
    char buf[64]{};
    std::strftime(buf, sizeof(buf), fmt, &tm);
    return buf;
}
std::string CToolUnits::TimePointToLocalStringMs(const std::optional<TP>& tp)
{
    if (!tp) return {};
    using namespace std::chrono;
    auto ms_since_epoch = duration_cast<milliseconds>(tp->time_since_epoch());
    auto ms = static_cast<int>(ms_since_epoch.count() % 1000);
    std::time_t t = system_clock::to_time_t(*tp);
    std::tm tm{};
#if defined(_WIN32)
    localtime_s(&tm, &t);
#else
    localtime_r(&t, &tm);
#endif
    char date[32]{};
    std::strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &tm);
    char out[40]{};
    std::snprintf(out, sizeof(out), "%s.%03d", date, ms);
    return out;
}
SourceCode/Bond/Servo/ToolUnits.h
@@ -1,6 +1,11 @@
#pragma once
#include <string>
#include <chrono>
#include <optional>
#include <utility>
enum class QuickRange { Today, Last7Days, ThisMonth, ThisYear };
using TP = std::chrono::system_clock::time_point;
class CToolUnits
{
public:
@@ -32,5 +37,27 @@
    static bool startsWith(const std::string& str, const std::string& prefix);
    static std::string& toHexString(int value, std::string& strOut);
    static void convertString(const char* pszBuffer, int size, std::string& strOut);
    static inline std::chrono::system_clock::time_point FileTimeToTimePointUTC(const FILETIME& ftUtc);
    static bool GetCtrlTimeUtc(const CDateTimeCtrl& ctrl,
        std::chrono::system_clock::time_point& outUtc);
    static bool GetCtrlDateRangeUtc_StartOfDay(const CDateTimeCtrl& ctrl,
        std::chrono::system_clock::time_point& outUtc);
    static bool GetCtrlDateRangeUtc_EndOfDay(const CDateTimeCtrl& ctrl,
        std::chrono::system_clock::time_point& outUtc);
    static std::chrono::system_clock::time_point CToolUnits::LocalSTtoUtcTP(const SYSTEMTIME& stLocal);
    static SYSTEMTIME CToolUnits::MakeLocalST(int y, int m, int d, int hh, int mi, int ss, int ms);
    static bool IsLeap(int y);
    static int DaysInMonth(int y, int m);
    static void GetTodayYMD_Local(int& y, int& m, int& d);
    static void LocalCalendarMinusDays(int& y, int& m, int& d, int nDays);
    static std::pair<std::chrono::system_clock::time_point,
        std::chrono::system_clock::time_point>
        CalcQuickRangeUtc(QuickRange r);
    static bool containsCI(const std::string& hay, const std::string& needle);
    static std::string TimePointToLocalString(const std::optional<TP>& tp,
        const char* fmt = "%Y-%m-%d %H:%M:%S");
    static std::string TimePointToUtcString(const std::optional<TP>& tp,
        const char* fmt = "%Y-%m-%d %H:%M:%S");
    static std::string TimePointToLocalStringMs(const std::optional<TP>& tp);
};
SourceCode/Bond/Servo/resource.h
Binary files differ
SourceCode/Bond/Servo/sqlite3.c
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
SourceCode/Bond/Servo/sqlite3.h
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
SourceCode/Bond/Servo/sqlite3ext.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,719 @@
/*
** 2006 June 7
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance.  Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of
** sqlite3.h.
*/
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3.h"
/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING:  In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only.  If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
struct sqlite3_api_routines {
  void * (*aggregate_context)(sqlite3_context*,int nBytes);
  int  (*aggregate_count)(sqlite3_context*);
  int  (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
  int  (*bind_double)(sqlite3_stmt*,int,double);
  int  (*bind_int)(sqlite3_stmt*,int,int);
  int  (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
  int  (*bind_null)(sqlite3_stmt*,int);
  int  (*bind_parameter_count)(sqlite3_stmt*);
  int  (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
  const char * (*bind_parameter_name)(sqlite3_stmt*,int);
  int  (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
  int  (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
  int  (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
  int  (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
  int  (*busy_timeout)(sqlite3*,int ms);
  int  (*changes)(sqlite3*);
  int  (*close)(sqlite3*);
  int  (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
                           int eTextRep,const char*));
  int  (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
                             int eTextRep,const void*));
  const void * (*column_blob)(sqlite3_stmt*,int iCol);
  int  (*column_bytes)(sqlite3_stmt*,int iCol);
  int  (*column_bytes16)(sqlite3_stmt*,int iCol);
  int  (*column_count)(sqlite3_stmt*pStmt);
  const char * (*column_database_name)(sqlite3_stmt*,int);
  const void * (*column_database_name16)(sqlite3_stmt*,int);
  const char * (*column_decltype)(sqlite3_stmt*,int i);
  const void * (*column_decltype16)(sqlite3_stmt*,int);
  double  (*column_double)(sqlite3_stmt*,int iCol);
  int  (*column_int)(sqlite3_stmt*,int iCol);
  sqlite_int64  (*column_int64)(sqlite3_stmt*,int iCol);
  const char * (*column_name)(sqlite3_stmt*,int);
  const void * (*column_name16)(sqlite3_stmt*,int);
  const char * (*column_origin_name)(sqlite3_stmt*,int);
  const void * (*column_origin_name16)(sqlite3_stmt*,int);
  const char * (*column_table_name)(sqlite3_stmt*,int);
  const void * (*column_table_name16)(sqlite3_stmt*,int);
  const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
  const void * (*column_text16)(sqlite3_stmt*,int iCol);
  int  (*column_type)(sqlite3_stmt*,int iCol);
  sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
  void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
  int  (*complete)(const char*sql);
  int  (*complete16)(const void*sql);
  int  (*create_collation)(sqlite3*,const char*,int,void*,
                           int(*)(void*,int,const void*,int,const void*));
  int  (*create_collation16)(sqlite3*,const void*,int,void*,
                             int(*)(void*,int,const void*,int,const void*));
  int  (*create_function)(sqlite3*,const char*,int,int,void*,
                          void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                          void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                          void (*xFinal)(sqlite3_context*));
  int  (*create_function16)(sqlite3*,const void*,int,int,void*,
                            void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*));
  int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
  int  (*data_count)(sqlite3_stmt*pStmt);
  sqlite3 * (*db_handle)(sqlite3_stmt*);
  int (*declare_vtab)(sqlite3*,const char*);
  int  (*enable_shared_cache)(int);
  int  (*errcode)(sqlite3*db);
  const char * (*errmsg)(sqlite3*);
  const void * (*errmsg16)(sqlite3*);
  int  (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
  int  (*expired)(sqlite3_stmt*);
  int  (*finalize)(sqlite3_stmt*pStmt);
  void  (*free)(void*);
  void  (*free_table)(char**result);
  int  (*get_autocommit)(sqlite3*);
  void * (*get_auxdata)(sqlite3_context*,int);
  int  (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
  int  (*global_recover)(void);
  void  (*interruptx)(sqlite3*);
  sqlite_int64  (*last_insert_rowid)(sqlite3*);
  const char * (*libversion)(void);
  int  (*libversion_number)(void);
  void *(*malloc)(int);
  char * (*mprintf)(const char*,...);
  int  (*open)(const char*,sqlite3**);
  int  (*open16)(const void*,sqlite3**);
  int  (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
  int  (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
  void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
  void  (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
  void *(*realloc)(void*,int);
  int  (*reset)(sqlite3_stmt*pStmt);
  void  (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_double)(sqlite3_context*,double);
  void  (*result_error)(sqlite3_context*,const char*,int);
  void  (*result_error16)(sqlite3_context*,const void*,int);
  void  (*result_int)(sqlite3_context*,int);
  void  (*result_int64)(sqlite3_context*,sqlite_int64);
  void  (*result_null)(sqlite3_context*);
  void  (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
  void  (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
  void  (*result_value)(sqlite3_context*,sqlite3_value*);
  void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
  int  (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
                         const char*,const char*),void*);
  void  (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
  char * (*xsnprintf)(int,char*,const char*,...);
  int  (*step)(sqlite3_stmt*);
  int  (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
                                char const**,char const**,int*,int*,int*);
  void  (*thread_cleanup)(void);
  int  (*total_changes)(sqlite3*);
  void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
  int  (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
  void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
                                         sqlite_int64),void*);
  void * (*user_data)(sqlite3_context*);
  const void * (*value_blob)(sqlite3_value*);
  int  (*value_bytes)(sqlite3_value*);
  int  (*value_bytes16)(sqlite3_value*);
  double  (*value_double)(sqlite3_value*);
  int  (*value_int)(sqlite3_value*);
  sqlite_int64  (*value_int64)(sqlite3_value*);
  int  (*value_numeric_type)(sqlite3_value*);
  const unsigned char * (*value_text)(sqlite3_value*);
  const void * (*value_text16)(sqlite3_value*);
  const void * (*value_text16be)(sqlite3_value*);
  const void * (*value_text16le)(sqlite3_value*);
  int  (*value_type)(sqlite3_value*);
  char *(*vmprintf)(const char*,va_list);
  /* Added ??? */
  int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
  /* Added by 3.3.13 */
  int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
  int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
  int (*clear_bindings)(sqlite3_stmt*);
  /* Added by 3.4.1 */
  int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
                          void (*xDestroy)(void *));
  /* Added by 3.5.0 */
  int (*bind_zeroblob)(sqlite3_stmt*,int,int);
  int (*blob_bytes)(sqlite3_blob*);
  int (*blob_close)(sqlite3_blob*);
  int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
                   int,sqlite3_blob**);
  int (*blob_read)(sqlite3_blob*,void*,int,int);
  int (*blob_write)(sqlite3_blob*,const void*,int,int);
  int (*create_collation_v2)(sqlite3*,const char*,int,void*,
                             int(*)(void*,int,const void*,int,const void*),
                             void(*)(void*));
  int (*file_control)(sqlite3*,const char*,int,void*);
  sqlite3_int64 (*memory_highwater)(int);
  sqlite3_int64 (*memory_used)(void);
  sqlite3_mutex *(*mutex_alloc)(int);
  void (*mutex_enter)(sqlite3_mutex*);
  void (*mutex_free)(sqlite3_mutex*);
  void (*mutex_leave)(sqlite3_mutex*);
  int (*mutex_try)(sqlite3_mutex*);
  int (*open_v2)(const char*,sqlite3**,int,const char*);
  int (*release_memory)(int);
  void (*result_error_nomem)(sqlite3_context*);
  void (*result_error_toobig)(sqlite3_context*);
  int (*sleep)(int);
  void (*soft_heap_limit)(int);
  sqlite3_vfs *(*vfs_find)(const char*);
  int (*vfs_register)(sqlite3_vfs*,int);
  int (*vfs_unregister)(sqlite3_vfs*);
  int (*xthreadsafe)(void);
  void (*result_zeroblob)(sqlite3_context*,int);
  void (*result_error_code)(sqlite3_context*,int);
  int (*test_control)(int, ...);
  void (*randomness)(int,void*);
  sqlite3 *(*context_db_handle)(sqlite3_context*);
  int (*extended_result_codes)(sqlite3*,int);
  int (*limit)(sqlite3*,int,int);
  sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
  const char *(*sql)(sqlite3_stmt*);
  int (*status)(int,int*,int*,int);
  int (*backup_finish)(sqlite3_backup*);
  sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
  int (*backup_pagecount)(sqlite3_backup*);
  int (*backup_remaining)(sqlite3_backup*);
  int (*backup_step)(sqlite3_backup*,int);
  const char *(*compileoption_get)(int);
  int (*compileoption_used)(const char*);
  int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
                            void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*),
                            void(*xDestroy)(void*));
  int (*db_config)(sqlite3*,int,...);
  sqlite3_mutex *(*db_mutex)(sqlite3*);
  int (*db_status)(sqlite3*,int,int*,int*,int);
  int (*extended_errcode)(sqlite3*);
  void (*log)(int,const char*,...);
  sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
  const char *(*sourceid)(void);
  int (*stmt_status)(sqlite3_stmt*,int,int);
  int (*strnicmp)(const char*,const char*,int);
  int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
  int (*wal_autocheckpoint)(sqlite3*,int);
  int (*wal_checkpoint)(sqlite3*,const char*);
  void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
  int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
  int (*vtab_config)(sqlite3*,int op,...);
  int (*vtab_on_conflict)(sqlite3*);
  /* Version 3.7.16 and later */
  int (*close_v2)(sqlite3*);
  const char *(*db_filename)(sqlite3*,const char*);
  int (*db_readonly)(sqlite3*,const char*);
  int (*db_release_memory)(sqlite3*);
  const char *(*errstr)(int);
  int (*stmt_busy)(sqlite3_stmt*);
  int (*stmt_readonly)(sqlite3_stmt*);
  int (*stricmp)(const char*,const char*);
  int (*uri_boolean)(const char*,const char*,int);
  sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
  const char *(*uri_parameter)(const char*,const char*);
  char *(*xvsnprintf)(int,char*,const char*,va_list);
  int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
  /* Version 3.8.7 and later */
  int (*auto_extension)(void(*)(void));
  int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
                     void(*)(void*));
  int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
                      void(*)(void*),unsigned char);
  int (*cancel_auto_extension)(void(*)(void));
  int (*load_extension)(sqlite3*,const char*,const char*,char**);
  void *(*malloc64)(sqlite3_uint64);
  sqlite3_uint64 (*msize)(void*);
  void *(*realloc64)(void*,sqlite3_uint64);
  void (*reset_auto_extension)(void);
  void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
                        void(*)(void*));
  void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
                         void(*)(void*), unsigned char);
  int (*strglob)(const char*,const char*);
  /* Version 3.8.11 and later */
  sqlite3_value *(*value_dup)(const sqlite3_value*);
  void (*value_free)(sqlite3_value*);
  int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
  int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
  /* Version 3.9.0 and later */
  unsigned int (*value_subtype)(sqlite3_value*);
  void (*result_subtype)(sqlite3_context*,unsigned int);
  /* Version 3.10.0 and later */
  int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
  int (*strlike)(const char*,const char*,unsigned int);
  int (*db_cacheflush)(sqlite3*);
  /* Version 3.12.0 and later */
  int (*system_errno)(sqlite3*);
  /* Version 3.14.0 and later */
  int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
  char *(*expanded_sql)(sqlite3_stmt*);
  /* Version 3.18.0 and later */
  void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
  /* Version 3.20.0 and later */
  int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
                    sqlite3_stmt**,const char**);
  int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
                      sqlite3_stmt**,const void**);
  int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
  void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
  void *(*value_pointer)(sqlite3_value*,const char*);
  int (*vtab_nochange)(sqlite3_context*);
  int (*value_nochange)(sqlite3_value*);
  const char *(*vtab_collation)(sqlite3_index_info*,int);
  /* Version 3.24.0 and later */
  int (*keyword_count)(void);
  int (*keyword_name)(int,const char**,int*);
  int (*keyword_check)(const char*,int);
  sqlite3_str *(*str_new)(sqlite3*);
  char *(*str_finish)(sqlite3_str*);
  void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
  void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
  void (*str_append)(sqlite3_str*, const char *zIn, int N);
  void (*str_appendall)(sqlite3_str*, const char *zIn);
  void (*str_appendchar)(sqlite3_str*, int N, char C);
  void (*str_reset)(sqlite3_str*);
  int (*str_errcode)(sqlite3_str*);
  int (*str_length)(sqlite3_str*);
  char *(*str_value)(sqlite3_str*);
  /* Version 3.25.0 and later */
  int (*create_window_function)(sqlite3*,const char*,int,int,void*,
                            void (*xStep)(sqlite3_context*,int,sqlite3_value**),
                            void (*xFinal)(sqlite3_context*),
                            void (*xValue)(sqlite3_context*),
                            void (*xInv)(sqlite3_context*,int,sqlite3_value**),
                            void(*xDestroy)(void*));
  /* Version 3.26.0 and later */
  const char *(*normalized_sql)(sqlite3_stmt*);
  /* Version 3.28.0 and later */
  int (*stmt_isexplain)(sqlite3_stmt*);
  int (*value_frombind)(sqlite3_value*);
  /* Version 3.30.0 and later */
  int (*drop_modules)(sqlite3*,const char**);
  /* Version 3.31.0 and later */
  sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
  const char *(*uri_key)(const char*,int);
  const char *(*filename_database)(const char*);
  const char *(*filename_journal)(const char*);
  const char *(*filename_wal)(const char*);
  /* Version 3.32.0 and later */
  const char *(*create_filename)(const char*,const char*,const char*,
                           int,const char**);
  void (*free_filename)(const char*);
  sqlite3_file *(*database_file_object)(const char*);
  /* Version 3.34.0 and later */
  int (*txn_state)(sqlite3*,const char*);
  /* Version 3.36.1 and later */
  sqlite3_int64 (*changes64)(sqlite3*);
  sqlite3_int64 (*total_changes64)(sqlite3*);
  /* Version 3.37.0 and later */
  int (*autovacuum_pages)(sqlite3*,
     unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
     void*, void(*)(void*));
  /* Version 3.38.0 and later */
  int (*error_offset)(sqlite3*);
  int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
  int (*vtab_distinct)(sqlite3_index_info*);
  int (*vtab_in)(sqlite3_index_info*,int,int);
  int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
  int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
  /* Version 3.39.0 and later */
  int (*deserialize)(sqlite3*,const char*,unsigned char*,
                     sqlite3_int64,sqlite3_int64,unsigned);
  unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
                              unsigned int);
  const char *(*db_name)(sqlite3*,int);
  /* Version 3.40.0 and later */
  int (*value_encoding)(sqlite3_value*);
  /* Version 3.41.0 and later */
  int (*is_interrupted)(sqlite3*);
  /* Version 3.43.0 and later */
  int (*stmt_explain)(sqlite3_stmt*,int);
  /* Version 3.44.0 and later */
  void *(*get_clientdata)(sqlite3*,const char*);
  int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
};
/*
** This is the function signature used for all extension entry points.  It
** is also defined in the file "loadext.c".
*/
typedef int (*sqlite3_loadext_entry)(
  sqlite3 *db,                       /* Handle to the database. */
  char **pzErrMsg,                   /* Used to set error string on failure. */
  const sqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition.  But the main library does not want to redefine
** the API.  So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
#define sqlite3_aggregate_context      sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count        sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob              sqlite3_api->bind_blob
#define sqlite3_bind_double            sqlite3_api->bind_double
#define sqlite3_bind_int               sqlite3_api->bind_int
#define sqlite3_bind_int64             sqlite3_api->bind_int64
#define sqlite3_bind_null              sqlite3_api->bind_null
#define sqlite3_bind_parameter_count   sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index   sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name    sqlite3_api->bind_parameter_name
#define sqlite3_bind_text              sqlite3_api->bind_text
#define sqlite3_bind_text16            sqlite3_api->bind_text16
#define sqlite3_bind_value             sqlite3_api->bind_value
#define sqlite3_busy_handler           sqlite3_api->busy_handler
#define sqlite3_busy_timeout           sqlite3_api->busy_timeout
#define sqlite3_changes                sqlite3_api->changes
#define sqlite3_close                  sqlite3_api->close
#define sqlite3_collation_needed       sqlite3_api->collation_needed
#define sqlite3_collation_needed16     sqlite3_api->collation_needed16
#define sqlite3_column_blob            sqlite3_api->column_blob
#define sqlite3_column_bytes           sqlite3_api->column_bytes
#define sqlite3_column_bytes16         sqlite3_api->column_bytes16
#define sqlite3_column_count           sqlite3_api->column_count
#define sqlite3_column_database_name   sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype        sqlite3_api->column_decltype
#define sqlite3_column_decltype16      sqlite3_api->column_decltype16
#define sqlite3_column_double          sqlite3_api->column_double
#define sqlite3_column_int             sqlite3_api->column_int
#define sqlite3_column_int64           sqlite3_api->column_int64
#define sqlite3_column_name            sqlite3_api->column_name
#define sqlite3_column_name16          sqlite3_api->column_name16
#define sqlite3_column_origin_name     sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16   sqlite3_api->column_origin_name16
#define sqlite3_column_table_name      sqlite3_api->column_table_name
#define sqlite3_column_table_name16    sqlite3_api->column_table_name16
#define sqlite3_column_text            sqlite3_api->column_text
#define sqlite3_column_text16          sqlite3_api->column_text16
#define sqlite3_column_type            sqlite3_api->column_type
#define sqlite3_column_value           sqlite3_api->column_value
#define sqlite3_commit_hook            sqlite3_api->commit_hook
#define sqlite3_complete               sqlite3_api->complete
#define sqlite3_complete16             sqlite3_api->complete16
#define sqlite3_create_collation       sqlite3_api->create_collation
#define sqlite3_create_collation16     sqlite3_api->create_collation16
#define sqlite3_create_function        sqlite3_api->create_function
#define sqlite3_create_function16      sqlite3_api->create_function16
#define sqlite3_create_module          sqlite3_api->create_module
#define sqlite3_create_module_v2       sqlite3_api->create_module_v2
#define sqlite3_data_count             sqlite3_api->data_count
#define sqlite3_db_handle              sqlite3_api->db_handle
#define sqlite3_declare_vtab           sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache    sqlite3_api->enable_shared_cache
#define sqlite3_errcode                sqlite3_api->errcode
#define sqlite3_errmsg                 sqlite3_api->errmsg
#define sqlite3_errmsg16               sqlite3_api->errmsg16
#define sqlite3_exec                   sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired                sqlite3_api->expired
#endif
#define sqlite3_finalize               sqlite3_api->finalize
#define sqlite3_free                   sqlite3_api->free
#define sqlite3_free_table             sqlite3_api->free_table
#define sqlite3_get_autocommit         sqlite3_api->get_autocommit
#define sqlite3_get_auxdata            sqlite3_api->get_auxdata
#define sqlite3_get_table              sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover         sqlite3_api->global_recover
#endif
#define sqlite3_interrupt              sqlite3_api->interruptx
#define sqlite3_last_insert_rowid      sqlite3_api->last_insert_rowid
#define sqlite3_libversion             sqlite3_api->libversion
#define sqlite3_libversion_number      sqlite3_api->libversion_number
#define sqlite3_malloc                 sqlite3_api->malloc
#define sqlite3_mprintf                sqlite3_api->mprintf
#define sqlite3_open                   sqlite3_api->open
#define sqlite3_open16                 sqlite3_api->open16
#define sqlite3_prepare                sqlite3_api->prepare
#define sqlite3_prepare16              sqlite3_api->prepare16
#define sqlite3_prepare_v2             sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2           sqlite3_api->prepare16_v2
#define sqlite3_profile                sqlite3_api->profile
#define sqlite3_progress_handler       sqlite3_api->progress_handler
#define sqlite3_realloc                sqlite3_api->realloc
#define sqlite3_reset                  sqlite3_api->reset
#define sqlite3_result_blob            sqlite3_api->result_blob
#define sqlite3_result_double          sqlite3_api->result_double
#define sqlite3_result_error           sqlite3_api->result_error
#define sqlite3_result_error16         sqlite3_api->result_error16
#define sqlite3_result_int             sqlite3_api->result_int
#define sqlite3_result_int64           sqlite3_api->result_int64
#define sqlite3_result_null            sqlite3_api->result_null
#define sqlite3_result_text            sqlite3_api->result_text
#define sqlite3_result_text16          sqlite3_api->result_text16
#define sqlite3_result_text16be        sqlite3_api->result_text16be
#define sqlite3_result_text16le        sqlite3_api->result_text16le
#define sqlite3_result_value           sqlite3_api->result_value
#define sqlite3_rollback_hook          sqlite3_api->rollback_hook
#define sqlite3_set_authorizer         sqlite3_api->set_authorizer
#define sqlite3_set_auxdata            sqlite3_api->set_auxdata
#define sqlite3_snprintf               sqlite3_api->xsnprintf
#define sqlite3_step                   sqlite3_api->step
#define sqlite3_table_column_metadata  sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup         sqlite3_api->thread_cleanup
#define sqlite3_total_changes          sqlite3_api->total_changes
#define sqlite3_trace                  sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings      sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook            sqlite3_api->update_hook
#define sqlite3_user_data              sqlite3_api->user_data
#define sqlite3_value_blob             sqlite3_api->value_blob
#define sqlite3_value_bytes            sqlite3_api->value_bytes
#define sqlite3_value_bytes16          sqlite3_api->value_bytes16
#define sqlite3_value_double           sqlite3_api->value_double
#define sqlite3_value_int              sqlite3_api->value_int
#define sqlite3_value_int64            sqlite3_api->value_int64
#define sqlite3_value_numeric_type     sqlite3_api->value_numeric_type
#define sqlite3_value_text             sqlite3_api->value_text
#define sqlite3_value_text16           sqlite3_api->value_text16
#define sqlite3_value_text16be         sqlite3_api->value_text16be
#define sqlite3_value_text16le         sqlite3_api->value_text16le
#define sqlite3_value_type             sqlite3_api->value_type
#define sqlite3_vmprintf               sqlite3_api->vmprintf
#define sqlite3_vsnprintf              sqlite3_api->xvsnprintf
#define sqlite3_overload_function      sqlite3_api->overload_function
#define sqlite3_prepare_v2             sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2           sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings         sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob          sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes             sqlite3_api->blob_bytes
#define sqlite3_blob_close             sqlite3_api->blob_close
#define sqlite3_blob_open              sqlite3_api->blob_open
#define sqlite3_blob_read              sqlite3_api->blob_read
#define sqlite3_blob_write             sqlite3_api->blob_write
#define sqlite3_create_collation_v2    sqlite3_api->create_collation_v2
#define sqlite3_file_control           sqlite3_api->file_control
#define sqlite3_memory_highwater       sqlite3_api->memory_highwater
#define sqlite3_memory_used            sqlite3_api->memory_used
#define sqlite3_mutex_alloc            sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter            sqlite3_api->mutex_enter
#define sqlite3_mutex_free             sqlite3_api->mutex_free
#define sqlite3_mutex_leave            sqlite3_api->mutex_leave
#define sqlite3_mutex_try              sqlite3_api->mutex_try
#define sqlite3_open_v2                sqlite3_api->open_v2
#define sqlite3_release_memory         sqlite3_api->release_memory
#define sqlite3_result_error_nomem     sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig    sqlite3_api->result_error_toobig
#define sqlite3_sleep                  sqlite3_api->sleep
#define sqlite3_soft_heap_limit        sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find               sqlite3_api->vfs_find
#define sqlite3_vfs_register           sqlite3_api->vfs_register
#define sqlite3_vfs_unregister         sqlite3_api->vfs_unregister
#define sqlite3_threadsafe             sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob        sqlite3_api->result_zeroblob
#define sqlite3_result_error_code      sqlite3_api->result_error_code
#define sqlite3_test_control           sqlite3_api->test_control
#define sqlite3_randomness             sqlite3_api->randomness
#define sqlite3_context_db_handle      sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes  sqlite3_api->extended_result_codes
#define sqlite3_limit                  sqlite3_api->limit
#define sqlite3_next_stmt              sqlite3_api->next_stmt
#define sqlite3_sql                    sqlite3_api->sql
#define sqlite3_status                 sqlite3_api->status
#define sqlite3_backup_finish          sqlite3_api->backup_finish
#define sqlite3_backup_init            sqlite3_api->backup_init
#define sqlite3_backup_pagecount       sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining       sqlite3_api->backup_remaining
#define sqlite3_backup_step            sqlite3_api->backup_step
#define sqlite3_compileoption_get      sqlite3_api->compileoption_get
#define sqlite3_compileoption_used     sqlite3_api->compileoption_used
#define sqlite3_create_function_v2     sqlite3_api->create_function_v2
#define sqlite3_db_config              sqlite3_api->db_config
#define sqlite3_db_mutex               sqlite3_api->db_mutex
#define sqlite3_db_status              sqlite3_api->db_status
#define sqlite3_extended_errcode       sqlite3_api->extended_errcode
#define sqlite3_log                    sqlite3_api->log
#define sqlite3_soft_heap_limit64      sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid               sqlite3_api->sourceid
#define sqlite3_stmt_status            sqlite3_api->stmt_status
#define sqlite3_strnicmp               sqlite3_api->strnicmp
#define sqlite3_unlock_notify          sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint     sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint         sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook               sqlite3_api->wal_hook
#define sqlite3_blob_reopen            sqlite3_api->blob_reopen
#define sqlite3_vtab_config            sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict       sqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
#define sqlite3_close_v2               sqlite3_api->close_v2
#define sqlite3_db_filename            sqlite3_api->db_filename
#define sqlite3_db_readonly            sqlite3_api->db_readonly
#define sqlite3_db_release_memory      sqlite3_api->db_release_memory
#define sqlite3_errstr                 sqlite3_api->errstr
#define sqlite3_stmt_busy              sqlite3_api->stmt_busy
#define sqlite3_stmt_readonly          sqlite3_api->stmt_readonly
#define sqlite3_stricmp                sqlite3_api->stricmp
#define sqlite3_uri_boolean            sqlite3_api->uri_boolean
#define sqlite3_uri_int64              sqlite3_api->uri_int64
#define sqlite3_uri_parameter          sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf          sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2      sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension         sqlite3_api->auto_extension
#define sqlite3_bind_blob64            sqlite3_api->bind_blob64
#define sqlite3_bind_text64            sqlite3_api->bind_text64
#define sqlite3_cancel_auto_extension  sqlite3_api->cancel_auto_extension
#define sqlite3_load_extension         sqlite3_api->load_extension
#define sqlite3_malloc64               sqlite3_api->malloc64
#define sqlite3_msize                  sqlite3_api->msize
#define sqlite3_realloc64              sqlite3_api->realloc64
#define sqlite3_reset_auto_extension   sqlite3_api->reset_auto_extension
#define sqlite3_result_blob64          sqlite3_api->result_blob64
#define sqlite3_result_text64          sqlite3_api->result_text64
#define sqlite3_strglob                sqlite3_api->strglob
/* Version 3.8.11 and later */
#define sqlite3_value_dup              sqlite3_api->value_dup
#define sqlite3_value_free             sqlite3_api->value_free
#define sqlite3_result_zeroblob64      sqlite3_api->result_zeroblob64
#define sqlite3_bind_zeroblob64        sqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
#define sqlite3_value_subtype          sqlite3_api->value_subtype
#define sqlite3_result_subtype         sqlite3_api->result_subtype
/* Version 3.10.0 and later */
#define sqlite3_status64               sqlite3_api->status64
#define sqlite3_strlike                sqlite3_api->strlike
#define sqlite3_db_cacheflush          sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno           sqlite3_api->system_errno
/* Version 3.14.0 and later */
#define sqlite3_trace_v2               sqlite3_api->trace_v2
#define sqlite3_expanded_sql           sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid  sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3             sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3           sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer           sqlite3_api->bind_pointer
#define sqlite3_result_pointer         sqlite3_api->result_pointer
#define sqlite3_value_pointer          sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange          sqlite3_api->vtab_nochange
#define sqlite3_value_nochange         sqlite3_api->value_nochange
#define sqlite3_vtab_collation         sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count          sqlite3_api->keyword_count
#define sqlite3_keyword_name           sqlite3_api->keyword_name
#define sqlite3_keyword_check          sqlite3_api->keyword_check
#define sqlite3_str_new                sqlite3_api->str_new
#define sqlite3_str_finish             sqlite3_api->str_finish
#define sqlite3_str_appendf            sqlite3_api->str_appendf
#define sqlite3_str_vappendf           sqlite3_api->str_vappendf
#define sqlite3_str_append             sqlite3_api->str_append
#define sqlite3_str_appendall          sqlite3_api->str_appendall
#define sqlite3_str_appendchar         sqlite3_api->str_appendchar
#define sqlite3_str_reset              sqlite3_api->str_reset
#define sqlite3_str_errcode            sqlite3_api->str_errcode
#define sqlite3_str_length             sqlite3_api->str_length
#define sqlite3_str_value              sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql         sqlite3_api->normalized_sql
/* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain         sqlite3_api->stmt_isexplain
#define sqlite3_value_frombind         sqlite3_api->value_frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules           sqlite3_api->drop_modules
/* Version 3.31.0 and later */
#define sqlite3_hard_heap_limit64      sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key                sqlite3_api->uri_key
#define sqlite3_filename_database      sqlite3_api->filename_database
#define sqlite3_filename_journal       sqlite3_api->filename_journal
#define sqlite3_filename_wal           sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename        sqlite3_api->create_filename
#define sqlite3_free_filename          sqlite3_api->free_filename
#define sqlite3_database_file_object   sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state              sqlite3_api->txn_state
/* Version 3.36.1 and later */
#define sqlite3_changes64              sqlite3_api->changes64
#define sqlite3_total_changes64        sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages       sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset           sqlite3_api->error_offset
#define sqlite3_vtab_rhs_value         sqlite3_api->vtab_rhs_value
#define sqlite3_vtab_distinct          sqlite3_api->vtab_distinct
#define sqlite3_vtab_in                sqlite3_api->vtab_in
#define sqlite3_vtab_in_first          sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next           sqlite3_api->vtab_in_next
/* Version 3.39.0 and later */
#ifndef SQLITE_OMIT_DESERIALIZE
#define sqlite3_deserialize            sqlite3_api->deserialize
#define sqlite3_serialize              sqlite3_api->serialize
#endif
#define sqlite3_db_name                sqlite3_api->db_name
/* Version 3.40.0 and later */
#define sqlite3_value_encoding         sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted         sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain           sqlite3_api->stmt_explain
/* Version 3.44.0 and later */
#define sqlite3_get_clientdata         sqlite3_api->get_clientdata
#define sqlite3_set_clientdata         sqlite3_api->set_clientdata
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
  /* This case when the file really is being compiled as a loadable
  ** extension */
# define SQLITE_EXTENSION_INIT1     const sqlite3_api_routines *sqlite3_api=0;
# define SQLITE_EXTENSION_INIT2(v)  sqlite3_api=v;
# define SQLITE_EXTENSION_INIT3     \
    extern const sqlite3_api_routines *sqlite3_api;
#else
  /* This case when the file is being statically linked into the
  ** application */
# define SQLITE_EXTENSION_INIT1     /*no-op*/
# define SQLITE_EXTENSION_INIT2(v)  (void)v; /* unused parameter */
# define SQLITE_EXTENSION_INIT3     /*no-op*/
#endif
#endif /* SQLITE3EXT_H */