1. SGMeasurement实现规划的地址
2. 优化读取位时输入的地址不是8的倍数的问题
3. 添加读写心跳的功能
已修改6个文件
311 ■■■■ 文件已修改
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.cpp 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp 174 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.h 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.cpp
@@ -703,18 +703,19 @@
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const long nWordCount = (nBitCount + 15) / 16;
    const long nByteSize = nWordCount * sizeof(short);
    // === 自动对齐到起始字 ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + nBitCount;
    long nWordCount = (nTotalBits + 15) / 16;
    long nByteSize = nWordCount * sizeof(short);
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nDevNo, nByteSize, vecRaw);
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, nByteSize, vecRaw);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
@@ -722,10 +723,11 @@
    for (long i = 0; i < nWordCount; ++i) {
        short word = static_cast<unsigned char>(vecRaw[i * 2]) |
            (static_cast<unsigned char>(vecRaw[i * 2 + 1]) << 8);
        for (int j = 0; j < 16; ++j) {
            long bitIndex = i * 16 + j;
            if (bitIndex >= nBitOffset && vecData.size() < static_cast<size_t>(nBitCount)) {
            vecData.push_back((word & (1 << j)) != 0);
            if (vecData.size() >= static_cast<size_t>(nBitCount)) {
                return 0;
            }
        }
    }
@@ -826,30 +828,48 @@
        return nRet;
    }
    if (nDevNo % 8 != 0) {
        UpdateLastError(ERROR_CODE_INVALID_PARAM);
        return ERROR_CODE_INVALID_PARAM;
    }
    const short nDevType = CalculateDeviceType(station, enDevType);
    const size_t nWordCount = (vecData.size() + 15) / 16;
    // === 1. 自动对齐起始地址 ===
    long nWordAlignedStartBit = nDevNo / 16 * 16;
    long nBitOffset = nDevNo - nWordAlignedStartBit;
    long nTotalBits = nBitOffset + static_cast<long>(vecData.size());
    size_t nWordCount = (nTotalBits + 15) / 16;
    // === 2. 先读取原始值以支持非对齐覆盖 ===
    std::vector<char> vecRaw;
    nRet = ReadDataEx(station, nDevType, nWordAlignedStartBit, static_cast<long>(nWordCount * sizeof(short)), vecRaw);
    if (nRet != 0) {
        UpdateLastError(nRet);
        return nRet;
    }
    // === 3. 合并新数据 ===
    std::vector<short> vecWordBuffer(nWordCount, 0);
    for (size_t i = 0; i < nWordCount; ++i) {
        vecWordBuffer[i] = static_cast<unsigned char>(vecRaw[i * 2]) | (static_cast<unsigned char>(vecRaw[i * 2 + 1]) << 8);
    }
    for (size_t i = 0; i < vecData.size(); ++i) {
        size_t bitIndex = nBitOffset + i;
        size_t wordIdx = bitIndex / 16;
        size_t bitPos = bitIndex % 16;
        if (vecData[i]) {
            vecWordBuffer[i / 16] |= (1 << (i % 16));
            vecWordBuffer[wordIdx] |= (1 << bitPos);
        }
        else {
            vecWordBuffer[wordIdx] &= ~(1 << bitPos);
        }
    }
    // 转换 short -> char
    std::vector<char> vecByteBuffer;
    vecByteBuffer.resize(nWordCount * sizeof(short));
    // === 4. 转为字节流写入 ===
    std::vector<char> vecByteBuffer(nWordCount * 2);
    for (size_t i = 0; i < nWordCount; ++i) {
        vecByteBuffer[i * 2] = static_cast<char>(vecWordBuffer[i] & 0xFF);
        vecByteBuffer[i * 2 + 1] = static_cast<char>((vecWordBuffer[i] >> 8) & 0xFF);
    }
    return WriteDataEx(station, nDevType, nDevNo, vecByteBuffer);
    return WriteDataEx(station, nDevType, nWordAlignedStartBit, vecByteBuffer);
}
// 扩展写字数据
@@ -1146,7 +1166,7 @@
}
// 扩展位软元件设置
long CPerformanceMelsec::SetBitDeviceEx(const StationIdentifier& station, long nDevType, long nDevNo) {
long CPerformanceMelsec::SetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo) {
    std::lock_guard<std::mutex> lock(m_mtx);
    // 检查参数有效性
@@ -1156,6 +1176,7 @@
        return nRet;
    }
    long nDevType = static_cast<long>(enDevType);
    nRet = mdDevSetEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo);
    if (nRet != 0) {
        UpdateLastError(nRet);
@@ -1166,7 +1187,7 @@
}
// 扩展位软元件复位
long CPerformanceMelsec::ResetBitDeviceEx(const StationIdentifier& station, long nDevType, long nDevNo) {
long CPerformanceMelsec::ResetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo) {
    std::lock_guard<std::mutex> lock(m_mtx);
    // 检查参数有效性
@@ -1176,6 +1197,7 @@
        return nRet;
    }
    long nDevType = static_cast<long>(enDevType);
    nRet = mdDevRstEx(m_nPath, station.nNetNo, station.nStNo, nDevType, nDevNo);
    if (nRet != 0) {
        UpdateLastError(nRet);
SourceCode/Bond/SGMeasurement/CCLinkPerformance/PerformanceMelsec.h
@@ -413,8 +413,8 @@
    int ResetBitDevice(const StationIdentifier& station, DeviceType enDevType, short enDevNo);
    // 扩展设置/复位对象站的指定位软元件
    long SetBitDeviceEx(const StationIdentifier& station, long nDevType, long nDevNo);
    long ResetBitDeviceEx(const StationIdentifier& station, long nDevType, long nDevNo);
    long SetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo);
    long ResetBitDeviceEx(const StationIdentifier& station, DeviceType enDevType, long nDevNo);
    // 执行对象站的CPU
    int ControlCPU(const StationIdentifier& station, ControlCode enControlCode);
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp
@@ -7,17 +7,29 @@
#define LOG_TYPE_WARNING    1
#define LOG_TYPE_NORMAL     2
// === PLC 控制命令输入位配置 ===
#define PLC_CMD_BIT_START       0     // PLC命令起始位(通常为B0)
#define PLC_CMD_BIT_COUNT       2     // 总共几个命令位(B0=Start, B1=Stop)
// === 日志打印宏定义 ===
#define LOG_MSG(msg, type) LogInfo(msg, type)
// === 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 应答输出配置(PC -> PLC) ===
#define PLC_ACK_MAX_LIFE        5     // PLC响应信号最大保留周期数(每周期为 m_nIntervalMs 毫秒)
#define PLC_ACK_BASE_BIT        10    // PLC应答起始地址(B10表示B0的应答;B11表示B1)
#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_RESULT_ADDR_START   0x37B0  // PLC结果寄存器起始地址(如W37B0)
#define PLC_RESULT_ADDR_COUNT   4       // 结果寄存器数量(如W37B0, W37B2, W37B4, W37B6)
#define IS_RISING_EDGE(prev, curr) (!(prev) && (curr))
@@ -31,20 +43,18 @@
{
    m_pPlc = std::make_unique<CCCLinkIEControl>();
    if (!m_pPlc) {
        if (m_cbLog) {
            m_cbLog(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl 实例。"), LOG_TYPE_ERROR);
        }
        LOG_MSG(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl 实例。"), LOG_TYPE_ERROR);
        return false;
    }
    int ret = m_pPlc->Connect(CC_LINK_IE_CONTROL_CHANNEL(1));
    if (ret != 0) {
        m_bConnected = false;
        if (m_cbLog) {
            CString strError;
            strError.Format(_T("PLC控制器连接失败,错误码:%d"), ret);
            m_cbLog(strError, LOG_TYPE_ERROR);
        }
        LOG_MSG(strError, LOG_TYPE_ERROR);
        return false;
    }
@@ -80,14 +90,14 @@
bool CPLCSignalListener::Start()
{
    if (m_bRunning || !m_pPlc) {
        if (m_cbLog) {
            m_cbLog(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
        }
        LOG_MSG(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
        return false;
    }
    m_bRunning = true;
    m_thread = std::thread(&CPLCSignalListener::ThreadProc, this);
    StartHeartbeatMonitor();
    return true;
}
@@ -97,25 +107,117 @@
    if (m_thread.joinable()) {
        m_thread.join();
    }
    StopHeartbeatMonitor();
}
void CPLCSignalListener::PulseBitDevice(DeviceType eDevType, short nBitNo, int nDelayMs/* = 50*/)
void CPLCSignalListener::LogInfo(const CString& strText, int nType)
{
    m_pPlc->SetBitDevice(m_station, eDevType, nBitNo);
    if (m_cbLog) {
        m_cbLog(strText, nType);
    }
}
bool CPLCSignalListener::SendHeartbeat()
{
    if (!m_pPlc || !m_bConnected) {
        return false;
    }
    static bool bToggle = false;
    bToggle = !bToggle;
    int ret = m_pPlc->WriteBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_HEARTBEAT_PC_TO_PLC_ADDR, BitContainer{ bToggle });
    return (ret == 0);
}
bool CPLCSignalListener::CheckHeartbeat()
{
    static bool bLastHeartbeat = false;
    if (!m_pPlc || !m_bConnected) {
        return false;
    }
    BitContainer vec;
    int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_HEARTBEAT_PLC_TO_PC_ADDR, 1, vec);
    if (ret != 0 || vec.empty()) {
        return false;
    }
    bool bCurrent = vec[0];
    bool bChanged = (bCurrent != bLastHeartbeat);
    bLastHeartbeat = bCurrent;
    return bChanged;
}
bool CPLCSignalListener::MonitorHeartbeat()
{
    if (CheckHeartbeat()) {
        m_nMissedHeartbeatCount = 0;
        if (m_bHeartbeatLost) {
            m_bHeartbeatLost = false;
            LOG_MSG(_T("PLC心跳恢复!"), LOG_TYPE_SUCCESS);
        }
        return true;
    }
    else {
        m_nMissedHeartbeatCount++;
        if (m_nMissedHeartbeatCount > MAX_MISSED_HEARTBEAT) {
            if (!m_bHeartbeatLost) {
                m_bHeartbeatLost = true;
                m_nMissedHeartbeatCount = 0;
                LOG_MSG(_T("PLC心跳信号中断!"), LOG_TYPE_ERROR);
            }
            return false;
        }
    }
    return true;
}
void CPLCSignalListener::StartHeartbeatMonitor()
{
    m_bHeartbeatRunning = true;
    m_heartbeatThread = std::thread([this]() {
        while (m_bHeartbeatRunning) {
            SendHeartbeat();
            MonitorHeartbeat();
            std::this_thread::sleep_for(std::chrono::milliseconds(m_nIntervalMs * 5));
        }
    });
}
void CPLCSignalListener::StopHeartbeatMonitor()
{
    m_bHeartbeatRunning = false;
    if (m_heartbeatThread.joinable()) {
        m_heartbeatThread.join();
    }
}
void CPLCSignalListener::PulseBitDevice(DeviceType eDevType, long nBitNo, int nDelayMs/* = 50*/)
{
    m_pPlc->SetBitDeviceEx(m_station, eDevType, nBitNo);
    ::Sleep(nDelayMs);
    m_pPlc->ResetBitDevice(m_station, eDevType, nBitNo);
    m_pPlc->ResetBitDeviceEx(m_station, eDevType, nBitNo);
}
void CPLCSignalListener::HandleAckLife(int i, bool bCurrTriggerBit)
{
    if (m_vecAckSent[i] && !bCurrTriggerBit) {
        m_pPlc->ResetBitDevice(m_station, PLC_BIT_DEVICE_TYPE, short(PLC_ACK_BASE_BIT + i));
        m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
        m_vecAckSent[i] = false;
    }
    if (m_vecAckSent[i]) {
        if (++m_vecAckCounter[i] > PLC_ACK_MAX_LIFE) {
            m_pPlc->ResetBitDevice(m_station, PLC_BIT_DEVICE_TYPE, short(PLC_ACK_BASE_BIT + i));
            m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
            m_vecAckSent[i] = false;
        }
    }
@@ -123,17 +225,15 @@
void CPLCSignalListener::ThreadProc()
{
    while (m_bRunning) {
    while (m_bRunning && m_bConnected) {
        BitContainer vecBits;
        int ret = m_pPlc->ReadBitData(m_station, PLC_BIT_DEVICE_TYPE, PLC_CMD_BIT_START, PLC_CMD_BIT_COUNT, vecBits);
        if (ret != 0/*&& vecBits.size() != PLC_CMD_BIT_COUNT*/) {
        int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_CMD_BIT_START, PLC_CMD_BIT_COUNT, vecBits);
        if (ret != 0 && vecBits.size() != PLC_CMD_BIT_COUNT) {
            ::Sleep(m_nIntervalMs);
            if (m_cbLog) {
                CString strError;
                strError.Format(_T("PLC读取位数据失败,错误码:%d"), ret);
                m_cbLog(strError, LOG_TYPE_ERROR);
            }
            LOG_MSG(strError, LOG_TYPE_ERROR);
            continue;
        }
@@ -146,7 +246,7 @@
                    if (m_cbStart) {
                        m_cbStart();
                        WriteOutValues(OutValuesArray{ 0.0, 0.0, 0.0, 0.0 });
                        if (m_pPlc->SetBitDevice(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                        if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                            m_vecAckSent[i] = true;
                            m_vecAckCounter[i] = 0;
                        }
@@ -156,7 +256,7 @@
                case 1:
                    if (m_cbStop) {
                        m_cbStop();
                        if (m_pPlc->SetBitDevice(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                        if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
                            m_vecAckSent[i] = true;
                            m_vecAckCounter[i] = 0;
                        }
@@ -180,21 +280,27 @@
bool CPLCSignalListener::WriteOutValues(const OutValuesArray& values)
{
    if (!m_pPlc) {
        if (m_cbLog) {
            m_cbLog(_T("PLC控制器未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
        }
    if (!m_pPlc || !m_bConnected) {
        LOG_MSG(_T("PLC未连接或未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
        return false;
    }
    static const short PLC_RESULT_ADDR[4] = { 100, 102, 104, 106 };
    if (PLC_RESULT_ADDR_COUNT != 4) {
        LOG_MSG(_T("PLC结果寄存器数量配置错误,必须为4个。"), LOG_TYPE_ERROR);
        return false;
    }
    for (int i = 0; i < 4; ++i) {
    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 };
        int ret = m_pPlc->WriteWordData(m_station, PLC_WORD_DEVICE_TYPE, PLC_RESULT_ADDR[i], vec);
        short nTargetAddr = PLC_RESULT_ADDR_START + i * 2;
        int ret = m_pPlc->WriteWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, nTargetAddr, vec);
        if (ret != 0) {
            CString msg;
            msg.Format(_T("写入OUT%d到地址%d失败,值=%.2f"), i + 1, nTargetAddr, values[i]);
            LOG_MSG(msg, LOG_TYPE_ERROR);
            return false;
        }
    }
SourceCode/Bond/SGMeasurement/PLCSignalListener.h
@@ -31,26 +31,46 @@
    bool WriteOutValues(const OutValuesArray& values);
private:
    void PulseBitDevice(DeviceType eDevType, short nBitNo, int nDelayMs = 50);
    void LogInfo(const CString& strText, int nType);
    bool SendHeartbeat();
    bool CheckHeartbeat();
    bool MonitorHeartbeat();
    void StartHeartbeatMonitor();
    void StopHeartbeatMonitor();
    void PulseBitDevice(DeviceType eDevType, long nBitNo, int nDelayMs = 50);
    void HandleAckLife(int i, bool bCurrTriggerBit);
    void ThreadProc();
private:
    std::unique_ptr<CCCLinkIEControl> m_pPlc;
    StationIdentifier m_station;
    int m_nIntervalMs = 200;
    // === PLC 通信核心对象 ===
    std::unique_ptr<CCCLinkIEControl> m_pPlc; // PLC 通信控制器
    StationIdentifier m_station;              // PLC 站号
    std::atomic<bool> m_bConnected{ false };  // 是否成功连接
    std::atomic<bool> m_bConnected{ false };
    std::atomic<bool> m_bRunning{ false };
    std::thread m_thread;
    std::vector<bool> m_vecPrevBits;
    // === 控制参数 ===
    int m_nIntervalMs = 200;                  // 轮询周期(ms)
    std::array<bool, 2> m_vecAckSent = { false, false }; // 是否已发送 M10/M11
    std::array<int, 2> m_vecAckCounter = { 0, 0 };       // 计数器,超时自动清除
    // === 命令触发状态缓存 ===
    std::vector<bool> m_vecPrevBits;          // 上一周期的命令位状态(用于检测上升沿)
    Callback m_cbStart;
    Callback m_cbStop;
    AnalyzeCallback m_cbAnalyze;
    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;                     // 主监听线程对象
    // === 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 掉线)
};
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj
@@ -175,6 +175,7 @@
      <PreprocessorDefinitions>_WINDOWS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
      <AdditionalIncludeDirectories>.;..;.\DLL\64bit;.\CCLinkPerformance;..\MELSECSDK\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <Optimization>MaxSpeed</Optimization>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
@@ -887,13 +887,13 @@
        if (type == -1) {
            AppendLogLineRichStyled(msg, LOG_COLOR_ERROR);
        }
        if (type == 0) {
        else if (type == 0) {
            AppendLogLineRichStyled(msg, LOG_COLOR_SUCCESS);
        }
        else if (type == 1) {
            AppendLogLineRichStyled(msg, LOG_COLOR_WARNING);
        }
        else {
        else if (type == 2) {
            AppendLogLineRichStyled(msg, LOG_COLOR_NORMAL);
        }
    });
@@ -903,9 +903,14 @@
    // 设置 PLC 监听器的开始采集回调函数
    m_plcListener.SetStartCallback([this]() {
        InitDataStorage();
        if (!m_bConnected) {
            ConnectToDevice();
        }
        if (InitDataStorage()) {
        StartDataStorage();
        UpdateControlStatus(m_bConnected, m_bSaving);
        }
    });
    // 设置 PLC 监听器的停止采集回调函数
@@ -916,6 +921,11 @@
    // 设置 PLC 监听器的分析回调函数
    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 };
        }
        std::array<double, 4> result;
        for (int i = 0; i < 4; ++i) {
            result[i] = AnalyzeStoredData(i + 1); // OUT1 ~ OUT4