#include "iec104masterqt.h" #include #include #include #include #include IEC104MasterQt::IEC104MasterQt(QObject *parent) : QObject(parent), m_socket(new QTcpSocket(this)), m_connected(false), m_rxCounter(0), m_txCounter(0), m_lastAckRx(0), m_lastAckTx(0), m_t0(30), m_t1(15), m_t2(10), m_t3(20), m_k(12), m_w(8), nasdu(1) { // Setup timers m_timerT1 = new QTimer(this); m_timerT2 = new QTimer(this); m_timerT3 = new QTimer(this); // m_timerT1->setSingleShot(true); // m_timerT2->setSingleShot(true); // m_timerT3->setSingleShot(false); // Connect socket signals connect(m_socket, &QTcpSocket::connected, this, &IEC104MasterQt::onSocketConnected); connect(m_socket, &QTcpSocket::disconnected, this, &IEC104MasterQt::onSocketDisconnected); connect(m_socket, &QTcpSocket::readyRead, this, &IEC104MasterQt::onSocketReadyRead); // 兼容不同Qt版本的错误信号连接 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // Qt 5.15+ 使用 errorOccurred connect(m_socket, &QTcpSocket::errorOccurred, this, &IEC104MasterQt::onSocketError); #else // Qt 5.15 之前使用 error connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError))); #endif // Connect timer signals bool status=connect(m_timerT1, SIGNAL(timeout()), this, SLOT(onTimerT1Timeout())); status = connect(m_timerT2, SIGNAL(timeout()), this, SLOT(onTimerT2Timeout())); status = connect(m_timerT3, SIGNAL(timeout()), this, SLOT(onTimerT3Timeout())); status = false; //m_timerT3->start(m_t3 * 1000); } IEC104MasterQt::~IEC104MasterQt() { closeLink(); } bool IEC104MasterQt::openLink() { if (m_connected) { emit logMessage("Already connected"); return true; } emit logMessage(QString("Connecting to %1:%2").arg(m_serverIP).arg(m_port)); m_socket->connectToHost(m_serverIP, m_port); return true; } void IEC104MasterQt::closeLink() { if (m_connected) { sendStopDT(); } m_socket->disconnectFromHost(); m_connected = false; emit connectionStatusChanged(false); m_timerT1->stop(); m_timerT2->stop(); m_timerT3->stop(); } void IEC104MasterQt::setConnectionParameters(const QString &serverIP, int port) { m_serverIP = serverIP; m_port = port; } bool IEC104MasterQt::sendStartDT() { return sendUMessage(0x07); // STARTDT ACT } bool IEC104MasterQt::sendStopDT() { return sendUMessage(0x13); // STOPDT ACT } bool IEC104MasterQt::sendTestFR() { return sendUMessage(0x43); // TESTFR ACT } bool IEC104MasterQt::sendInterrogation(int asdu, int group) { QByteArray data; data.append(static_cast(0x64)); // type data.append(static_cast(0x01)); // qual data.append(static_cast(0x06)); // cause data.append(static_cast(0x00)); data.append(static_cast(asdu & 0xFF)); data.append(static_cast((asdu >> 8) & 0xFF)); data.append(static_cast(0x00)); data.append(static_cast(0x00)); data.append(static_cast(0x00)); data.append(static_cast(20 + group)); return sendIMessage(data); } bool IEC104MasterQt::sendASDUMessage(const QByteArray &data) { return sendIMessage(data); } bool IEC104MasterQt::sendUMessage(int command) { if (!m_connected) { emit logMessage("Not connected, cannot send U message"); return false; } APCI apci; apci.start = 0x68; apci.len = 0x04; apci.field1 = 0x03 | command; apci.field2 = 0; apci.field3 = 0; apci.field4 = 0; QByteArray data(reinterpret_cast(&apci), sizeof(apci)); qint64 bytesWritten = m_socket->write(data); if (bytesWritten == sizeof(apci)) { emit dataSent(data); emit logMessage(QString("Sent U message: %1").arg(toHexString(data).constData())); return true; } else { emit logMessage("Failed to send U message"); return false; } } bool IEC104MasterQt::sendSMessage() { if (!m_connected) { return false; } APCI apci; apci.start = 0x68; apci.len = 0x04; apci.field1 = 0x01; apci.field2 = 0; apci.field3 = m_rxCounter & 0xFE; apci.field4 = (m_rxCounter >> 8) & 0xFF; QByteArray data(reinterpret_cast(&apci), sizeof(apci)); if (m_socket->write(data) == sizeof(apci)) { emit dataSent(data); emit logMessage(QString("Sent S message: %1").arg(toHexString(data).constData())); return true; } return false; } bool IEC104MasterQt::sendIMessage(const QByteArray &asduData) { if (!m_connected) { return false; } QByteArray buffer; buffer.resize(sizeof(APCI) + asduData.size()); APCI *apci = reinterpret_cast(buffer.data()); apci->start = 0x68; apci->len = 0x04 + asduData.size(); apci->field1 = m_txCounter & 0xFE; apci->field2 = (m_txCounter >> 8) & 0xFF; apci->field3 = m_rxCounter & 0xFE; apci->field4 = (m_rxCounter >> 8) & 0xFF; memcpy(buffer.data() + sizeof(APCI), asduData.constData(), asduData.size()); if (m_socket->write(buffer) == buffer.size()) { emit dataSent(buffer); emit logMessage(QString("Sent I message: %1").arg(toHexString(buffer).constData())); m_txCounter += 2; return true; } return false; } void IEC104MasterQt::onSocketConnected() { m_connected = true; emit connectionStatusChanged(true); emit logMessage("Socket connected successfully"); // Start data transfer sendStartDT(); // 使用单次定时器确保在事件循环中执行 //QTimer::singleShot(10, this, &IEC104MasterQt::delayedStartT3Timer); // Start supervision timer m_timerT3->start(m_t3 * 1000); // if (!m_timerT3->isActive()) // { // m_timerT3->start(3000); // } } void IEC104MasterQt::onSocketDisconnected() { m_connected = false; emit connectionStatusChanged(false); emit logMessage("Socket disconnected"); m_timerT1->stop(); m_timerT2->stop(); m_timerT3->stop(); } void IEC104MasterQt::onSocketReadyRead() { QByteArray data = m_socket->readAll(); emit dataReceived(data); emit logMessage(QString("Received data: %1").arg(toHexString(data).constData())); processReceivedData(data); } void IEC104MasterQt::onSocketError(QAbstractSocket::SocketError error) { Q_UNUSED(error) emit logMessage(QString("Socket error: %1").arg(m_socket->errorString())); if (m_connected) { m_connected = false; emit connectionStatusChanged(false); } } void IEC104MasterQt::onTimerT1Timeout() { emit logMessage("T1 timeout - No confirmation received"); closeLink(); } void IEC104MasterQt::onTimerT2Timeout() { sendSMessage(); //m_timerT2->start(m_t2 * 1000); } void IEC104MasterQt::onTimerT3Timeout() { if (m_connected) { sendTestFR(); //m_timerT3->start(m_t3 * 1000); } } void IEC104MasterQt::delayedStartT3Timer() { if (m_connected && m_timerT3) { if (m_timerT3->isActive()) { m_timerT3->stop(); } m_timerT3->start(m_t3 * 1000); emit logMessage(QString("T3 timer started via delayed call, active: %1, interval: %2ms") .arg(m_timerT3->isActive()).arg(m_t3 * 1000)); } } void IEC104MasterQt::processReceivedData(const QByteArray &data) { int index = 0; while (index < data.size()) { if (static_cast(data[index]) == 0x68) { if (index + 1 < data.size()) { int length = static_cast(data[index + 1]); if (index + length + 2 <= data.size()) { QByteArray frame = data.mid(index, length + 2); const APCI *apci = reinterpret_cast(frame.constData()); int formatType = apci->field1 & 0x03; if (formatType == 0 || formatType == 2) { // I format //m_timerT3->stop(); // Reset test timer int revSendNum = (apci->field2 << 8) | apci->field1; m_rxCounter = revSendNum; m_lastAckTx = (apci->field4 << 8) | apci->field3; sendSMessage(); QByteArray asduData = frame.mid(sizeof(APCI)); decodeIMessage(asduData); } else if (formatType == 3) { // U format //m_timerT3->stop(); // Reset test timer processFormatU(*apci); m_lastAckTx = (apci->field4 << 8) | apci->field3; } index += length + 2; continue; } } } index++; } } void IEC104MasterQt::decodeIMessage(const QByteArray &data) { if (data.size() < 6) return; const ASDU_header *header = reinterpret_cast(data.constData()); int num = header->qual & 0x7F; bool seq = (header->qual & 0x80) != 0; int cause = header->tx_cause_1; int asdu = (header->commom_asdu_2 << 8) | header->commom_asdu_1; emit logMessage(QString("ASDU Address=%1, TypeID=%2, Count=%3, Cause=%4") .arg(asdu) .arg(header->type) .arg(num) .arg(cause)); int pos = sizeof(ASDU_header); switch (header->type) { case 1: { // Single-point information (遥信) if (seq) { if (pos + sizeof(SP104) <= data.size()) { const SP104 *sp = reinterpret_cast(data.constData() + pos); int address = (sp->addr2 << 16) | (sp->addr1 << 8) | sp->addr0; updateEquipmentStatus(address, sp->siq.spi, 0); emit logMessage(QString("SP %1: %2").arg(address).arg(sp->siq.spi)); pos += sizeof(SP104); // Process subsequent SIQ values in sequence while (pos < data.size()) { const SIQ104 *siq = reinterpret_cast(data.constData() + pos); address++; updateEquipmentStatus(address, siq->spi, 0); emit logMessage(QString("SP %1: %2").arg(address).arg(siq->spi)); pos++; } } } else { while (pos + sizeof(SP104) <= data.size()) { const SP104 *sp = reinterpret_cast(data.constData() + pos); int address = (sp->addr2 << 16) | (sp->addr1 << 8) | sp->addr0; updateEquipmentStatus(address, sp->siq.spi, 0); emit logMessage(QString("SP %1: %2").arg(address).arg(sp->siq.spi)); pos += sizeof(SP104); } } if (cause == 3) { sendSMessage(); } } break; case 3: { // Double-point information without time tag (双点信息,不带时标) if (seq) { // 序列方式(地址连续) if (pos + sizeof(DP104) <= data.size()) { const DP104* dp = reinterpret_cast(data.constData() + pos); int address = (dp->addr2 << 16) | (dp->addr1 << 8) | dp->addr0; // 解析双点状态 int status = 0; switch (dp->diq.dpi) { case 1: status = 1; break; // 分 case 2: status = 2; break; // 合 case 0: status = 0; break; // 中间状态 case 3: status = 4; break; // 不确定状态 } updateEquipmentStatus(address, status, 1); emit logMessage(QString("DP %1: %2 (DPI=%3)").arg(address).arg(status).arg(dp->diq.dpi)); pos += sizeof(DP104); // 处理后续的双点信息(序列方式) while (pos < data.size()) { const DIQ104* diq = reinterpret_cast(data.constData() + pos); address++; int subsequentStatus = 0; switch (diq->dpi) { case 1: subsequentStatus = 1; break; case 2: subsequentStatus = 2; break; case 0: subsequentStatus = 0; break; case 3: subsequentStatus = 4; break; } updateEquipmentStatus(address, subsequentStatus, 1); emit logMessage(QString("DP %1: %2 (DPI=%3)").arg(address).arg(subsequentStatus).arg(diq->dpi)); pos++; } } } else { // 非序列方式(独立地址) while (pos + sizeof(DP104) <= data.size()) { const DP104* dp = reinterpret_cast(data.constData() + pos); int address = (dp->addr2 << 16) | (dp->addr1 << 8) | dp->addr0; // 解析双点状态 int status = 0; switch (dp->diq.dpi) { case 1: status = 1; break; // 分 case 2: status = 2; break; // 合 case 0: status = 0; break; // 中间状态 case 3: status = 4; break; // 不确定状态 } updateEquipmentStatus(address, status, 1); emit logMessage(QString("DP %1: %2 (DPI=%3)").arg(address).arg(status).arg(dp->diq.dpi)); pos += sizeof(DP104); } } if (cause == 3) { sendSMessage(); // 发送确认帧 } break; } break; case 9: { // Measured value, normalized value (规一化测量值) while (pos + 5 <= data.size()) { // 地址3字节 + 值2字节 + QDS1字节 const unsigned char *ptr = reinterpret_cast(data.constData() + pos); int address = (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; // 规一化值 (2字节,有符号) short normalizedValue = (ptr[4] << 8) | ptr[3]; float value = normalizedValue; // 可根据需要转换为实际工程值 QDS104 qds; memcpy(&qds, ptr + 5, sizeof(QDS104)); updateMeasuredValue(address, value, !qds.iv, qds.ov, qds.bl, qds.sb); emit logMessage(QString("Normalized Value %1: %2, Valid: %3") .arg(address) .arg(value) .arg(!qds.iv)); pos += 6; // 3字节地址 + 2字节值 + 1字节QDS } } break; case 13: { // Measured value, short floating point value (短浮点数测量值) while (pos + sizeof(SFP104) <= data.size()) { const SFP104 *sfp = reinterpret_cast(data.constData() + pos); int address = (sfp->addr2 << 16) | (sfp->addr1 << 8) | sfp->addr0; float value = bytesToFloat(sfp->val); updateMeasuredValue(address, value, !sfp->qds.iv, sfp->qds.ov, sfp->qds.bl, sfp->qds.sb); emit logMessage(QString("Short Float %1: %2, Valid: %3, Overflow: %4") .arg(address) .arg(value) .arg(!sfp->qds.iv) .arg(sfp->qds.ov)); pos += sizeof(SFP104); } } break; case 30: { // Single-point information with time tag (带时标遥信) while (pos + sizeof(SP104_T) <= data.size()) { const SP104_T *spt = reinterpret_cast(data.constData() + pos); int address = (spt->addr2 << 16) | (spt->addr1 << 8) | spt->addr0; updateEquipmentStatus(address, spt->siq.spi, 0); emit logMessage(QString("SP with time %1: %2").arg(address).arg(spt->siq.spi)); pos += sizeof(SP104_T); } if (cause == 3) { sendSMessage(); } } break; case 31: { // Double-point information with time tag (带时标双点遥信) while (pos + sizeof(SP104_T) <= data.size()) { const SP104_T *spt = reinterpret_cast(data.constData() + pos); int address = (spt->addr2 << 16) | (spt->addr1 << 8) | spt->addr0; int nStatus = 0; if (spt->siq.spi && !spt->siq.rs1) { nStatus = 1; } // 1 0分 else if (!spt->siq.spi && spt->siq.rs1) { nStatus = 2; } // 0 1合 else if (spt->siq.spi && spt->siq.rs1) { nStatus = 4; } // 11不确定状态 else { nStatus = 0; } // updateEquipmentStatus(address, nStatus, 1); emit logMessage(QString("DP with time %1: %2 %3").arg(address).arg(spt->siq.spi).arg(spt->siq.rs1)); pos += sizeof(SP104_T); } if (cause == 3) { sendSMessage(); } } break; case 35: { // Measured value, scaled value with time tag CP56Time2a (带时标的标度化值) while (pos + sizeof(ScaledValueWithTime) <= data.size()) { const ScaledValueWithTime* svt = reinterpret_cast(data.constData() + pos); int address = (svt->addr2 << 16) | (svt->addr1 << 8) | svt->addr0; // 标度化值(2字节有符号整数) short scaledValue = (svt->value_high << 8) | svt->value_low; float value = static_cast(scaledValue); // 可根据实际标度转换为工程值 // 解析时标 QDateTime timestamp = cp56TimeToDateTime(svt->time); // 更新测量值(带时标) QMutexLocker locker(&m_statusMutex); MeasuredValue mv; mv.value = value; mv.valid = !svt->qds.iv; mv.overflow = svt->qds.ov; mv.blocked = svt->qds.bl; mv.substituted = svt->qds.sb; mv.timestamp = timestamp; // 使用报文中的时标 m_measuredValues[address] = mv; emit measuredValueUpdated(address, value, !svt->qds.iv); emit logMessage(QString("Scaled Value with time %1: %2, Valid: %3, Time: %4") .arg(address) .arg(value) .arg(!svt->qds.iv) .arg(timestamp.toString("yyyy-MM-dd hh:mm:ss.zzz"))); pos += sizeof(ScaledValueWithTime); } break; } case 36: { // Measured value, short float point value w/CP56Time2a (带时标短浮点数) while (pos + sizeof(SFP104_T) <= data.size()) { const SFP104_T *sfpt = reinterpret_cast(data.constData() + pos); int address = (sfpt->addr2 << 16) | (sfpt->addr1 << 8) | sfpt->addr0; float value = bytesToFloat(sfpt->val); QDateTime timestamp = cp56TimeToDateTime(sfpt->time); updateMeasuredValue(address, value, !sfpt->qds.iv, sfpt->qds.ov, sfpt->qds.bl, sfpt->qds.sb); emit logMessage(QString("Short Float with time %1: %2, Valid: %3, Time: %4") .arg(address) .arg(value) .arg(!sfpt->qds.iv) .arg(timestamp.toString("yyyy-MM-dd hh:mm:ss.zzz"))); pos += sizeof(SFP104_T); } } break; // 新增:遥控命令确认 case 45: // Single command (单命令) case 46: // Double command (双命令) case 59: processControlCommand(header, data.mid(pos), cause); break; // 新增:设值命令确认 case 50: // Setpoint command, normalized value (设点命令规范化值) case 51: // Setpoint command, normalized value with time tag //case 100: // Setpoint command, short floating point value (设点命令短浮点数) processSetpointCommand(header, data.mid(pos), cause); break; case 140: // Setpoint command, normalized value with time tag { if (sizeof(ER140_Head) <= data.size()) { const ER140_Head *spt = reinterpret_cast(data.constData()); // 信息对象地址 int msgadder = (spt->msgadder3 << 16) | (spt->msgadder2 << 8) | spt->msgadder1; // 装置地址 -- 四遥表中装置地址 int eqpmtadder = (spt->eqpmtadder2 << 8) | spt->eqpmtadder1; // 信息序号 int msgid = (spt->addr2 << 8) | spt->addr1; // 双点信息 合2 分1 int dpvalue = spt->dpvalue; // 相对时间 int dsTime = (spt->dsTime2 << 8) | spt->dsTime1; // 故障序号 int erid = (spt->erid2 << 8) | spt->erid1; // 时标 QDateTime timestamp = cp56TimeToDateTime(spt->time); // 故障电量个数 int erNum = spt->num; QString strTemp = QString("ER with timer Type: %1; msgadder: %2; eqpmtadder: %3; msgid: %4; dpvalue: %5; erid: %6; erNum: 57") .arg(spt->head.type) .arg(msgadder) .arg(eqpmtadder) .arg(msgid) .arg(dpvalue) .arg(erid) .arg(erNum); emit logMessage(strTemp); pos = sizeof(ER140_Head); int nIndex = 0; while (pos + sizeof(ER140_Data) + sizeof(unsigned char) <= data.size()) { const ER140_Data *spt1 = reinterpret_cast(data.constData() + pos); QString strReset = "ER_Data" + QString::number(nIndex) + "/" + QString::number(spt->num) + " subType=" + QString::number(spt1->ERType); QString strData = ""; pos += sizeof(ER142_Data); int nErIndex = (spt1->Num2 << 8) | spt1->Num1; strData = " nErIndex = " + QString::number(nErIndex); strReset += strData; switch (spt1->ERType) { case 1: case 2: { unsigned char pCharData = 0; memcpy(&pCharData, data.constData() + pos, sizeof(unsigned char)); strData = QString::number(pCharData); pos += sizeof(unsigned char); } break; case 3: case 4: { unsigned char Data1[2] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 2); int value = (Data1[1] << 8) | Data1[0]; strData = QString::number(value); pos += sizeof(unsigned char) * 2; } break; case 5: case 6: case 7: { unsigned char Data1[4] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 4); if (spt1->ERType == 7) { float value = bytesToFloat(Data1); strData = QString::number(value); } else { int value = (Data1[3] << 24) | (Data1[2] << 16) | (Data1[1] << 8) | Data1[0]; strData = QString::number(value); } pos += sizeof(unsigned char) * 4; } break; case 8: { char Data1[8] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 8); strData = QString::fromLocal8Bit(Data1, 8); pos += sizeof(unsigned char) * 8; } break; } // pos += sizeof(unsigned char)*8; strReset.append(" Data=" + strData); emit logMessage(strReset); nIndex++; } } } break; case 142: // Setpoint command, normalized value with time tag { if (sizeof(ER142_Head) <= data.size()) { const ER142_Head *spt = reinterpret_cast(data.constData()); int eqpmtadder = (spt->eqpmtadder2 << 8) | spt->eqpmtadder1; int ERID = (spt->addr2 << 8) | spt->addr1; int eventnum = spt->evetnum; QString strTemp = QString("ER with timer Type: %1; eqpmtadder: %2; msgid: %3; eventnum: %4").arg(spt->head.type).arg(eqpmtadder).arg(ERID).arg(eventnum); emit logMessage(strTemp); pos = sizeof(ER142_Head); int nIndex = 0; while (pos + sizeof(ER142_Data) + sizeof(unsigned char) <= data.size()) { const ER142_Data *spt1 = reinterpret_cast(data.constData() + pos); QString strReset = "ER_Data" + QString::number(nIndex) + "/" + QString::number(spt->ernum) + " subType=" + QString::number(spt1->ERType); QString strData = ""; pos += sizeof(ER142_Data); int nErIndex = (spt1->Num2 << 8) | spt1->Num1; strData = " nErIndex = " + QString::number(nErIndex); strReset += strData; switch (spt1->ERType) { case 1: case 2: { unsigned char pCharData = 0; memcpy(&pCharData, data.constData() + pos, sizeof(unsigned char)); strData = QString::number(pCharData); pos += sizeof(unsigned char); } break; case 3: case 4: { unsigned char Data1[2] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 2); int value = (Data1[1] << 8) | Data1[0]; strData = QString::number(value); pos += sizeof(unsigned char) * 2; } break; case 5: case 6: case 7: { unsigned char Data1[4] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 4); if (spt1->ERType == 7) { float value = bytesToFloat(Data1); strData = QString::number(value); } else { int value = (Data1[3] << 24) | (Data1[2] << 16) | (Data1[1] << 8) | Data1[0]; strData = QString::number(value); } pos += sizeof(unsigned char) * 4; } break; case 8: { char Data1[8] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 8); strData = QString::fromLocal8Bit(Data1, 8); pos += sizeof(unsigned char) * 8; } break; } // pos += sizeof(unsigned char)*8; strReset.append(" Data=" + strData); emit logMessage(strReset); nIndex++; } } } break; case 143: // Setpoint command, normalized value with time tag { if (sizeof(ER143_Head) <= data.size()) { const ER143_Head *spt = reinterpret_cast(data.constData()); // 信息对象地址 int msgadder = (spt->msgadder3 << 16) | (spt->msgadder2 << 8) | spt->msgadder1; // 装置地址 int eqpmtadder = (spt->eqpmtadder2 << 8) | spt->eqpmtadder1; // 故障序号 int ERID = (spt->addr2 << 8) | spt->addr1; int NGD = spt->ngd; // 大循环的范围 // 时间 QString strTimeData = ""; pos = sizeof(ER143_Head); for (int nIndex = 0; nIndex < NGD; nIndex++) { int nEventNum = 0; if (msgadder == 0) { const ER143_SData *SData = reinterpret_cast(data.constData() + pos); pos += sizeof(ER143_LData); int nTimeData = (SData->nTime[1] << 8) | SData->nTime[0]; nEventNum = SData->nNumber; strTimeData = "DTime=" + QString::number(nTimeData) + "ms"; } else { const ER143_LData *SData = reinterpret_cast(data.constData() + pos); pos += sizeof(ER143_LData); int nTimeData = (SData->nTime[3] << 24) | (SData->nTime[2] << 16) | (SData->nTime[1] << 8) | SData->nTime[0]; nEventNum = SData->nNumber; strTimeData = "DTime=" + QString::number(nTimeData) + "ms"; } emit logMessage("Event" + QString::number(nIndex) + " " + strTimeData); for (int subIndex = 0; subIndex < nEventNum; subIndex++) { const ER143_Data *spt1 = reinterpret_cast(data.constData() + pos); QString strReset = "ER_Data" + QString::number(subIndex) + "/" + nEventNum + " subType=" + QString::number(spt1->ERType); QString strData = ""; pos += sizeof(ER143_Data); int nErIndex = (spt1->Num2 << 8) | spt1->Num1; strData = " nErIndex = " + QString::number(nErIndex); strReset += strData; switch (spt1->ERType) { case 1: case 2: { unsigned char pCharData = 0; memcpy(&pCharData, data.constData() + pos, sizeof(unsigned char)); strData = QString::number(pCharData); pos += sizeof(unsigned char); } break; case 3: case 4: { unsigned char Data1[2] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 2); int value = (Data1[1] << 8) | Data1[0]; strData = QString::number(value); pos += sizeof(unsigned char) * 2; } break; case 5: case 6: case 7: { unsigned char Data1[4] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 4); if (spt1->ERType == 7) { float value = bytesToFloat(Data1); strData = QString::number(value); } else { int value = (Data1[3] << 24) | (Data1[2] << 16) | (Data1[1] << 8) | Data1[0]; strData = QString::number(value); } pos += sizeof(unsigned char) * 4; } break; case 8: { char Data1[8] = {0}; memcpy(Data1, data.constData() + pos, sizeof(unsigned char) * 8); strData = QString::fromLocal8Bit(Data1, 8); pos += sizeof(unsigned char) * 8; } break; } strReset.append(" Data=" + strData); emit logMessage(strReset); } } QString strTemp = QString("ER with timer Type: %1; msgadder: %2;eqpmtadder: %3; ERID: %4; NGD: %5").arg(spt->head.type).arg(msgadder).arg(eqpmtadder).arg(ERID).arg(NGD); emit logMessage(strTemp); } } break; case 100: { // Interrogation command (总召) if (cause == 7) { // 激活确认 emit logMessage("Interrogation activation confirmation received"); // 可以在这里触发数据处理或状态更新 } else if (cause == 10) { // 激活终止 sendSMessage(); emit logMessage("Interrogation activation termination received"); // 总召唤完成,可以在这里发送完成信号 //emit interrogationCompleted(true); } else if (cause == 47) { // 命令出错 emit logMessage("Interrogation command error"); //emit interrogationCompleted(false); } break; } case 145://定值返回数据处理 { FixValueCommand(header, data.mid(pos), cause); } break; case F_FR_NA_1: // 文件准备就绪 processFileReady(header, data.mid(pos), cause); break; case F_SR_NA_1: // 节准备就绪 processSectionReady(header, data.mid(pos), cause); break; case F_SC_NA_1: // 文件召唤/选择 processFileCall(header, data.mid(pos), cause); break; case F_LS_NA_1: // 最后的节/段 processLastSection(header, data.mid(pos), cause); break; case F_AF_NA_1: // 认可文件/段 processFileAck(header, data.mid(pos), cause); break; case F_SG_NA_1: // 段 processFileSegment(header, data.mid(pos), cause); break; case F_DR_NA_1: // 目录 processDirectory(header, data.mid(pos), cause); break; default: emit logMessage(QString("Unhandled ASDU type: %1").arg(header->type)); break; } } // 字节序列转换为浮点数 float IEC104MasterQt::bytesToFloat(const unsigned char *bytes) { // IEC 104使用小端字节序 uint32_t temp = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; return *reinterpret_cast(&temp); } // CP56Time2a时间格式转换为QDateTime QDateTime IEC104MasterQt::cp56TimeToDateTime(const unsigned char *timeData) { // CP56Time2a格式: // 字节1-2: 毫秒(0-59999) // 字节3: 分钟(0-59) // 字节4: 小时(0-23) // 字节5: 日期(bit0-4:日, bit5-7:星期) // 字节6: 月(0-12) // 字节7: 年(0-99, 相对于2000年) int milliseconds = (timeData[1] << 8) | timeData[0]; int seconds = milliseconds / 1000; milliseconds = milliseconds % 1000; int minutes = timeData[2] & 0x3F; // 去除RES1和RES2位 int hours = timeData[3] & 0x1F; // 去除RES位 int day = timeData[4] & 0x1F; int month = timeData[5] & 0x0F; int year = timeData[6] & 0x7F + 2000; QDate date(year, month, day); QTime time(hours, minutes, seconds, milliseconds); return QDateTime(date, time, Qt::UTC); // IEC 104使用UTC时间 } QByteArray IEC104MasterQt::dateTimeToCp56Time2a(const QDateTime &dateTime) { QByteArray cp56Time(7, 0); // 转换为本地时区(104规约通常使用本地时间) QDateTime localDateTime = dateTime.toLocalTime(); // 获取时间组件 QDate date = localDateTime.date(); QTime time = localDateTime.time(); // 字节1-2:毫秒(低字节在前) int milliseconds = time.msec() + time.second() * 1000; cp56Time[0] = static_cast(milliseconds & 0xFF); // 低字节 cp56Time[1] = static_cast((milliseconds >> 8) & 0xFF); // 高字节 // 字节3:分钟 cp56Time[2] = static_cast(time.minute() & 0x3F); // 只取低6位 // 字节4:小时 cp56Time[3] = static_cast(time.hour() & 0x1F); // 只取低5位 // 字节5:月的日期(1-31) cp56Time[4] = static_cast(date.day() & 0x1F); // 只取低5位 // 字节6:星期(位0-2) + 月的索引(位4-7) int dayOfWeek = date.dayOfWeek(); // 1=周一, 7=周日 if (dayOfWeek == 7) dayOfWeek = 0; // 104规约中0表示周日 else dayOfWeek--; // 调整为0-6 int month = date.month() - 1; // 月份索引(0-11) cp56Time[5] = static_cast((month << 4) | (dayOfWeek & 0x07)); // 字节7:年(0-99,相对于2000年) int year = date.year() - 2000; cp56Time[6] = static_cast(year & 0x7F); return cp56Time; } void IEC104MasterQt::updateEquipmentStatus(int address, int status, int nSingle) { QMutexLocker locker(&m_statusMutex); m_equipmentStatus[address] = status; emit equipmentStatusUpdated(address, status, nSingle); } void IEC104MasterQt::updateMeasuredValue(int address, float value, bool valid, bool overflow, bool blocked, bool substituted) { QMutexLocker locker(&m_statusMutex); MeasuredValue mv; mv.value = value; mv.valid = valid; mv.overflow = overflow; mv.blocked = blocked; mv.substituted = substituted; mv.timestamp = QDateTime::currentDateTime(); m_measuredValues[address] = mv; emit measuredValueUpdated(address, value, valid); } QMap IEC104MasterQt::getMeasuredValues() const { QMutexLocker locker(&m_statusMutex); return m_measuredValues; } QMap > > IEC104MasterQt::getFixDataValues() const { QMutexLocker locker(&m_statusMutex); return m_fixValues; } QByteArray IEC104MasterQt::toHexString(const QByteArray &data) { QByteArray result; for (int i = 0; i < data.size(); ++i) { if (i > 0) result.append(' '); result.append(QByteArray::number(static_cast(data[i]), 16).right(2).rightJustified(2, '0')); } return result; } void IEC104MasterQt::processFormatU(const APCI &apci) { int command = apci.field1; switch (command) { case 0x07: // STARTDT CON emit logMessage("Received STARTDT confirmation"); break; case 0x0B: // STOPDT CON emit logMessage("Received STOPDT confirmation"); break; case 0x13: // STOPDT ACT emit logMessage("Received STOPDT ACT"); // 应该回复 STOPDT CON sendUMessage(0x23); break; case 0x23: // STOPDT CON emit logMessage("Received STOPDT confirmation"); break; case 0x43: // TESTFR CON emit logMessage("Received TESTFR confirmation"); // 应该回复 TESTFR CON sendUMessage(0x83); break; case 0x83: // TESTFR CON emit logMessage("Received TESTFR confirmation"); break; default: emit logMessage(QString("Unknown U format command: 0x%1").arg(command, 2, 16, QLatin1Char('0'))); break; } } QString IEC104MasterQt::getDataValue(int &nPos, const QByteArray &data, int nType) { QString strData=""; switch (nType) { case 1: case 2: { unsigned char pCharData = 0; memcpy(&pCharData, data.constData() + nPos, sizeof(unsigned char)); strData = QString::number(pCharData); nPos += sizeof(unsigned char); } break; case 3: case 4: { unsigned char Data1[2] = {0}; memcpy(Data1, data.constData() + nPos, sizeof(unsigned char) * 2); int value = (Data1[1] << 8) | Data1[0]; strData = QString::number(value); nPos += sizeof(unsigned char) * 2; } break; case 5: case 6: case 7: { unsigned char Data1[4] = {0}; memcpy(Data1, data.constData() + nPos, sizeof(unsigned char) * 4); if (nType == 7) { float value = bytesToFloat(Data1); strData = QString::number(value); } else { int value = (Data1[3] << 24) | (Data1[2] << 16) | (Data1[1] << 8) | Data1[0]; strData = QString::number(value); } nPos += sizeof(unsigned char) * 4; } break; case 8: { char Data1[8] = {0}; memcpy(Data1, data.constData() + nPos, sizeof(unsigned char) * 8); strData = QString::fromLocal8Bit(Data1, 8); nPos += sizeof(unsigned char) * 8; } break; } return strData; } QString IEC104MasterQt::Data2Value(int &nPos, const QByteArray &data, int nType) { if (nPos < 0 || nPos >= data.size()) { return QString(); } switch (nType) { case TYPE_UI8: { if (nPos + 1 > data.size()) return QString(); quint8 value = static_cast(data.at(nPos)); nPos += 1; return QString::number(value); } case TYPE_I8: { if (nPos + 1 > data.size()) return QString(); qint8 value = static_cast(data.at(nPos)); nPos += 1; return QString::number(value); } case TYPE_UI16: { if (nPos + 2 > data.size()) return QString(); quint16 value = *reinterpret_cast(data.constData() + nPos); value = qFromLittleEndian(value); nPos += 2; return QString::number(value); } case TYPE_I16: { if (nPos + 2 > data.size()) return QString(); qint16 value = *reinterpret_cast(data.constData() + nPos); value = qFromLittleEndian(value); nPos += 2; return QString::number(value); } case TYPE_UI32: { if (nPos + 4 > data.size()) return QString(); quint32 value = *reinterpret_cast(data.constData() + nPos); value = qFromLittleEndian(value); nPos += 4; return QString::number(value); } case TYPE_I32: { if (nPos + 4 > data.size()) return QString(); qint32 value = *reinterpret_cast(data.constData() + nPos); value = qFromLittleEndian(value); nPos += 4; return QString::number(value); } case TYPE_F32: { if (nPos + 4 > data.size()) return QString(); // 使用手动字节交换方法处理浮点数 unsigned char Data1[4] = {0}; memcpy(Data1, data.constData() + nPos, sizeof(unsigned char) * 4); float value = bytesToFloat(Data1); nPos += 4; return QString::number(value); } case TYPE_S64: { if (nPos + 8 > data.size()) return QString(); char Data1[8] = {0}; memcpy(Data1, data.constData() + nPos, sizeof(unsigned char) * 8); nPos += sizeof(unsigned char) * 8; // 注意:浮点数的字节序处理需要特别小心 // 这里假设系统字节序与数据字节序一致,实际使用时可能需要手动转换 nPos += 8; return QString::fromLocal8Bit(Data1, 8); } default: return QString(); } } bool IEC104MasterQt::Value2Data(QByteArray &data, int nType, const QString &strData) { bool ok = false; switch (nType) { case TYPE_UI8: { quint8 value = static_cast(strData.toUShort(&ok)); if (ok) { data.append(static_cast(value)); } break; } case TYPE_I8: { qint8 value = static_cast(strData.toShort(&ok)); if (ok) { data.append(static_cast(value)); } break; } case TYPE_UI16: { quint16 value = static_cast(strData.toUShort(&ok)); if (ok) { quint16 leValue = qToLittleEndian(value); data.append(reinterpret_cast(&leValue), 2); } break; } case TYPE_I16: { qint16 value = static_cast(strData.toShort(&ok)); if (ok) { qint16 leValue = qToLittleEndian(value); data.append(reinterpret_cast(&leValue), 2); } break; } case TYPE_UI32: { quint32 value = strData.toUInt(&ok); if (ok) { quint32 leValue = qToLittleEndian(value); data.append(reinterpret_cast(&leValue), 4); } break; } case TYPE_I32: { qint32 value = strData.toInt(&ok); if (ok) { qint32 leValue = qToLittleEndian(value); data.append(reinterpret_cast(&leValue), 4); } break; } case TYPE_F32: { float value = strData.toFloat(&ok); if (ok) { // 将浮点数转换为小端序字节数组 union { float f; quint8 b[4]; } converter; converter.f = value; // 小端序:先存低位字节 data.append(static_cast(converter.b[0])); data.append(static_cast(converter.b[1])); data.append(static_cast(converter.b[2])); data.append(static_cast(converter.b[3])); } break; } case TYPE_S64: { char value[8] =""; if (strData.size()!=8) { memcpy(value,strData.toLocal8Bit(),sizeof(value)); data.append(reinterpret_cast(&value), 8); } } default: ok = false; break; } return ok; } bool IEC104MasterQt::sendSingleCommand(int commonAddress, int objectAddress, bool command, bool selectExecute) { if (!m_connected) { emit logMessage("Not connected, cannot send single command"); return false; } QByteArray data; // ASDU头 data.append(static_cast(0x2D)); // 类型: 45-单命令, 46-双命令 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(0x06)); // 传送原因: 6-激活, 8-停止激活 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); // 命令值 unsigned char commandValue = command ? 0x01 : 0x00; if (selectExecute) { // QOC (命令质量) data.append(static_cast(commandValue+0x80)); // 通常命令 } else { data.append(static_cast(commandValue)); } // if (selectExecute) // { // // QOC (命令质量) // data.append(static_cast(0x80)); // 通常命令 // } // else { data.append(static_cast(0x00)); } emit logMessage(QString("Sending single command: Address=%1, Command=%2, SelectExecute=%3") .arg(objectAddress) .arg(command ? "ON" : "OFF") .arg(selectExecute)); // 保存待确认的命令 if (selectExecute) { QMutexLocker locker(&m_commandMutex); ControlCommand cmd; cmd.address = objectAddress; cmd.type = ControlCommandType::SinglePoint; cmd.value = command; cmd.commonAddress = commonAddress; cmd.timestamp = QDateTime::currentDateTime(); m_pendingCommands[objectAddress] = cmd; } return sendIMessage(data); } // 新增:发送双点命令 bool IEC104MasterQt::sendDoubleCommand(int commonAddress, int objectAddress, int command, bool selectExecute) { if (!m_connected) { emit logMessage("Not connected, cannot send double command"); return false; } QByteArray data; // ASDU头 data.append(static_cast(selectExecute ? 0x2E : 0x2F)); // 类型: 46-双命令, 47-双命令停止激活 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(selectExecute ? 0x06 : 0x08)); // 传送原因: 6-激活, 8-停止激活 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); // 双点命令值 (1-分, 2-合) unsigned char commandValue = static_cast(command & 0x03); data.append(static_cast(commandValue)); // QOC (命令质量) data.append(static_cast(0x00)); // 通常命令 emit logMessage(QString("Sending double command: Address=%1, Command=%2, SelectExecute=%3") .arg(objectAddress) .arg(command) .arg(selectExecute)); // 保存待确认的命令 if (selectExecute) { QMutexLocker locker(&m_commandMutex); ControlCommand cmd; cmd.address = objectAddress; cmd.type = ControlCommandType::DoublePoint; cmd.value = (command == 2); // 转换为bool: 1=false, 2=true cmd.commonAddress = commonAddress; cmd.timestamp = QDateTime::currentDateTime(); m_pendingCommands[objectAddress] = cmd; } return sendIMessage(data); } // 新增:发送双点命令 bool IEC104MasterQt::sendDoubleCommand2(int commonAddress, int objectAddress, int command, bool selectExecute,bool isRun) { if (!m_connected) { emit logMessage("Not connected, cannot send double command"); return false; } QByteArray data; // ASDU头 data.append(static_cast(0x3B)); // data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 if (selectExecute) { data.append(static_cast(isRun ? 0x06 : 0x08)); // 传送原因: 6-激活,7-确认 8-停止激活 } else { data.append(static_cast(isRun ? 0x06 : 0x08)); // 传送原因: 6-激活,7-确认 8-停止激活 } data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); // 双点命令值 (1-分, 2-合) if (selectExecute) { unsigned char commandValue = static_cast(command & 0x03); data.append(static_cast(commandValue)+0x80); }else { unsigned char commandValue = static_cast(command & 0x03); data.append(static_cast(commandValue)); } // QOC (命令质量) // data.append(static_cast(0x00)); // 通常命令 // 时标 QDateTime timestamp = QDateTime::currentDateTime(); QByteArray date = dateTimeToCp56Time2a(timestamp); data.append(date); emit logMessage(QString("Sending double command: Address=%1, Command=%2, SelectExecute=%3") .arg(objectAddress) .arg(command) .arg(selectExecute)); // 保存待确认的命令 if (selectExecute) { QMutexLocker locker(&m_commandMutex); ControlCommand cmd; cmd.address = objectAddress; cmd.type = ControlCommandType::DoublePoint; cmd.value = (command == 2); // 转换为bool: 1=false, 2=true cmd.commonAddress = commonAddress; cmd.timestamp = QDateTime::currentDateTime(); m_pendingCommands[objectAddress] = cmd; } return sendIMessage(data); } bool IEC104MasterQt::sendDoubleCommand3(int commonAddress, int nEqpmtID, int objectAddress, int command, bool selectExecute /*= true*/, bool isRun /*= true*/) { if (!m_connected) { emit logMessage("Not connected, cannot send double command"); return false; } QByteArray data; // ASDU头 data.append(static_cast(0x3B)); // 类型: 46-双命令, 47-双命令停止激活 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(isRun ? 0x06 : 0x08)); // 传送原因: 6-激活, 8-停止激活 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast(( nEqpmtID ) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); // 双点命令值 (1-分, 2-合) if (selectExecute) { unsigned char commandValue = static_cast(command & 0x03); //data.append(static_cast(commandValue) + 0x80); data.append(static_cast(commandValue)); } else { unsigned char commandValue = static_cast(command & 0x03); data.append(static_cast(commandValue)); } // QOC (命令质量) // data.append(static_cast(0x00)); // 通常命令 // 时标 QDateTime timestamp = QDateTime::currentDateTime(); QByteArray date = dateTimeToCp56Time2a(timestamp); data.append(date); emit logMessage(QString("Sending double command: Address=%1, Command=%2, SelectExecute=%3") .arg(objectAddress) .arg(command) .arg(selectExecute)); // 保存待确认的命令 if (selectExecute) { QMutexLocker locker(&m_commandMutex); ControlCommand cmd; cmd.address = objectAddress; cmd.type = ControlCommandType::DoublePoint; cmd.value = (command == 2); // 转换为bool: 1=false, 2=true cmd.commonAddress = commonAddress; cmd.EqpmtAddress = nEqpmtID; cmd.timestamp = QDateTime::currentDateTime(); m_pendingCommands[objectAddress] = cmd; } return sendIMessage(data); } bool IEC104MasterQt::getFixAreaValue(int commonAddress,int objectAddress,int nEqpmtID) { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } QByteArray data; // ASDU头 data.append(static_cast(0x91)); // 类型: 145 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(0x05)); // 传送原因: 3突发 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); //装置地址 data.append(static_cast(nEqpmtID & 0xFF)); // 公共地址低字节 data.append(static_cast((nEqpmtID >> 8) & 0xFF)); // 公共地址高字节 data.append(static_cast(0xF1)); // 信息序号(INF) data.append(static_cast(0x00));//返回信息标识 data.append(static_cast(0x01));//通用分类数据集数目 data.append(static_cast(0x00));//通用标识序号 data.append(static_cast(0x01));//通用标识序号 data.append(static_cast(0x00));//描述类别 emit logMessage(QString("Sending normalized setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(commonAddress) .arg(objectAddress) .arg(nEqpmtID)); return sendIMessage(data); } bool IEC104MasterQt::getFixValue(int commonAddress,int objectAddress,int nEqpmtID) { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } m_FixData.clear(); //清除前一次记录 QByteArray data; // ASDU头 data.append(static_cast(0x91)); // 类型: 145 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(0x05)); // 传送原因: 3突发 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); //装置地址 data.append(static_cast(nEqpmtID & 0xFF)); // 公共地址低字节 data.append(static_cast((nEqpmtID >> 8) & 0xFF)); // 公共地址高字节 //data.append(static_cast(0x00)); data.append(static_cast(0xF1)); // 信息序号(INF) data.append(static_cast(0x00));//返回信息标识 data.append(static_cast(0x01));//通用分类数据集数目 NGD data.append(static_cast(0x01));//通用标识序号 GIN组 data.append(static_cast(0x00));//通用标识序号 GIN条目 data.append(static_cast(0x00));//通用标识序号 KOD data.append(static_cast(0x03));//数据类型 3 data.append(static_cast(0x01));//数据大小 1 data.append(static_cast(0x01));//数据序号 1 data.append(static_cast(0x00));//描述类别 GID emit logMessage(QString("Sending normalized setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(commonAddress) .arg(objectAddress) .arg(nEqpmtID)); return sendIMessage(data); } bool IEC104MasterQt::sendFixAreaValueCommand(FixAreaCommandType nType, int commonAddress,int objectAddress,int nEqpmtID,int nFixArea) { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } QByteArray data; // ASDU头 data.append(static_cast(0x91)); // 类型: 145 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(0x05)); // 传送原因: 3突发 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); //装置地址 data.append(static_cast(nEqpmtID & 0xFF)); // 公共地址低字节 data.append(static_cast((nEqpmtID >> 8) & 0xFF)); // 公共地址高字节 if(nType == FixAreaCommandType::FixAreaCommandType_Preset) data.append(static_cast(0xF9)); // 信息序号(INF) else if(nType == FixAreaCommandType::FixAreaCommandType_Execut) data.append(static_cast(0xFA)); // 信息序号(INF) else if(nType == FixAreaCommandType::FixAreaCommandType_Revoke) data.append(static_cast(0xFB)); // 信息序号(INF) else data.append(static_cast(0xF9)); // 信息序号(INF) data.append(static_cast(0x00));//返回信息标识 data.append(static_cast(0x01));//通用分类数据集数目 NGD data.append(static_cast(0x00));//通用标识序号 GIN组 data.append(static_cast(0x01));//通用标识序号 GIN条目 data.append(static_cast(0x00));//通用标识序号 KOD data.append(static_cast(0x03));//数据类型 3 data.append(static_cast(0x01));//数据大小 1 data.append(static_cast(0x01));//数据序号 1 data.append(static_cast((nFixArea + 1) & 0xFF));//描述类别 GID 区号+1 emit logMessage(QString("Sending normalized setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(commonAddress) .arg(objectAddress) .arg(nEqpmtID)); return sendIMessage(data); } bool IEC104MasterQt::sendPresetFixValueCommand(int commonAddress,int objectAddress,int nEqpmtID, int nFixArea,int nIndex,int nType, QString &msg) { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } QByteArray heasData; // ASDU头 heasData.append(static_cast(0x91)); // 类型: 145 heasData.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 heasData.append(static_cast(0x05)); // 传送原因: 3突发 heasData.append(static_cast(0x00)); heasData.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 heasData.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 heasData.append(static_cast(objectAddress & 0xFF)); heasData.append(static_cast((objectAddress >> 8) & 0xFF)); heasData.append(static_cast((objectAddress >> 16) & 0xFF)); //装置地址 heasData.append(static_cast(nEqpmtID & 0xFF)); // 公共地址低字节 heasData.append(static_cast((nEqpmtID >> 8) & 0xFF)); // 公共地址高字节 heasData.append(static_cast(0xF9)); // 信息序号(INF) heasData.append(static_cast(0x00));//返回信息标识 // 先改值 QByteArray ginData; bool ret = Value2Data(ginData,nType,msg); int ginDatalenth = 4; //需要根据类型计算 #if 1 //data.append(static_cast(0x9D));//通用分类数据集数目 NGD int nPos = 0; while (nPos < m_FixData.size()) { // 取出数据类型定义 const P_145_Area *ptrArea = reinterpret_cast(m_FixData.constData() + nPos); // 定值序号 nPos+=sizeof (P_145_Area); if(ptrArea->GINnum == nIndex) { m_FixData.replace(nPos, ginDatalenth, ginData); // 替换前4个字节 break; } // 添加偏移 QString strData=Data2Value(nPos,m_FixData,ptrArea->FixValueType); } //截取发送 nPos = 0; int startPos = 0; int length = 0; int cunot = 0; int index = 0; bool isLast; QByteArray fixData; while (nPos < m_FixData.size()) { index++; // 取出数据类型定义 const P_145_Area *ptrArea = reinterpret_cast(m_FixData.constData() + nPos); startPos = nPos; // 计算偏移 nPos+=sizeof (P_145_Area); QString strData=Data2Value(nPos,m_FixData,ptrArea->FixValueType); length = nPos - startPos; //单个定值包长度 // 拷贝 if (startPos + length <= m_FixData.size()) { for (int i = 0; i < length; ++i) { fixData.append(m_FixData[startPos + i]); } isLast = true; } //满20个打包发送 if ( index % 20 == 0) { QByteArray sendData; sendData.append(heasData); if(cunot == 0) { sendData.append(static_cast(0x94)); cunot = 1; } else { sendData.append(static_cast(0xd4)); cunot = 0; } sendData.append(fixData); sendIMessage(sendData); fixData.clear(); isLast = false; } } if (isLast) { QByteArray sendData; sendData.append(heasData); sendData.append(static_cast(0x01)); sendData.append(fixData); sendIMessage(sendData); fixData.clear(); isLast = false; } #else data.append(static_cast(0x01));//通用分类数据集数目 NGD data.append(static_cast((nFixArea + 1) & 0xFF));//通用标识序号 GIN组 区号+1 data.append(static_cast(nIndex));//通用标识序号 GIN条目 data.append(static_cast(0x00));//通用标识序号 KOD data.append(static_cast(0x03));//数据类型 3 data.append(static_cast(0x01));//数据大小 1 data.append(static_cast(0x01));//数据序号 1 data.append(ginData);//描述类别 GID 定值 #endif emit logMessage(QString("Sending normalized setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(commonAddress) .arg(objectAddress) .arg(nEqpmtID)); //return sendIMessage(data); return true; } bool IEC104MasterQt::sendSetFixValueCommand(FixAreaCommandType nType, int commonAddress,int objectAddress,int nEqpmtID,int nFixArea) { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } QByteArray data; // ASDU头 data.append(static_cast(0x91)); // 类型: 145 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(0x05)); // 传送原因: 3突发 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); //装置地址 data.append(static_cast(nEqpmtID & 0xFF)); // 公共地址低字节 data.append(static_cast((nEqpmtID >> 8) & 0xFF)); // 公共地址高字节 if(nType == FixAreaCommandType::FixAreaCommandType_Execut) data.append(static_cast(0xFA)); // 信息序号(INF) else if(nType == FixAreaCommandType::FixAreaCommandType_Revoke) data.append(static_cast(0xFB)); // 信息序号(INF) else data.append(static_cast(0x00)); // 信息序号(INF) data.append(static_cast(0x00));//返回信息标识 data.append(static_cast(0x01));//通用分类数据集数目 NGD data.append(static_cast((nFixArea + 1) & 0xFF));//通用标识序号 GIN组 区号+1 data.append(static_cast(0x00));//通用标识序号 GIN条目 data.append(static_cast(0x00));//通用标识序号 KOD data.append(static_cast(0x03));//数据类型 3 data.append(static_cast(0x01));//数据大小 1 data.append(static_cast(0x01));//数据序号 1 data.append(static_cast((nFixArea + 1) & 0xFF));//描述类别 GID 区号+1 emit logMessage(QString("Sending normalized setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(commonAddress) .arg(objectAddress) .arg(nEqpmtID)); return sendIMessage(data); } //时钟同步 bool IEC104MasterQt::sendTimeSynchronizationCommand() { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } int commonAddress = 1; int objectAddress = 0; QByteArray data; // ASDU头 data.append(static_cast(0x67)); // 类型: 103 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(0x06)); // 传送原因: 6激活 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); //CP56Time2a // 时标 QDateTime timestamp = QDateTime::currentDateTime(); QByteArray date = dateTimeToCp56Time2a(timestamp); data.append(date); return sendIMessage(data); } // 新增:发送浮点设值命令 bool IEC104MasterQt::sendFloatSetpoint(int commonAddress, int objectAddress, float value, bool selectExecute) { if (!m_connected) { emit logMessage("Not connected, cannot send float setpoint"); return false; } QByteArray data; // ASDU头 data.append(static_cast(selectExecute ? 0x32 : 0x33)); // 类型: 50-设点命令规范化值, 51-设点命令规范化值停止激活 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(selectExecute ? 0x06 : 0x08)); // 传送原因: 6-激活, 8-停止激活 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); // 规一化值 short normalizedValue = floatToNormalized(value); data.append(static_cast(normalizedValue & 0xFF)); data.append(static_cast((normalizedValue >> 8) & 0xFF)); // QOS (设值命令质量) data.append(static_cast(0x00)); // 通常命令 emit logMessage(QString("Sending normalized setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(objectAddress) .arg(value) .arg(selectExecute)); // 保存待确认的命令 if (selectExecute) { QMutexLocker locker(&m_commandMutex); ControlCommand cmd; cmd.address = objectAddress; cmd.type = ControlCommandType::FloatSetpoint; cmd.setpoint = value; cmd.commonAddress = commonAddress; cmd.timestamp = QDateTime::currentDateTime(); m_pendingCommands[objectAddress] = cmd; } return sendIMessage(data); } // 新增:发送短浮点数设值命令 bool IEC104MasterQt::sendSetpointCommandNormalized(int commonAddress, int objectAddress, float value, bool selectExecute) { if (!m_connected) { emit logMessage("Not connected, cannot send setpoint command"); return false; } QByteArray data; // ASDU头 data.append(static_cast(selectExecute ? 0x64 : 0x65)); // 类型: 100-设点命令短浮点数, 101-设点命令短浮点数停止激活 data.append(static_cast(0x01)); // 可变结构限定词: 1个信息对象 data.append(static_cast(selectExecute ? 0x06 : 0x08)); // 传送原因: 6-激活, 8-停止激活 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); // 公共地址低字节 data.append(static_cast((commonAddress >> 8) & 0xFF)); // 公共地址高字节 // 信息对象地址 data.append(static_cast(objectAddress & 0xFF)); data.append(static_cast((objectAddress >> 8) & 0xFF)); data.append(static_cast((objectAddress >> 16) & 0xFF)); // 短浮点数值 QByteArray floatBytes = floatToBytes(value); data.append(floatBytes); // QOS (设值命令质量) data.append(static_cast(0x00)); // 通常命令 emit logMessage(QString("Sending float setpoint: Address=%1, Value=%2, SelectExecute=%3") .arg(objectAddress) .arg(value) .arg(selectExecute)); // 保存待确认的命令 if (selectExecute) { QMutexLocker locker(&m_commandMutex); ControlCommand cmd; cmd.address = objectAddress; cmd.type = ControlCommandType::FloatSetpoint; cmd.setpoint = value; cmd.commonAddress = commonAddress; cmd.timestamp = QDateTime::currentDateTime(); m_pendingCommands[objectAddress] = cmd; } return sendIMessage(data); } bool IEC104MasterQt::sendSingleCommand2(int commonAddress, int objectAddress, bool command, bool selectExecute /*= true*/, bool isRun /*= true*/) { } // 新增:处理遥控命令确认 void IEC104MasterQt::processControlCommand(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < 4) return; // 至少需要地址3字节 + 命令值1字节 + QOC1字节 const unsigned char *ptr = reinterpret_cast(data.constData()); int objectAddress = (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; unsigned char commandValue = ptr[3]; QMutexLocker locker(&m_commandMutex); if (m_pendingCommands.contains(objectAddress)) { ControlCommand cmd = m_pendingCommands[objectAddress]; switch (cause) { case 7: // 激活确认 emit logMessage(QString("Control command activation confirmed: Address=%1") .arg(objectAddress)); emit controlCommandConfirmed(objectAddress, true, "Activation confirmed"); if (cmd.type== ControlCommandType::SinglePoint) { sendSingleCommand(cmd.commonAddress, cmd.address, cmd.value ? 1 : 0, false); } else if (cmd.type == ControlCommandType::DoublePoint) { sendDoubleCommand2(cmd.commonAddress, cmd.address, cmd.value ? 2 : 1, false); } m_pendingCommands.remove(objectAddress); break; case 9: // 激活终止 m_pendingCommands.remove(objectAddress); emit logMessage(QString("Control command activation terminated: Address=%1") .arg(objectAddress)); emit controlCommandConfirmed(objectAddress, true, "Activation terminated"); break; case 47: // 命令出错 m_pendingCommands.remove(objectAddress); emit logMessage(QString("Control command error: Address=%1") .arg(objectAddress)); emit controlCommandConfirmed(objectAddress, false, "Command error"); break; default: emit logMessage(QString("Unknown control command cause: %1").arg(cause)); break; } } } // 新增:处理设值命令确认 void IEC104MasterQt::processSetpointCommand(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < 6) return; // 至少需要地址3字节 + 值2字节 + QOS1字节 const unsigned char *ptr = reinterpret_cast(data.constData()); int objectAddress = (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; QMutexLocker locker(&m_commandMutex); if (m_pendingCommands.contains(objectAddress)) { ControlCommand cmd = m_pendingCommands[objectAddress]; switch (cause) { case 7: // 激活确认 emit logMessage(QString("Setpoint command activation confirmed: Address=%1") .arg(objectAddress)); emit setpointCommandConfirmed(objectAddress, true, "Activation confirmed"); break; case 9: // 激活终止 m_pendingCommands.remove(objectAddress); emit logMessage(QString("Setpoint command activation terminated: Address=%1") .arg(objectAddress)); emit setpointCommandConfirmed(objectAddress, true, "Activation terminated"); break; case 47: // 命令出错 m_pendingCommands.remove(objectAddress); emit logMessage(QString("Setpoint command error: Address=%1") .arg(objectAddress)); emit setpointCommandConfirmed(objectAddress, false, "Command error"); break; default: emit logMessage(QString("Unknown setpoint command cause: %1").arg(cause)); break; } } } // 新增:发送命令激活终止 void IEC104MasterQt::sendCommandActivationTermination(int type, int commonAddress, int objectAddress) { // 根据命令类型发送停止激活命令 switch (type) { case 45: // 单命令 sendSingleCommand(commonAddress, objectAddress, false, false); break; case 46: // 双命令 sendDoubleCommand(commonAddress, objectAddress, 0, false); break; case 50: // 设点命令规范化值 sendFloatSetpoint(commonAddress, objectAddress, 0.0f, false); break; case 100: // 设点命令短浮点数 sendSetpointCommandNormalized(commonAddress, objectAddress, 0.0f, false); break; default: emit logMessage(QString("Unknown command type for termination: %1").arg(type)); break; } } void IEC104MasterQt::FixValueCommand(const ASDU_header *header, const QByteArray &data, int cause) { //信息序号 if (data.size() < 6) return; // 至少需要地址3字节 + 值2字节 + QOS1字节 int nPos=sizeof(P_EP_145); const P_EP_145 *ptr = reinterpret_cast(data.constData()); int nEqpmtID=(ptr->eqpmtadder2 << 8) | ptr->eqpmtadder1; switch(ptr->msgID) { case FixValueType::GetFixAll: { bool isFixArea = false; while (nPos < data.size()) { // 取出数据类型定义 const P_145_Area *ptrArea = reinterpret_cast(data.constData() + nPos); // 取出数据 nPos+=sizeof (P_145_Area); QString strData=Data2Value(nPos,data,ptrArea->FixValueType); updateFixValue(nEqpmtID, ptrArea->GIN, ptrArea->GINnum, ptrArea->FixValueType, strData); if(strData.isEmpty()) isFixArea = true; } //将定值数据记录下来 if(!isFixArea) { int startPos = sizeof(P_EP_145); int length = data.size() - startPos; if (startPos + length <= data.size()) { for (int i = 0; i < length; ++i) { m_FixData.append(data[startPos + i]); } } } } break; case FixValueType::SetFixValue: { } break; case FixValueType::RunFixValue: { } break; case FixValueType::CancelFixValue: { } break; } } void IEC104MasterQt::updateFixValue(int nEqpmtID,int nAreaID, int nAddress, int nType, QString strData) { QMutexLocker locker(&m_statusMutex); FixDataValue mv; mv.nEqpmtId = nEqpmtID; mv.nAddr = nAddress; mv.nType = nType; mv.strData = strData;; mv.timestamp = QDateTime::currentDateTime(); auto iters=m_fixValues.find(nEqpmtID); if (iters==m_fixValues.end()) { QMap> mlistData; iters=m_fixValues.insert(nEqpmtID,mlistData); } auto iter=iters->find(nAreaID); if(iter==iters->end()) { QMap mlistData; iter=iters->insert(nAreaID,mlistData); } iter->insert(nAddress,mv); } // 新增:浮点数转换为字节序列 QByteArray IEC104MasterQt::floatToBytes(float value) { QByteArray bytes(4, 0); uint32_t temp = *reinterpret_cast(&value); // IEC 104使用小端字节序 bytes[0] = static_cast(temp & 0xFF); bytes[1] = static_cast((temp >> 8) & 0xFF); bytes[2] = static_cast((temp >> 16) & 0xFF); bytes[3] = static_cast((temp >> 24) & 0xFF); return bytes; } // 新增:浮点数转换为规一化值 short IEC104MasterQt::floatToNormalized(float value, float scale) { // 将浮点数转换为规一化值 (-1.0 到 1.0 映射到 -32768 到 32767) float normalized = value / scale; if (normalized > 1.0f) normalized = 1.0f; if (normalized < -1.0f) normalized = -1.0f; return static_cast(normalized * 32767.0f); } // 添加文件传输处理函数实现 void IEC104MasterQt::processFileReady(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < sizeof(FileReady)) return; const FileReady *fr = reinterpret_cast(data.constData()); //QString fileName = bytesToFileName(&fr->name1); int num = fr->name1; QString fileName = QString::number(num); int fileSize = bytesToFileSize(&fr->length1); bool ready = (fr->ready_status == 0); emit logMessage(QString("File ready: %1, Size: %2, Ready: %3") .arg(fileName).arg(fileSize).arg(ready)); emit fileReadyReceived(fileName, fileSize, ready); // 更新文件传输状态 QMutexLocker locker(&m_fileMutex); if (m_fileTransfers.contains(fileName)) { FileTransferInfo &info = m_fileTransfers[fileName]; info.fileSize = fileSize; info.status = ready ? FTS_Success : FTS_NotReady; info.lastUpdate = QDateTime::currentDateTime(); } } void IEC104MasterQt::processSectionReady(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < sizeof(SectionReady)) return; const SectionReady *sr = reinterpret_cast(data.constData()); //QString fileName = bytesToFileName(&sr->name1); int num = sr->name1; QString fileName = QString::number(num); int section = sr->section_name; int sectionSize = bytesToFileSize(&sr->length1); bool ready = (sr->ready_status == 0); emit logMessage(QString("Section ready: %1, Section: %2, Size: %3, Ready: %4") .arg(fileName).arg(section).arg(sectionSize).arg(ready)); emit sectionReadyReceived(fileName, section, sectionSize, ready); } void IEC104MasterQt::processFileSegment(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < 6) return; // 至少需要文件名和节名 const unsigned char *ptr = reinterpret_cast(data.constData()); //QString fileName = bytesToFileName(ptr+3); //有3个字节 int num = ptr[3]; QString fileName = QString::number(num); int section = ptr[5]; int length = ptr[6]; int datalen = data.size()-7; // 段数据从第6字节开始 QByteArray segmentData = data.mid(7,length); emit logMessage(QString("File segment: %1, Section: %2, Data size: %3") .arg(fileName).arg(section).arg(segmentData.size())); // 保存段数据 QMutexLocker locker(&m_fileMutex); if (m_fileTransfers.contains(fileName)) { FileTransferInfo &info = m_fileTransfers[fileName]; //info.fileData.append(segmentData); //如果该节已经存在,追加数据 if(info.fileDataMap.contains(section)) { info.fileDataMap[section].append(segmentData); } else { info.fileDataMap.insert(section, segmentData); } info.currentSection = section; info.lastUpdate = QDateTime::currentDateTime(); emit fileSegmentReceived(fileName, section, segmentData); } } //召唤目录 void IEC104MasterQt::processDirectory(const ASDU_header *header, const QByteArray &data, int cause) { int pos = 3; QList fileList; while (pos + 16 <= data.size()) { // 每个目录项9字节 const unsigned char *ptr = reinterpret_cast(data.constData() + pos); int fileNameID = (ptr[1]<< 8) | ptr[0]; int fileSize = bytesToFileSize(ptr + 2); QDateTime timestamp = cp56TimeToDateTime(ptr+6); fileList.append(QString("%1 (%2 bytes) Time=%3").arg(QString::number(fileNameID)).arg(fileSize).arg(timestamp.toString("yyyy-MM-dd hh:mm:ss.zzz"))); pos += 16; emit logMessage(QString("Directory entry: %1, Size: %2 Time=%3").arg(QString::number(fileNameID)).arg(fileSize).arg(timestamp.toString("yyyy-MM-dd hh:mm:ss.zzz"))); } emit directoryReceived(fileList); } // 添加其他文件传输处理函数(简化实现) void IEC104MasterQt::processFileCall(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < sizeof(FileCall)) return; const FileCall *fc = reinterpret_cast(data.constData()); QString fileName = bytesToFileName(&fc->name1); int section = fc->section_name; int qualifier = fc->qualifier; QString qualifierStr; switch (qualifier) { case FTRQ_SelectFile: qualifierStr = "Select File"; break; case FTRQ_RequestFile: qualifierStr = "Request File"; break; case FTRQ_SelectSection: qualifierStr = "Select Section"; break; case FTRQ_RequestSection: qualifierStr = "Request Section"; break; case FTRQ_Directory: qualifierStr = "Directory"; break; default: qualifierStr = "Unknown"; break; } emit logMessage(QString("File call: %1, Section: %2, Qualifier: %3") .arg(fileName).arg(section).arg(qualifierStr)); } //节最后段数据接收后校验数据 void IEC104MasterQt::processLastSection(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < sizeof(LastSection)) return; const LastSection *ls = reinterpret_cast(data.constData()); //QString fileName = bytesToFileName(&ls->name1); int num = ls->name1; QString fileName = QString::number(num); int section = ls->section_name; int lsq = ls->last_section; int checksum = ls->checksum; emit logMessage(QString("fileName: %1, Current Section: %2, lsq: %3, Checksum: 0x%4") .arg(fileName).arg(section).arg(lsq).arg(checksum, 4, 16, QLatin1Char('0'))); // 文件传输完成 QMutexLocker locker(&m_fileMutex); if (m_fileTransfers.contains(fileName)) { FileTransferInfo &info = m_fileTransfers[fileName]; info.status = FTS_Success; info.lastUpdate = QDateTime::currentDateTime(); // 计算校验和验证 quint16 calculatedChecksum = 0; int nsectionData = 0; if(lsq == SCQ_StopSelect) //所选节传输完毕 { if (info.fileDataMap.contains(section)) { const QByteArray sectionData = info.fileDataMap[section]; for (int i = 0; i < sectionData.size(); i++) { calculatedChecksum += static_cast(sectionData[i]); } nsectionData = sectionData.size(); emit logMessage(QString("calculatedCheck fileData SIZE: %1, fileData: %2").arg(sectionData.size()).arg(toHexString(sectionData).constData())); } } else if(lsq == SCQ_SelectFile) //所选文件传输完毕 { for (auto it = info.fileDataMap.constBegin(); it != info.fileDataMap.constEnd(); ++it) { const QByteArray& sectionData = it.value(); for (int i = 0; i < sectionData.size(); i++) { calculatedChecksum += static_cast(sectionData[i]); } nsectionData += sectionData.size(); } } calculatedChecksum &= 0x00FF; bool checksumValid = (calculatedChecksum == checksum); if (checksumValid) { emit fileTransferCompleted(fileName, section, lsq, true, "File transfer completed successfully"); emit logMessage(QString("File transfer completed: %1, Size: %2 bytes, Checksum valid") .arg(fileName).arg(nsectionData)); } else { emit fileTransferCompleted(fileName, section, lsq, false, QString("Checksum error: expected 0x%1, got 0x%2") .arg(checksum, 4, 16, QLatin1Char('0')) .arg(calculatedChecksum, 4, 16, QLatin1Char('0'))); emit logMessage(QString("File transfer checksum error: %1").arg(fileName)); } } } void IEC104MasterQt::processFileAck(const ASDU_header *header, const QByteArray &data, int cause) { if (data.size() < sizeof(FileAck)) return; const FileAck *fa = reinterpret_cast(data.constData()); QString fileName = bytesToFileName(&fa->name1); int section = fa->section_name; int ackStatus = fa->ack_status;//限定词? QString statusStr = (ackStatus == 0) ? "Accepted" : "Rejected"; emit logMessage(QString("File acknowledgement: %1, Section: %2, Status: %3") .arg(fileName).arg(section).arg(statusStr)); // 如果被拒绝,更新文件传输状态 if (ackStatus != 0) { QMutexLocker locker(&m_fileMutex); if (m_fileTransfers.contains(fileName)) { FileTransferInfo &info = m_fileTransfers[fileName]; info.status = FTS_AccessDenied; info.lastUpdate = QDateTime::currentDateTime(); emit fileTransferCompleted(fileName, section, ackStatus, false, "File transfer rejected by remote"); } } } // 文件传输公共接口实现 bool IEC104MasterQt::requestDirectory(int commonAddress,int msgAddress) { QByteArray data; data.append(static_cast(F_SC_NA_1)); // 类型 data.append(static_cast(0x01)); // 可变结构限定词 data.append(static_cast(0x05)); // 传送原因 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); // 文件名全0表示目录请求 data.append(static_cast(0x00)); data.append(static_cast(0x00)); data.append(static_cast(0x00)); data.append(static_cast(0x00)); data.append(static_cast(0x00)); // 节名 data.append(static_cast(0x02)); // 限定词 //请求目录 emit logMessage("Requesting directory..."); return sendIMessage(data); } //选择文件 bool IEC104MasterQt::selectFile(int commonAddress,int msgAddress, const QString &fileName) { QByteArray data; data.append(static_cast(F_SC_NA_1)); data.append(static_cast(0x01)); data.append(static_cast(0x0D)); data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); QByteArray nameBytes = fileNameToBytes(fileName); //data.append(nameBytes); data.append(static_cast(fileName.toInt() & 0xFF)); data.append(static_cast((fileName.toInt() >> 8) & 0xFF)); data.append(static_cast(0x00)); // 节名 data.append(static_cast(SCQ_SelectFile)); emit logMessage(QString("Selecting file: %1").arg(fileName)); // 初始化文件传输状态 QMutexLocker locker(&m_fileMutex); FileTransferInfo info; info.fileName = fileName; info.startTime = QDateTime::currentDateTime(); info.status = FTS_Success; m_fileTransfers[fileName] = info; return sendIMessage(data); } //请求文件 bool IEC104MasterQt::requestFile(int commonAddress,int msgAddress, const QString &fileName) { QByteArray data; data.append(static_cast(F_SC_NA_1)); data.append(static_cast(0x01)); data.append(static_cast(0x0D)); data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); QByteArray nameBytes = fileNameToBytes(fileName); //data.append(nameBytes); data.append(static_cast(fileName.toInt() & 0xFF)); data.append(static_cast((fileName.toInt() >> 8) & 0xFF)); data.append(static_cast(0x00)); data.append(static_cast(FTRQ_RequestFile)); emit logMessage(QString("Requesting file: %1").arg(fileName)); return sendIMessage(data); } //请求段数据 bool IEC104MasterQt::requestSection(int commonAddress,int msgAddress, const QString &fileName, int section) { if (!m_connected) { emit logMessage("Not connected, cannot request section"); return false; } QByteArray data; data.append(static_cast(F_SC_NA_1)); // 类型 data.append(static_cast(0x01)); // 可变结构限定词 data.append(static_cast(0x0D)); // 传送原因 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); QByteArray nameBytes = fileNameToBytes(fileName); //data.append(nameBytes); data.append(static_cast(fileName.toInt() & 0xFF)); data.append(static_cast((fileName.toInt() >> 8) & 0xFF)); data.append(static_cast(section)); // 节名 data.append(static_cast(SCQ_RequesSection)); // 限定词 emit logMessage(QString("Requesting section: %1, Section: %2").arg(fileName).arg(section)); return sendIMessage(data); } //认可段数据 bool IEC104MasterQt::acceptSection(int commonAddress,int msgAddress, const QString &fileName, int section, int lsq) { if (!m_connected) { emit logMessage("Not connected, cannot request section"); return false; } QByteArray data; data.append(static_cast(F_AF_NA_1)); // 类型 data.append(static_cast(0x01)); // 可变结构限定词 data.append(static_cast(0x0D)); // 传送原因 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); QByteArray nameBytes = fileNameToBytes(fileName); //data.append(nameBytes); data.append(static_cast(fileName.toInt() & 0xFF)); data.append(static_cast((fileName.toInt() >> 8) & 0xFF)); data.append(static_cast(section)); // 节名 data.append(static_cast(lsq)); // 限定词 确认段 emit logMessage(QString("Requesting section: %1, Section: %2").arg(fileName).arg(section)); return sendIMessage(data); } // 工具函数实现 QString IEC104MasterQt::bytesToFileName(const unsigned char *bytes) { char name[5] = {0}; for (int i = 0; i < 2; i++) { name[i] = bytes[i]; } return QString::fromLocal8Bit(name).trimmed(); } QByteArray IEC104MasterQt::fileNameToBytes(const QString &fileName) { QByteArray bytes(2, 0); QByteArray nameBytes = fileName.toLocal8Bit(); for (int i = 0; i < qMin(2, nameBytes.size()); i++) { bytes[i] = nameBytes[i]; } return bytes; } int IEC104MasterQt::bytesToFileSize(const unsigned char *bytes) { return (bytes[0]) | (bytes[1] << 8); } QByteArray IEC104MasterQt::fileSizeToBytes(int fileSize) { QByteArray bytes(2, 0); bytes[0] = static_cast(fileSize & 0xFF); bytes[1] = static_cast((fileSize >> 8) & 0xFF); return bytes; } QMap IEC104MasterQt::getFileTransfers() const { QMutexLocker locker(&m_fileMutex); return m_fileTransfers; } bool IEC104MasterQt::acknowledgeFile(int commonAddress,int msgAddress, const QString &fileName, bool accept) { if (!m_connected) { emit logMessage("Not connected, cannot acknowledge file"); return false; } QByteArray data; data.append(static_cast(F_AF_NA_1)); // 类型 data.append(static_cast(0x01)); // 可变结构限定词 data.append(static_cast(0x07)); // 传送原因 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); QByteArray nameBytes = fileNameToBytes(fileName); data.append(nameBytes); data.append(static_cast(0x00)); // 节名 data.append(static_cast(accept ? 0 : 1)); // 认可状态: 0=接受, 1=拒绝 emit logMessage(QString("Acknowledge file: %1, Accept: %2").arg(fileName).arg(accept)); return sendIMessage(data); } bool IEC104MasterQt::acknowledgeSection(int commonAddress,int msgAddress, const QString &fileName, int section, bool accept) { if (!m_connected) { emit logMessage("Not connected, cannot acknowledge section"); return false; } QByteArray data; data.append(static_cast(F_AF_NA_1)); // 类型 data.append(static_cast(0x01)); // 可变结构限定词 data.append(static_cast(0x07)); // 传送原因 data.append(static_cast(0x00)); data.append(static_cast(commonAddress & 0xFF)); data.append(static_cast((commonAddress >> 8) & 0xFF)); // 信息对象地址 data.append(static_cast(msgAddress & 0xFF)); data.append(static_cast((msgAddress >> 8) & 0xFF)); data.append(static_cast((msgAddress >> 16) & 0xFF)); QByteArray nameBytes = fileNameToBytes(fileName); data.append(nameBytes); data.append(static_cast(section)); // 节名 data.append(static_cast(accept ? 0 : 1)); // 认可状态: 0=接受, 1=拒绝 emit logMessage(QString("Acknowledge section: %1, Section: %2, Accept: %3") .arg(fileName).arg(section).arg(accept)); return sendIMessage(data); }