#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 命令输入配置(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软元件类型宏(用于应答、数据写入)===
|
#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 产品ID配置(PLC -> PC)===
|
#define PLC_PRODUCT_ID_ADDR 0x1B160 // 产品ID起始地址 (W1B160)
|
#define PLC_PRODUCT_ID_WORDS 10 // 产品ID长度(10个Word)
|
|
#define IS_RISING_EDGE(prev, curr) (!(prev) && (curr))
|
|
CPLCSignalListener::CPLCSignalListener() = default;
|
|
CPLCSignalListener::~CPLCSignalListener() {
|
Stop();
|
}
|
|
bool CPLCSignalListener::Initialize(StationIdentifier station, int nIntervalMs/* = 200*/)
|
{
|
m_pPlc = std::make_unique<CCCLinkIEControl>();
|
if (!m_pPlc) {
|
LOG_MSG(_T("PLC控制器初始化失败,无法创建 CCCLinkIEControl 实例。"), LOG_TYPE_ERROR);
|
return false;
|
}
|
|
int ret = m_pPlc->Connect(CC_LINK_IE_CONTROL_CHANNEL(1));
|
if (ret != 0) {
|
m_bConnected = false;
|
|
CString strError;
|
strError.Format(_T("PLC控制器连接失败,错误码:%d"), ret);
|
LOG_MSG(strError, LOG_TYPE_ERROR);
|
|
return false;
|
}
|
|
m_bConnected = true;
|
m_station = station;
|
m_nIntervalMs = nIntervalMs;
|
|
m_vecPrevBits.assign(PLC_CMD_BIT_COUNT, false);
|
|
return true;
|
}
|
|
void CPLCSignalListener::SetStartCallback(Callback cb)
|
{
|
m_cbStart = std::move(cb);
|
}
|
|
void CPLCSignalListener::SetStopCallback(Callback cb)
|
{
|
m_cbStop = std::move(cb);
|
}
|
|
void CPLCSignalListener::SetAnalyzeCallback(AnalyzeCallback cb)
|
{
|
m_cbAnalyze = std::move(cb);
|
}
|
|
void CPLCSignalListener::SetLogCallback(LogCallback cb)
|
{
|
m_cbLog = std::move(cb);
|
}
|
|
bool CPLCSignalListener::Start()
|
{
|
if (m_bRunning || !m_pPlc) {
|
LOG_MSG(_T("PLC信号监听器已在运行或PLC控制器未初始化。"), LOG_TYPE_ERROR);
|
return false;
|
}
|
|
m_bRunning = true;
|
m_thread = std::thread(&CPLCSignalListener::ThreadProc, this);
|
|
StartHeartbeatMonitor();
|
return true;
|
}
|
|
void CPLCSignalListener::Stop()
|
{
|
m_bRunning = false;
|
if (m_thread.joinable()) {
|
m_thread.join();
|
}
|
|
StopHeartbeatMonitor();
|
}
|
|
void CPLCSignalListener::LogInfo(const CString& strText, int nType)
|
{
|
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->ResetBitDeviceEx(m_station, eDevType, nBitNo);
|
}
|
|
void CPLCSignalListener::HandleAckLife(int i, bool bCurrTriggerBit)
|
{
|
if (m_vecAckSent[i] && !bCurrTriggerBit) {
|
m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
|
m_vecAckSent[i] = false;
|
}
|
|
if (m_vecAckSent[i]) {
|
if (++m_vecAckCounter[i] > PLC_ACK_MAX_LIFE) {
|
m_pPlc->ResetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, long(PLC_ACK_BASE_BIT + i));
|
m_vecAckSent[i] = false;
|
}
|
}
|
}
|
|
void CPLCSignalListener::ThreadProc()
|
{
|
while (m_bRunning && m_bConnected) {
|
BitContainer vecBits;
|
int ret = m_pPlc->ReadBitDataEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_CMD_BIT_START, PLC_CMD_BIT_COUNT, vecBits);
|
if (ret != 0 && vecBits.size() != PLC_CMD_BIT_COUNT) {
|
::Sleep(m_nIntervalMs);
|
|
CString strError;
|
strError.Format(_T("PLC读取位数据失败,错误码:%d"), ret);
|
LOG_MSG(strError, LOG_TYPE_ERROR);
|
|
continue;
|
}
|
|
for (int i = 0; i < PLC_CMD_BIT_COUNT; ++i) {
|
if (IS_RISING_EDGE(m_vecPrevBits[i], vecBits[i])) {
|
// 上升沿触发
|
switch (i) {
|
case 0:
|
// Start 命令
|
if (m_cbStart) {
|
m_cbStart();
|
WriteOutValues(OutValuesArray{ 0.0, 0.0, 0.0, 0.0 });
|
|
std::string strProductID;
|
if (ReadProductID(strProductID)) {
|
CString msg;
|
msg.Format(_T("读取到产品ID:%s"), CString(strProductID.c_str()));
|
LOG_MSG(msg, LOG_TYPE_SUCCESS);
|
}
|
}
|
|
// 发送应答信号
|
if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
|
m_vecAckSent[i] = true;
|
m_vecAckCounter[i] = 0;
|
}
|
break;
|
|
case 1:
|
// Stop 命令
|
if (m_cbStop) {
|
m_cbStop();
|
}
|
|
// Analyze 命令
|
if (m_cbAnalyze) {
|
auto results = m_cbAnalyze();
|
WriteOutValues(results);
|
}
|
|
// 发送应答信号
|
if (m_pPlc->SetBitDeviceEx(m_station, PLC_BIT_DEVICE_TYPE, PLC_ACK_BASE_BIT + i) == 0) {
|
m_vecAckSent[i] = true;
|
m_vecAckCounter[i] = 0;
|
}
|
break;
|
}
|
}
|
|
HandleAckLife(i, vecBits[i]);
|
m_vecPrevBits[i] = vecBits[i];
|
}
|
|
::Sleep(m_nIntervalMs);
|
}
|
}
|
|
bool CPLCSignalListener::WriteOutValues(const OutValuesArray& values)
|
{
|
if (!m_pPlc || !m_bConnected) {
|
LOG_MSG(_T("PLC未连接或未初始化,无法写入输出值。"), LOG_TYPE_ERROR);
|
return false;
|
}
|
|
if (PLC_RESULT_ADDR_COUNT != 4) {
|
LOG_MSG(_T("PLC结果寄存器数量配置错误,必须为4个。"), LOG_TYPE_ERROR);
|
return false;
|
}
|
|
for (int i = 0; i < PLC_RESULT_ADDR_COUNT; ++i) {
|
double dVal = values[i];
|
int32_t nScaled = 0;
|
|
if (dVal == DBL_MAX || dVal == DBL_MIN || std::isnan(dVal)) {
|
nScaled = static_cast<int32_t>(dVal);
|
}
|
else {
|
nScaled = static_cast<int32_t>(std::round(dVal * 1000.0));
|
}
|
|
short nTargetAddr = PLC_RESULT_ADDR_START + i * 2;
|
DWordContainer vec = { static_cast<uint32_t>(nScaled) };
|
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, dVal);
|
LOG_MSG(msg, LOG_TYPE_ERROR);
|
return false;
|
}
|
}
|
|
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;
|
}
|
|
bool CPLCSignalListener::WriteProductID(const std::string& strProductID)
|
{
|
if (!m_pPlc || !m_bConnected) {
|
LOG_MSG(_T("PLC未连接或未初始化,无法写入产品ID。"), LOG_TYPE_ERROR);
|
return false;
|
}
|
|
|
WordContainer vec;
|
vec.reserve(PLC_PRODUCT_ID_WORDS);
|
for (size_t i = 0; i < strProductID.size();) {
|
unsigned char c1 = static_cast<unsigned char>(strProductID[i]);
|
unsigned char c2 = 0;
|
|
if (i + 1 < strProductID.size()) {
|
c2 = static_cast<unsigned char>(strProductID[i + 1]);
|
}
|
|
uint16_t w = static_cast<uint16_t>(c2 << 8 | c1);
|
vec.push_back(w);
|
|
i += 2;
|
}
|
|
while (vec.size() < PLC_PRODUCT_ID_WORDS) {
|
vec.push_back(0);
|
}
|
|
int ret = m_pPlc->WriteWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, PLC_PRODUCT_ID_ADDR, vec);
|
if (ret != 0) {
|
CString msg;
|
msg.Format(_T("写入产品ID失败,错误码=%d"), ret);
|
LOG_MSG(msg, LOG_TYPE_ERROR);
|
return false;
|
}
|
|
return true;
|
}
|