基于C++和Qt开发IEC104规约主站调试软件需要综合协议规范、多线程编程和GUI框架知识以及IEC104通讯规约相关知识,以下是分步骤实现方案:
最终成果
IEC104通讯规约知识学习
上一章内容回顾:IEC104主站调试软件发送总召、时钟同步
1.报文解析:输入报文点击报文解析按钮
代码示例:
QByteArray frame = ui->textEdit->toPlainText().trimmed().toUtf8();
if(frame.isEmpty()){
MessageReminder("请在报文列表中输入有效报文","确认","取消");
return;
}
QByteArray hexFrame = QByteArray::fromHex(frame);
int sz = hexFrame.length();
if( sz<6 || (hexFrame[0]!= 0x68 && (hexFrame[1]< 0x04 || hexFrame[1]>0xFF))){
showMessage("无效报文",-1);
return;
}
if(sz>6){
hexFrame = hexFrame.mid(6,sz-6);
uint8_t* msg = (uint8_t*)hexFrame.data();
CS104_Connection cn = CS104_Connection_create("127.0.0.1", 2404);
CS101_AppLayerParameters alParams = CS104_Connection_getAppLayerParameters(cn);
CS101_ASDU asdu = CS101_ASDU_createFromBuffer(alParams, msg, sz - 6);
IEC60870_5_TypeID typeId = CS101_ASDU_getTypeID(asdu);
int nums = CS101_ASDU_getNumberOfElements(asdu);
CS101_CauseOfTransmission cause = CS101_ASDU_getCOT(asdu);
CP56Time2a newTime;
QString time;
QualityDescriptor qu;
QString quStr;
QString causeStr = m_causeHash.value(cause);
if(causeStr.isEmpty())
causeStr=QString("未知(%1)").arg(cause);
QString typeStr;
int address;
if (typeId == C_TS_TA_1)
qInfo()<<"test command with timestamp";
else if (typeId== C_IC_NA_1) {//总召唤
if (cause== CS101_COT_ACTIVATION) //激活确认
showMessage("主站总召激活确认请求报文",-1);
if (cause== CS101_COT_ACTIVATION_CON) //激活确认
showMessage("从站响应总召激活确认报文",-1);
else if (cause == CS101_COT_ACTIVATION_TERMINATION) //激活停止
showMessage("总召结束激活停止报文",-1);
}
else if(typeId ==M_SP_NA_1){ //单点遥信(不带时标)
bool value;
time = "无";
typeStr ="无时标单点遥信";
for (int i = 0; i < nums; i++) {
SinglePointInformation io =(SinglePointInformation) CS101_ASDU_getElement(asdu, i);
if(io==nullptr){
showMessage(QString("解析%1数据错误,序号=%2").arg(typeStr).arg(i),-1);
continue;
}
qu = SinglePointInformation_getQuality(io);
quStr = m_quMap.value(qu);
if(quStr.isEmpty())
quStr=QString("异常(0x%1)").arg(qu,2,16,QLatin1Char('0'));
address = InformationObject_getObjectAddress((InformationObject) io);
value = SinglePointInformation_getValue(io);
SinglePointInformation_destroy(io);
showMessage(QString("%1,原因:%2,地址:%3,值:%4,品质:%5,时间:%6").arg(typeStr).arg(causeStr).arg(address).arg(value).arg(quStr).arg(time),-1);
}
}
else if(typeId ==M_ME_TF_1 || typeId==M_ME_TC_1){ //短浮点遥测带时标
typeStr ="长时标短浮点遥测";
float value;
for (int i = 0; i < nums; i++) {
MeasuredValueShortWithCP56Time2a iotm =(MeasuredValueShortWithCP56Time2a) CS101_ASDU_getElement(asdu, i);
MeasuredValueShort io =(MeasuredValueShort) CS101_ASDU_getElement(asdu, i);
if(io==nullptr){
showMessage(QString("解析%1数据错误,序号=%2").arg(typeStr).arg(i),-1);
continue;
}
qu = MeasuredValueShort_getQuality(io);
quStr = m_quMap.value(qu);
if(quStr.isEmpty())
quStr=QString("异常(0x%1)").arg(qu,2,16,QLatin1Char('0'));
address = InformationObject_getObjectAddress((InformationObject) io);
value = MeasuredValueShort_getValue(io);
newTime = MeasuredValueShortWithCP56Time2a_getTimestamp(iotm);
if( typeId==M_ME_TC_1){
typeStr ="短时标短浮点遥测";
time = QString("%5:%6.%7").arg(CP56Time2a_getMinute(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getSecond(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getMillisecond(newTime),3,10,QLatin1Char('0'));
}
else
time = QString("%1-%2-%3 %4:%5:%6.%7").arg(CP56Time2a_getYear(newTime) + 2000)
.arg(CP56Time2a_getMonth(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getDayOfMonth(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getHour(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getMinute(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getSecond(newTime),2,10,QLatin1Char('0'))
.arg(CP56Time2a_getMillisecond(newTime),3,10,QLatin1Char('0'));
MeasuredValueShort_destroy(io);
MeasuredValueShortWithCP56Time2a_destroy(iotm);
showMessage(QString("%1,原因:%2,地址:%3,值:%4,品质:%5,时间:%6").arg(typeStr).arg(causeStr).arg(address).arg(value).arg(quStr).arg(time),-1);
}
}
运行演示:
2.报文导出:点击报文导出按钮,导出报文到本地txt文件
代码示例:
QString frame = ui->textEdit->toPlainText();
if(frame.isEmpty()){
MessageReminder("报文为空,无法导出","确定","取消");
return;
}
QString filename = QFileDialog::getSaveFileName(this, nullptr, nullptr, "*.txt").toUtf8();
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
// 处理错误,例如可以抛出异常或者返回错误标志
MessageReminder("导出到文件失败,导出文件不存在","确定","取消");
return;
}
QTextStream out(&file);
out<<frame;
file.close(); // 关闭文件
MessageReminder("导出报文完成","确定","取消");
3.报文开始停止滚动:点击开始滚动按钮即报文实时滚动,点击停止滚动按钮,即报文暂停滚动
代码示例:
if(!m_roll)//停止滚动
return;
if(ui->textEdit->toPlainText().length()>50000)
ui->textEdit->clear();
ui->textEdit->setTextColor(QColor(Qt::black));
QString typeStr = "主站:";
if(type==0){
typeStr = "主站:";
ui->textEdit->setTextColor(QColor(Qt::black));
}
else if(type==1){
typeStr ="从站:";
ui->textEdit->setTextColor(QColor(Qt::blue));
}
else
typeStr.clear();
ui->textEdit->append(typeStr+msg);
ui->textEdit->update();
4.清空报文:点击清空报文按钮,清空当前报文显示区
代码示例:
connect(ui->clearButton,&QPushButton::clicked,[=](){
ui->textEdit->clear();
});
本章节主要讲解功能按钮的功能实现和核心代码,结合前五个章节的内容,即可完成IEC104主站调试软件开发应用。关于IEC104主站调试软件开发到此章节正式结束,若有疑问欢迎各位博主咨询。