LAPTOP-SNT8I5JK\Boounion
2025-09-12 c62dbec7328a8b44e6ec61758e7b8463f2e502dd
Merge branch 'liuyang'
已修改22个文件
1454 ■■■■ 文件已修改
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.cpp 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.h 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.cpp 464 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.h 412 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/Logger.cpp 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/Logger.h 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp 111 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.h 143 ●●●● 补丁 | 查看 | 原始文档 | 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 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/AlarmManager.h 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageAlarm.cpp 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.cpp
@@ -40,7 +40,7 @@
        return ERROR_CODE_INVALID_DATA;
    }
    // 解析各位状态
    // 解析各位状态
    const short nBuffer = vecLedBuffer[0];
    outLedStatus.bExtPw = (nBuffer & (1 << 15)) != 0;
    outLedStatus.bRd = (nBuffer & (1 << 6)) != 0;
@@ -56,40 +56,40 @@
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测试
        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; // 站号超出范围
        return ERROR_CODE_STATION_OUT_OF_RANGE; // 站号超出范围
    }
    if (status.nGroupValue < 0 || status.nGroupValue > 32) {
        return ERROR_CODE_GROUP_OUT_OF_RANGE;   // 组超出范围
        return ERROR_CODE_GROUP_OUT_OF_RANGE;   // 组超出范围
    }
    if (status.nNetworkValue < 1 || status.nNetworkValue > 239) {
        return ERROR_CODE_NETWORK_OUT_OF_RANGE; // 网络号超出范围
        return ERROR_CODE_NETWORK_OUT_OF_RANGE; // 网络号超出范围
    }
    return 0; // 校验通过
    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;
    }
    // 确保线程安全的最小锁定范围
    // 确保线程安全的最小锁定范围
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        const short nDevType = CalculateDeviceType(station, enDevType);
SourceCode/Bond/SGMeasurement/CCLinkPerformance/CCLinkIEControl.h
@@ -4,14 +4,14 @@
#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 {
@@ -20,16 +20,16 @@
    ~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)
        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: {"
@@ -46,17 +46,17 @@
        }
    };
    // 读取目标站点CPU类型
    // 读取目标站点CPU类型
    // short ReadCPUCodeEx(const StationIdentifier& station, short& nCPUCode);
    // 板模式获取/设置
    // 板模式获取/设置
    int SetBoardModeEx(CCLinkIEControlMode mode);
    CCLinkIEControlMode GetBoardModeEx();
    // 获取板状态
    // 获取板状态
    int GetBoardStatusEx(BoardStatus& status);
    // 读取LED状态
    // 读取LED状态
    int ReadLedStatus(LedStatus& outLedStatus);
    int ReadDataEx(const StationIdentifier& station, DeviceType enDevType, long devNo, long size, void* pData);
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.cpp
@@ -23,9 +23,9 @@
#define LOG_DEBUG(msg)
#endif
// 初始化静态成员变量
// 初始化静态成员变量
std::unordered_map<int, std::string> CPerformanceMelsec::m_mapError = {
    // 板块SDK错误码
    // 板块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."},
@@ -118,7 +118,7 @@
    {-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."},
@@ -137,26 +137,26 @@
    m_bConnected.store(false);
}
// 析构函数
// 析构函数
CPerformanceMelsec::~CPerformanceMelsec() {
    Disconnect();
}
// 获取最近的错误信息
// 获取最近的错误信息
std::string CPerformanceMelsec::GetLastError() const {
    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;
    }
    // 遍历静态成员变量 m_mapError 并将每个错误信息写入文件
    // 遍历静态成员变量 m_mapError 并将每个错误信息写入文件
    for (const auto& entry : m_mapError) {
        const int nCode = entry.first;
        const std::string& strMessage = entry.second;
@@ -167,7 +167,7 @@
    return true;
}
// 加载错误信息
// 加载错误信息
bool CPerformanceMelsec::LoadErrorInfoFromFile(const std::string& filename) {
    std::ifstream inFile(filename);
    if (!inFile.is_open()) {
@@ -183,7 +183,7 @@
        std::string strToken;
        std::string strMessage;
        // 使用分隔符 "|" 解析每一行
        // 使用分隔符 "|" 解析每一行
        if (std::getline(iss, strToken, '|')) {
            nCode = std::stoi(strToken);
        }
@@ -200,7 +200,7 @@
    return true;
}
// 连接到PLC
// 连接到PLC
int CPerformanceMelsec::Connect(const short nChannel, const short nMode) {
    std::lock_guard<std::mutex> lock(m_mtx);
@@ -214,7 +214,7 @@
        return ERROR_CODE_INVALID_PARAM;
    }
    // 连接PLC,显式类型转换以匹配 mdOpen 的签名
    // 连接PLC,显式类型转换以匹配 mdOpen 的签名
    const short nRet = mdOpen(nChannel, nMode, &m_nPath);
    if (nRet == 0) {
        m_bConnected.store(true);
@@ -228,7 +228,7 @@
    return nRet;
}
// 断开连接
// 断开连接
int CPerformanceMelsec::Disconnect() {
    std::lock_guard<std::mutex> lock(m_mtx);
@@ -245,7 +245,7 @@
    return nRet;
}
// 可编程控制器软元件信息表的初始化
// 可编程控制器软元件信息表的初始化
int CPerformanceMelsec::InitializeController() {
    std::lock_guard<std::mutex> lock(m_mtx);
@@ -263,14 +263,14 @@
    return nRet;
}
// 获取版本信息
// 获取版本信息
int CPerformanceMelsec::GetBoardVersion(BoardVersion& version) {
    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) {
@@ -279,7 +279,7 @@
        return nRet;
    }
    // 填充版本信息到结构体
    // 填充版本信息到结构体
    version.fixedValue[0] = static_cast<char>(buf[0] & 0xFF);
    version.fixedValue[1] = static_cast<char>((buf[0] >> 8) & 0xFF);
@@ -310,16 +310,16 @@
    return nRet;
}
// 读取目标站点CPU类型
// 读取目标站点CPU类型
int CPerformanceMelsec::ReadCPUCode(const StationIdentifier& station, short& nCPUCode) {
    // 验证站点参数和数据有效性
    // 验证站点参数和数据有效性
    int nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // 确保线程安全的最小锁定范围
    // 确保线程安全的最小锁定范围
    {
        nCPUCode = 0;
        std::lock_guard<std::mutex> lock(m_mtx);
@@ -334,15 +334,15 @@
    return nRet;
}
// 板模式设置
// 板模式设置
int CPerformanceMelsec::SetBoardMode(const short nMode) {
    // 检查是否已经连接
    // 检查是否已经连接
    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);
@@ -357,9 +357,9 @@
    return nRet;
}
// 获取板模式
// 获取板模式
int CPerformanceMelsec::GetBoardMode(short& nMode) {
    // 检查是否已经连接
    // 检查是否已经连接
    if (!m_bConnected.load()) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
        return ERROR_CODE_NOT_CONNECTED;
@@ -381,7 +381,7 @@
    return 0;
}
// 板复位
// 板复位
int CPerformanceMelsec::BoardReset() {
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
@@ -398,7 +398,7 @@
    return nRet;
}
// 板LED读取
// 板LED读取
int CPerformanceMelsec::ReadBoardLed(std::vector<short>& vecLedBuffer) {
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected.load()) {
@@ -406,11 +406,11 @@
        return ERROR_CODE_NOT_CONNECTED;
    }
    // 清空 LED 缓冲区
    // 清空 LED 缓冲区
    vecLedBuffer.clear();
    vecLedBuffer.resize(16, 0);
    // 调用 SDK 函数读取 LED 数据
    // 调用 SDK 函数读取 LED 数据
    const short nRet = mdBdLedRead(m_nPath, vecLedBuffer.data());
    if (nRet != 0) {
        UpdateLastError(ERROR_CODE_NOT_CONNECTED);
@@ -421,7 +421,7 @@
    return nRet;
}
// 获取板状态
// 获取板状态
int CPerformanceMelsec::GetBoardStatus(BoardStatus& status) {
    std::lock_guard<std::mutex> lock(m_mtx);
    if (!m_bConnected) {
@@ -436,25 +436,25 @@
        LOG_ERROR(m_strLastError);
    }
    // 将 buf 映射到结构体
    // 将 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;
    }
    // 初始化读取缓冲区
    // 初始化读取缓冲区
    vecData.clear();
    vecData.resize(nSize, 0);
    // 确保线程安全的最小锁定范围
    // 确保线程安全的最小锁定范围
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        short* pData = vecData.data();
@@ -468,15 +468,15 @@
    }
    if (nRet != 0) {
        vecData.clear(); // 如果读取失败,清空缓冲区
        vecData.clear(); // 如果读取失败,清空缓冲区
    }
    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);
@@ -490,7 +490,7 @@
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>((static_cast<int>(nBitCount) + 15) / 16);  // 计算需要读取的字数量(向上取整)
    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);
@@ -498,15 +498,15 @@
    if (nRet == 0) {
        vecData.clear();
        // 将字数据解析为位数据
        // 将字数据解析为位数据
        for (short nIdx = 0; nIdx < nSize; ++nIdx) {
            const short nCurrentValue = vecTempBuffer[nIdx];
            // 遍历当前 short 中的每一位
            // 遍历当前 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;  // 如果已经读取完所需的位数,提前退出
                }
            }
        }
@@ -515,9 +515,9 @@
    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);
@@ -536,44 +536,44 @@
    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;
    }
    const auto nSize = static_cast<short>(nDWordCount * 2); // 每个双字占两个字(每个双字占 4 字节)
    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); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        ConvertShortToUint32(vecTempBuffer, vecData);
    }
    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;
    }
    // 数据有效性
    // 数据有效性
    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);
@@ -588,9 +588,9 @@
    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);
@@ -604,16 +604,16 @@
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const auto nSize = static_cast<short>((static_cast<int>(vecData.size()) + 15) / 16);  // 计算需要写入的字数量(向上取整)
    const auto nSize = static_cast<short>((static_cast<int>(vecData.size()) + 15) / 16);  // 计算需要写入的字数量(向上取整)
    // 准备临时缓冲区来存储转换后的 16 位数据
    // 准备临时缓冲区来存储转换后的 16 位数据
    std::vector<short> vecTempBuffer(nSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        // 将位数据按字打包到临时缓冲区
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        // 将位数据按字打包到临时缓冲区
        for (int i = 0; i < vecData.size(); ++i) {
            if (vecData[i]) {
                // 使用 & 0xFFFF 保证不会超过 16 位,防止溢出
                // 使用 & 0xFFFF 保证不会超过 16 位,防止溢出
                vecTempBuffer[i / 16] |= static_cast<short>((1 << (i % 16)) & 0xFFFF);
            }
        }
@@ -622,16 +622,16 @@
    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;
    }
    // 计算需要写入的字节数(每个字占 2 字节)
    // 计算需要写入的字节数(每个字占 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()));
@@ -639,30 +639,30 @@
    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;
    }
    // 计算需要写入的字节数(每个双字占 4 字节)
    // 计算需要写入的字节数(每个双字占 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); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        ConvertUint32ToShort(vecData, vecBuffer);
    }
    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);
@@ -678,7 +678,7 @@
    std::vector<short> vecBuffer(nSize / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        nRet = mdReceiveEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo, &nSize, vecBuffer.data());
    }
@@ -687,7 +687,7 @@
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        vecData.resize(nSize);
        ConvertShortToChar(vecBuffer, vecData);
    }
@@ -695,7 +695,7 @@
    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) {
@@ -705,7 +705,7 @@
    const short nDevType = CalculateDeviceType(station, enDevType);
    // === 自动对齐到起始字 ===
    // === 自动对齐到起始字 ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + nBitCount;
@@ -735,7 +735,7 @@
    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) {
@@ -762,7 +762,7 @@
    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) {
@@ -791,27 +791,27 @@
    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;
    }
    // 将 vecData 转换为 short 类型的缓冲区
    // 将 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); // 线程安全保护
        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);
@@ -820,7 +820,7 @@
    return nRet;
}
// 扩展写位数据
// 扩展写位数据
long CPerformanceMelsec::WriteBitDataEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo, const BitContainer& vecData) {
    long nRet = ValidateStationAndData(station, vecData);
    if (nRet != 0) {
@@ -830,13 +830,13 @@
    const short nDevType = CalculateDeviceType(station, enDevType);
    // === 1. 自动对齐起始地址 ===
    // === 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. 先读取原始值以支持非对齐覆盖 ===
    // === 2. 先读取原始值以支持非对齐覆盖 ===
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, static_cast<long>(nWordCount * sizeof(short)), vecRaw);
    if (nRet != 0) {
@@ -844,7 +844,7 @@
        return nRet;
    }
    // === 3. 合并新数据 ===
    // === 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);
@@ -862,7 +862,7 @@
        }
    }
    // === 4. 转为字节流写入 ===
    // === 4. 转为字节流写入 ===
    std::vector<char> vecByteBuffer(nWordCount * 2);
    for (size_t i = 0; i < nWordCount; ++i) {
        vecByteBuffer[i * 2] = static_cast<char>(vecWordBuffer[i] & 0xFF);
@@ -872,7 +872,7 @@
    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) {
@@ -892,7 +892,7 @@
    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) {
@@ -914,7 +914,7 @@
    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);
@@ -922,48 +922,48 @@
        return ERROR_INVALID_PARAMETER;
    }
    // 准备 dev 数据
    std::vector<short> devBuffer(vecSoftElements.size() * 3 + 1, 0); // 每个软元件需要 3 个 short,外加一个计数器
    devBuffer[0] = static_cast<short>(vecSoftElements.size());                 // 第一个元素是软元件数量
    // 准备 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;                // 点数
        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 个字节
        nBufferSize += element.nElementCount * 2; // 每个点占用 2 个字节
    }
    // 锁保护及调用 mdRandREx
    // 锁保护及调用 mdRandREx
    long nRet = 0;
    std::vector<short> vecBuffer(nBufferSize / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 确保线程安全
        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;
    }
    // 将读取到的 short 数据转换为 char 数据
    // 将读取到的 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); // 高字节
        vecData[i * 2] = static_cast<char>(vecBuffer[i] & 0xFF);            // 低字节
        vecData[i * 2 + 1] = static_cast<char>((vecBuffer[i] >> 8) & 0xFF); // 高字节
    }
    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);
@@ -971,26 +971,26 @@
        return ERROR_INVALID_PARAMETER;
    }
    // 准备 dev 数据
    std::vector<long> devBuffer(vecSoftElements.size() * 3 + 1, 0); // 每个软元件需要 3 个 long,外加一个计数器
    devBuffer[0] = static_cast<long>(vecSoftElements.size());                 // 第一个元素是软元件数量
    // 准备 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); // 点数
        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
    // 锁保护及调用 mdRandWEx
    long nRet = 0;
    std::vector<short> vecBuffer(vecData.size() / 2, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 确保线程安全
        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);
@@ -999,9 +999,9 @@
    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);
@@ -1016,37 +1016,37 @@
    long nActualSize = (nSize + 1) / 2;
    std::vector<short> vecBuffer(nActualSize, 0);
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        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); // 更新错误码
        UpdateLastError(nRet); // 更新错误码
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        ConvertShortToChar(vecBuffer, vecData);
    }
    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;
    }
    // 将 vecData 转换为 short 类型的缓冲区
    // 将 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); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRemBufWriteEx(m_nPath, station.nNetNo, station.nStNo, nOffset, &nSize, vecBuffer.data());
    }
@@ -1059,7 +1059,7 @@
    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)) {
@@ -1067,14 +1067,14 @@
        return ERROR_CODE_INVALID_PARAM;
    }
    // 将缓冲区大小调整为 nSize
    // 将缓冲区大小调整为 nSize
    vecData.resize(nSize, 0);
    std::vector<short> vecBuffer((nSize + 1) / 2, 0); // 转换为 short 类型
    std::vector<short> vecBuffer((nSize + 1) / 2, 0); // 转换为 short 类型
    // 调用底层 SDK
    // 调用底层 SDK
    long nRet = 0;
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        nRet = mdRemBufReadIPEx(m_nPath, static_cast<long>(nAddress), nOffset, &nSize, vecBuffer.data());
    }
@@ -1083,14 +1083,14 @@
        LOG_ERROR(m_strLastError);
    }
    else {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全保护
        ConvertShortToChar(vecBuffer, vecData);
    }
    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)) {
@@ -1098,13 +1098,13 @@
        return ERROR_CODE_INVALID_PARAM;
    }
    // 转换 vecData 为 short 类型的缓冲区
    // 转换 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); // 线程安全
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全
        ConvertCharToShort(vecData, vecBuffer);
        nRet = mdRemBufWriteIPEx(m_nPath, static_cast<long>(nAddress), nOffset, &nSize, vecBuffer.data());
    }
@@ -1117,18 +1117,18 @@
    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;
    }
    // 确保线程安全的最小锁定范围
    // 确保线程安全的最小锁定范围
    {
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全
        std::lock_guard<std::mutex> lock(m_mtx); // 线程安全
        const short nDevType = CalculateDeviceType(station, enDevType);
        nRet = mdDevSet(m_nPath, CombineStation(station), nDevType, nDevNo);
    }
@@ -1141,16 +1141,16 @@
    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;
    }
    // 确保线程安全的最小锁定范围
    // 确保线程安全的最小锁定范围
    {
        std::lock_guard<std::mutex> lock(m_mtx);
        const short nDevType = CalculateDeviceType(station, enDevType);
@@ -1165,11 +1165,11 @@
    return nRet;
}
// 扩展位软元件设置
// 扩展位软元件设置
long CPerformanceMelsec::SetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo) {
    std::lock_guard<std::mutex> lock(m_mtx);
    // 检查参数有效性
    // 检查参数有效性
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
@@ -1186,11 +1186,11 @@
    return nRet;
}
// 扩展位软元件复位
// 扩展位软元件复位
long CPerformanceMelsec::ResetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo) {
    std::lock_guard<std::mutex> lock(m_mtx);
    // 检查参数有效性
    // 检查参数有效性
    long nRet = ValidateStation(station);
    if (nRet != 0) {
        UpdateLastError(nRet);
@@ -1207,23 +1207,23 @@
    return nRet;
}
// 执行对象站的CPU
// 执行对象站的CPU
int CPerformanceMelsec::ControlCPU(const StationIdentifier& station, ControlCode enControlCode) {
    // 验证站点参数和数据有效性
    // 验证站点参数和数据有效性
    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); // 参数错误
        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);
@@ -1237,7 +1237,7 @@
    return nRet;
}
// 事件等待
// 事件等待
int CPerformanceMelsec::WaitForBoardEvent(std::vector<short> vecEventNumbers, const int nTimeoutMs, EventDetails& details) {
    std::lock_guard<std::mutex> lock(m_mtx);
@@ -1251,12 +1251,12 @@
        return ERROR_CODE_INVALID_PARAM;
    }
    // 第 0 个元素存储数量,最大支持 64 个事件
    // 第 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);
@@ -1269,100 +1269,100 @@
    return nRet;
}
//============================================辅助函数=======================================================
// 更新最近的错误信息
//============================================辅助函数=======================================================
// 更新最近的错误信息
void CPerformanceMelsec::UpdateLastError(const int nCode) {
    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.";
        }
        if (nCode >= -20480 && nCode <= -16384) {
            // CC-Link 系统检测出的错误
            // CC-Link 系统检测出的错误
            m_strLastError = "Error detected in the CC-Link system.";
        }
        if (nCode >= -12288 && nCode <= -8193) {
            // CC-Link IE TSN 系统检测出的错误
            // CC-Link IE TSN 系统检测出的错误
            m_strLastError = "Error detected in the CC-Link IE TSN system.";
        }
        if (nCode >= -8192 && nCode <= -4097) {
            // CC-Link IE 控制网络系统检测出的错误
            // CC-Link IE 控制网络系统检测出的错误
            m_strLastError = "Error detected in the CC-Link IE control network system.";
        }
        if (nCode >= -4096 && nCode <= -257) {
            // MELSECNET/10 或 MELSECNET/网络系统错误范围
            // MELSECNET/10 或 MELSECNET/网络系统错误范围
            m_strLastError = "Errors detected in MELSECNET/10 or MELSECNET/network system.";
        }
        if (nCode >= 4096 && nCode <= 16383) {
            // MELSEC 数据链接库范围
            // 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 >= 16384 && nCode <= 20479) {
            // PLC CPU 检测范围
            // 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.";
        }
    }
}
// 检查连接状态和站点参数有效性
// 检查连接状态和站点参数有效性
int CPerformanceMelsec::ValidateStation(const StationIdentifier& station) const {
    // 检查是否已连接
    // 检查是否已连接
    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;
    }
    return 0; // 参数有效
    return 0; // 参数有效
}
// 验证站点参数和数据有效性
// 验证站点参数和数据有效性
int CPerformanceMelsec::ValidateStationAndSize(const StationIdentifier& station, const short nCount) const {
    // 验证站点参数
    // 验证站点参数
    const int nRet = ValidateStation(station);
    if (nRet != 0) {
        return nRet; // 如果站点验证失败,返回对应错误码
        return nRet; // 如果站点验证失败,返回对应错误码
    }
    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);
@@ -1381,30 +1381,30 @@
    return true;
}
//============================================静态辅助函数====================================================
// 延时,并且转发窗口消息
//============================================静态辅助函数====================================================
// 延时,并且转发窗口消息
void CPerformanceMelsec::Delay(const unsigned int nDelayMs) {
    MSG message;
    // 如果延迟时间为 0,仅处理一次消息队列
    // 如果延迟时间为 0,仅处理一次消息队列
    if (nDelayMs == 0) {
        // 非阻塞的检查消息队列
        // 非阻塞的检查消息队列
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&message);  // 将消息转化为有效的窗口消息
            DispatchMessage(&message);   // 派发消息给相应的窗口过程
            TranslateMessage(&message);  // 将消息转化为有效的窗口消息
            DispatchMessage(&message);   // 派发消息给相应的窗口过程
        }
        return;
    }
    DWORD finish;
    const DWORD start = GetTickCount();  // 获取当前的时间戳(从系统启动以来的毫秒数)
    const DWORD start = GetTickCount();  // 获取当前的时间戳(从系统启动以来的毫秒数)
    do {
        if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
            TranslateMessage(&message);  // 转换消息
            DispatchMessage(&message);   // 处理消息
            TranslateMessage(&message);  // 转换消息
            DispatchMessage(&message);   // 处理消息
        }
        Sleep(1);   // 暂停 1 毫秒,防止过度占用 CPU
        finish = GetTickCount(); // 获取当前的时间戳
    } while ((finish - start) < nDelayMs);  // 循环直到经过的时间大于指定的延迟时间
        Sleep(1);   // 暂停 1 毫秒,防止过度占用 CPU
        finish = GetTickCount(); // 获取当前的时间戳
    } while ((finish - start) < nDelayMs);  // 循环直到经过的时间大于指定的延迟时间
}
BoardType CPerformanceMelsec::FindBoardTypeByChannel(const int nChannel) {
@@ -1426,136 +1426,136 @@
    return BoardType::UNKNOWN;
}
// 合并网络号和站点号
// 合并网络号和站点号
short CPerformanceMelsec::CombineStation(const StationIdentifier& station) {
    return static_cast<short>(station.nStNo | ((station.nNetNo << 8) & 0xFF00));
}
// 计算软元件类型
// 计算软元件类型
short CPerformanceMelsec::CalculateDeviceType(const StationIdentifier& station, DeviceType 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 的值
        // 起始 I/O No. ÷ 16 的值
        nDevType += 0 / 16;
    }
    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 容器大小
    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]);       // 低字节
            vecShort[i / 2] = static_cast<unsigned char>(vecChar[i]);       // 低字节
        }
        else {
            vecShort[i / 2] |= static_cast<unsigned char>(vecChar[i]) << 8; // 高字节
            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 容器大小
    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[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 容器大小
    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]);          // 低字节
            vecShort[i / 2] = static_cast<short>(vecUint8[i]);          // 低字节
        }
        else {
            vecShort[i / 2] |= static_cast<short>(vecUint8[i] << 8);    // 高字节
            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 容器大小
    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[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
    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[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
    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[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; // 如果站点验证失败,返回对应错误码
        return nRet; // 如果站点验证失败,返回对应错误码
    }
    // 验证数据是否为空
    // 验证数据是否为空
    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");
    // 自动计算 nGroupSize
    // 自动计算 nGroupSize
    constexpr size_t nGroupSize = sizeof(U) / sizeof(T);
    // 如果 T 和 U 的大小相等,直接转换
    // 如果 T 和 U 的大小相等,直接转换
    if (sizeof(T) == sizeof(U)) {
        vecHigh.assign(vecLow.begin(), vecLow.end());
        return;
    }
    // 如果 U 的大小是 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; // 向上取整
    // 计算完整组的数量
    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)));
    }
@@ -1563,27 +1563,27 @@
    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");
    // 自动计算 nGroupSize
    // 自动计算 nGroupSize
    constexpr size_t nGroupSize = sizeof(T) / sizeof(U);
    // 如果 T 和 U 的大小相等,直接转换
    // 如果 T 和 U 的大小相等,直接转换
    if (sizeof(T) == sizeof(U)) {
        vecLow.assign(vecHigh.begin(), vecHigh.end());
        return;
    }
    // 如果 T 的大小是 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; // 低容器的大小
    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));
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
SourceCode/Bond/SGMeasurement/Logger.h
@@ -4,14 +4,14 @@
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();          // 内部打开文件
    void OpenLogFile();          // 内部打开文件
    CCriticalSection m_csLogLock;
    CString m_strCurrentLogPath;
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp
@@ -1,35 +1,39 @@
#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))
@@ -43,7 +47,7 @@
{
    m_pPlc = std::make_unique<CCCLinkIEControl>();
    if (!m_pPlc) {
        LOG_MSG(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl 实例。"), LOG_TYPE_ERROR);
        LOG_MSG(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl 实例。"), LOG_TYPE_ERROR);
        return false;
    }
@@ -52,7 +56,7 @@
        m_bConnected = false;
        CString strError;
        strError.Format(_T("PLC控制器连接失败,错误码:%d"), ret);
        strError.Format(_T("PLC控制器连接失败,错误码:%d"), ret);
        LOG_MSG(strError, LOG_TYPE_ERROR);
        return false;
@@ -90,7 +94,7 @@
bool CPLCSignalListener::Start()
{
    if (m_bRunning || !m_pPlc) {
        LOG_MSG(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
        LOG_MSG(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
        return false;
    }
@@ -160,7 +164,7 @@
        if (m_bHeartbeatLost) {
            m_bHeartbeatLost = false;
            LOG_MSG(_T("PLC心跳恢复!"), LOG_TYPE_SUCCESS);
            LOG_MSG(_T("PLC心跳恢复!"), LOG_TYPE_SUCCESS);
        }
        return true;
@@ -172,7 +176,7 @@
            if (!m_bHeartbeatLost) {
                m_bHeartbeatLost = true;
                m_nMissedHeartbeatCount = 0;
                LOG_MSG(_T("PLC心跳信号中断!"), LOG_TYPE_ERROR);
                LOG_MSG(_T("PLC心跳信号中断!"), LOG_TYPE_ERROR);
            }
            return false;
        }
@@ -232,7 +236,7 @@
            ::Sleep(m_nIntervalMs);
            CString strError;
            strError.Format(_T("PLC读取位数据失败,错误码:%d"), ret);
            strError.Format(_T("PLC读取位数据失败,错误码:%d"), ret);
            LOG_MSG(strError, LOG_TYPE_ERROR);
            continue;
@@ -240,7 +244,7 @@
        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) {
@@ -249,6 +253,13 @@
                        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;
@@ -281,25 +292,25 @@
bool CPLCSignalListener::WriteOutValues(const OutValuesArray& values)
{
    if (!m_pPlc || !m_bConnected) {
        LOG_MSG(_T("PLC未连接或未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
        LOG_MSG(_T("PLC未连接或未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
        return false;
    }
    if (PLC_RESULT_ADDR_COUNT != 4) {
        LOG_MSG(_T("PLC结果寄存器数量配置错误,必须为4个。"), LOG_TYPE_ERROR);
        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 };
        // 放大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);
        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]);
            msg.Format(_T("写入OUT%d到地址%d失败,值=%.2f"), i + 1, nTargetAddr, values[i]);
            LOG_MSG(msg, LOG_TYPE_ERROR);
            return false;
        }
@@ -307,3 +318,39 @@
    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
@@ -19,160 +19,169 @@
    ~CPLCSignalListener();
    /**
     * @brief 初始化 PLC 信号监听器。
     * @brief 初始化 PLC 信号监听器。
     *
     * @param station      目标 PLC 的站号标识符。
     * @param nIntervalMs  轮询周期(单位:毫秒),默认 200ms。
     * @return true        初始化成功。
     * @return false       初始化失败。
     * @param station      目标 PLC 的站号标识符。
     * @param nIntervalMs  轮询周期(单位:毫秒),默认 200ms。
     * @return true        初始化成功。
     * @return false       初始化失败。
     */
    bool Initialize(StationIdentifier station, int nIntervalMs = 200);
    /**
     * @brief 设置开始命令的回调函数(对应 PLC 的 Start 信号)。
     * @brief 设置开始命令的回调函数(对应 PLC 的 Start 信号)。
     *
     * @param cb  用户自定义的开始回调函数。
     * @param cb  用户自定义的开始回调函数。
     */
    void SetStartCallback(Callback cb);
    /**
     * @brief 设置停止命令的回调函数(对应 PLC 的 Stop 信号)。
     * @brief 设置停止命令的回调函数(对应 PLC 的 Stop 信号)。
     *
     * @param cb  用户自定义的停止回调函数。
     * @param cb  用户自定义的停止回调函数。
     */
    void SetStopCallback(Callback cb);
    /**
     * @brief 设置分析计算回调函数,在接收到停止命令后调用。
     * @brief 设置分析计算回调函数,在接收到停止命令后调用。
     *
     * @param cb  返回计算结果数组(OUT1~OUT4)的函数。
     * @param cb  返回计算结果数组(OUT1~OUT4)的函数。
     */
    void SetAnalyzeCallback(AnalyzeCallback cb);
    /**
     * @brief 设置日志输出回调函数。
     * @brief 设置日志输出回调函数。
     *
     * @param cb  用户提供的日志输出接口(包含日志内容和日志类型)。
     * @param cb  用户提供的日志输出接口(包含日志内容和日志类型)。
     */
    void SetLogCallback(LogCallback cb);
    /**
     * @brief 启动信号监听线程。
     * @brief 启动信号监听线程。
     *
     * @return true   启动成功。
     * @return false  启动失败(可能已运行或未初始化)。
     * @return true   启动成功。
     * @return false  启动失败(可能已运行或未初始化)。
     */
    bool Start();
    /**
     * @brief 停止信号监听线程。
     * @brief 停止信号监听线程。
     */
    void Stop();
    /**
     * @brief 向 PLC 写入分析结果值(通常为 OUT1 ~ OUT4)。
     * @brief 向 PLC 写入分析结果值(通常为 OUT1 ~ OUT4)。
     *
     * @param values  包含四个 double 类型的结果值,将转换为整数后写入 PLC。
     * @return true   写入成功。
     * @return false  写入失败。
     * @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 输出日志信息(封装日志回调)。
     * @brief 输出日志信息(封装日志回调)。
     *
     * @param strText 日志内容文本。
     * @param nType   日志类型(LOG_TYPE_NORMAL / ERROR / WARNING 等)。
     * @param strText 日志内容文本。
     * @param nType   日志类型(LOG_TYPE_NORMAL / ERROR / WARNING 等)。
     */
    void LogInfo(const CString& strText, int nType);
    /**
     * @brief 向 PLC 写入心跳信号(交替位)。
     * @brief 向 PLC 写入心跳信号(交替位)。
     *
     * @return true  写入成功。
     * @return false 写入失败(如未连接)。
     * @return true  写入成功。
     * @return false 写入失败(如未连接)。
     */
    bool SendHeartbeat();
    /**
     * @brief 检查从 PLC 读取到的心跳信号是否发生变化。
     * @brief 检查从 PLC 读取到的心跳信号是否发生变化。
     *
     * @return true  心跳有变化(PLC 正常在线)。
     * @return false 心跳无变化(可能离线)。
     * @return true  心跳有变化(PLC 正常在线)。
     * @return false 心跳无变化(可能离线)。
     */
    bool CheckHeartbeat();
    /**
     * @brief 监控 PLC 心跳是否中断,并自动记录状态。
     * @brief 监控 PLC 心跳是否中断,并自动记录状态。
     *
     * @return true  PLC 在线或在允许的未响应次数内。
     * @return false PLC 心跳中断,超过允许阈值。
     * @return true  PLC 在线或在允许的未响应次数内。
     * @return false PLC 心跳中断,超过允许阈值。
     */
    bool MonitorHeartbeat();
    /**
     * @brief 启动心跳监控线程(独立于信号监听线程)。
     * @brief 启动心跳监控线程(独立于信号监听线程)。
     */
    void StartHeartbeatMonitor();
    /**
     * @brief 停止心跳监控线程。
     * @brief 停止心跳监控线程。
     */
    void StopHeartbeatMonitor();
    /**
     * @brief 向指定软元件位写入一个脉冲(先写1,延时后自动写0)。
     * @brief 向指定软元件位写入一个脉冲(先写1,延时后自动写0)。
     *
     * @param eDevType 位软元件类型(如 M/B)。
     * @param nBitNo   位地址编号。
     * @param nDelayMs 脉冲持续时间,默认50毫秒。
     * @param eDevType 位软元件类型(如 M/B)。
     * @param nBitNo   位地址编号。
     * @param nDelayMs 脉冲持续时间,默认50毫秒。
     */
    void PulseBitDevice(DeviceType eDevType, long nBitNo, int nDelayMs = 50);
    /**
     * @brief 管理 ACK 响应的生命周期,超时自动清除。
     * @brief 管理 ACK 响应的生命周期,超时自动清除。
     *
     * @param i                控制位索引(0=Start,1=Stop)。
     * @param bCurrTriggerBit  当前从 PLC 读取到的触发位状态。
     * @param i                控制位索引(0=Start,1=Stop)。
     * @param bCurrTriggerBit  当前从 PLC 读取到的触发位状态。
     */
    void HandleAckLife(int i, bool bCurrTriggerBit);
    /**
     * @brief 主监听线程逻辑,循环读取 PLC 控制信号并处理触发。
     * @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 {};
    }
@@ -510,22 +547,17 @@
        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
@@ -116,48 +116,24 @@
    std::vector<AlarmData> getAlarms(int startPosition, int count);
    /**
     * 获取筛选后的报警数据
     * @param id 报警ID的筛选条件
     * @param severityLevel 报警等级筛选条件
     * @param deviceName 设备名称的筛选条件
     * @param unitName 单元名称的筛选条件
     * @param description 报警描述的筛选条件
     * 筛选报警数据
     * @param keyword 关键字筛选条件
     * @param startTime 起始时间筛选条件
     * @param endTime 结束时间筛选条件
     * @param pageNumber 页码
     * @param pageSize 每页的记录数
     * @return 包含查询结果的报警数据
     * @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);
    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/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;