文章目录
- 一、DLT645通信规约
- 1.DLT645-1997通信规约
- 2.DLT645-2007通讯规约
- 3.DLT645-1997与DLT645-2007的区别
- 二、DLT645通信规约报文解析
- 1.DLT645-1997通信规约报文解析
- 2.DLT645-2007通信规约报文解析
- 三、C++代码组织报文与解析报文
一、DLT645通信规约
DLT645协议,包括DLT645-1997和DLT645-2007,是一种问答式(主从式)通信规约。在这种通信模式下,通常存在一个主站和一个或多个从站。主站负责发起通信请求,从站则根据主站的请求提供相应的响应。
问答式规约的通信过程通常包括以下几个步骤:
- 主站发起请求:主站发送一个请求报文给特定的从站,请求报文中包含了需要从站执行的操作,如读取电能量、读取实时参数等。
- 从站接收请求:从站接收到主站的请求后,根据请求的内容进行处理。
- 从站准备响应:从站根据请求的内容准备相应的数据,并构造一个响应报文。
- 从站发送响应:从站将构造好的响应报文发送回主站。
- 主站接收响应:主站接收到从站的响应后,根据响应内容进行相应的处理。
1.DLT645-1997通信规约
DLT645-1997是中国电力行业标准,全称为《多功能电能表通信协议》,主要用于电力系统中电能表的通信。这个标准定义了电能表与数据终端设备之间进行数据交换时的物理层、链路层以及应用层的通信协议。DLT645-1997协议采用主-从结构的半双工通信模式,硬件接口通常使用RS-485。
- 适用范围:适用于本地系统中多功能表的费率装置与手持单元(HHU)或其他数据终端设备进行点对点的或一主多从的数据交换方式。
- 物理层:采用RS-485标准串行电气接口
- 链路层:规定了通信链路及应用技术规范。
- 应用层:涉及到电能表的数据交换,如读取电能量、功率等信息
字节格式:
每字节含8位二进制码,传输时加上一个起始位(0)、一个偶校验位和一个停止位(1)共11位。其传输序列如下。D0
是字节的最低有效位,D7
是字节的最高有效位。先传低位,后传高位。
报文帧格式:
帧是传送信息的基本单元。帧格式如下:
说明 | 代码 |
---|---|
帧起始符(1个字节) | 68H |
地址域(6个字节) | A0~A5 |
帧起始符(1个字节) | 68H |
控制码(1个字节) | C |
数据长度域(1个字节) | L |
数据域 (变长) | DATA |
校验码(1个字节) | CS |
结束符(1个字节) | 16H |
-
帧起始符68H:标识一帧信息的开始,其值为68H=01101000B
-
地址域A0∽A5:地址域由6个字节构成,每字节2位BCD码。地址长度为12位十进制数,可以为表号、资产号、用户号、设备号等。通常作为电能表表号字段,如下。具体使用可由用户自行决定。当使用的地址码长度不足6字节时,用十六进制AAH补足6字节。低地址位在先,高地址位在后。当地址为999999999999H时,为广播地址。
-
控制码C:控制码格式如下
-
D7
- D7=0:由主站发出的命令帧
- D7=1:由从站发出的应答帧
-
D6
- D6=0:从站正确应答
- D6=1:从站对异常信息的应答
-
D5
- D5=0:无后续数据帧
- D5=1:有后续数据帧
-
D4∽D0:请求及应答功能码
00000:保留 00001:读数据 00010:读后续数据(在第一次读取之后,继续读取剩余的数据。并不是一次读取多个点位的数据) 00011:重读数据 00100:写数据 01000:广播校时 01010:写设备地址 01100:更改通信速率 01111:修改密码 10000:最大需量清零
-
-
数据长度L:L为数据域的字节数。读数据时L≤200,写数据时L≤50,L=0 表示无数据域。
-
数据域DATA:数据域包括数据标识和数据、密码等,其结构随控制码的功能而改变(变长)。传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理。
数据标识:
本规约采用四级树状结构的标识法来表示这些数据。用2个字节的4个字段分别标识数据的类型和属性,这2个字节为DI1和DI0,4个字段分别为DI1H、DI1L、DI0H、DI0L,其中DI0L为最低级标识字段,DI1H为最高级标识段。
- DI1H标识数据的类型
- DI1L、DI0H、DI0L标识数据的不同属性
对于电能量和最大需量数据,由于其具有多个属性,如时域性(当前值、上月值、上上月值等)、分类属性(有功、无功)、供电方向属性(正向、反向)、费率属性(总量、不同费率的量)等,它们的标识如下:
电能量数据标识:
最大需量数据标识:
-
校验码CS:从帧起始符开始到校验码之前的所有各字节的模256的和, 即各字节二进制算术和,不计超过256的溢出值。
-
结束符号16H:标识一帧信息的结束,其值为16H=00010110B。
传输事项:
- 前导字节:在发送帧信息之前,先发送1-4个字节
FEH
,以唤醒接收方。 - 传输次序:所有数据项均先传送低位字节,后传送高位字节。
- 传输响应:每次通信都是由主站按信息帧地址域选择的从站发出请求命令帧开始,被请求的从站根据命令帧中控制码的要求作出响应。
- 差错控制:字节校验为偶校验,帧校验为纵向信息校验和,按收方无论检测到偶校验出错或纵向信息校验和出错,均放该信息帧,不予响应。
2.DLT645-2007通讯规约
DLT645-2007是中国电力行业标准,全称为《多功能电能表通信协议》,主要用于电力系统中电能表的通信。这个标准定义了电能表与数据终端设备之间进行数据交换时的物理层、链路层以及应用层的通信协议。DLT645-2007协议采用主-从结构的半双工通信模式,硬件接口通常使用RS-485。
本协议为主-从结构的半双工通信方式。手持单元或其它数据终端为主站,多功能电能表为从站。每个多功能电能表均有各自的地址编码。通信链路的建立与解除均由主站发出的信息帧来控制。每帧由帧起始符、从站地址域、控制码、数据域长度、数据域、帧信息纵向校验码及帧结束符7个域组成。每部分由若干字节组成。
字节格式:
每字节含8位二进制码,传输时加上一个起始位(0)、一个偶校验位和一个停止位(1), 共 11位。其传输序列如下。D0 是字节的最低有效位,D7 是字节的最高有效位。先传低位,后传高位。
报文帧格式:
帧是传送信息的基本单元。帧格式如下:
说明 | 代码 |
---|---|
帧起始符(1个字节) | 68H |
地址域(6个字节) | A0~A5 |
帧起始符(1个字节) | 68H |
控制码(1个字节) | C |
数据长度域(1个字节) | L |
数据域 (变长) | DATA |
校验码(1个字节) | CS |
结束符(1个字节) | 16H |
-
帧起始符68H:标识一帧信息的开始,其值为68H=01101000B
-
地址域 A0~A5:地址域由 6 个字节构成,每字节 2 位 BCD 码,地址长度可达12位十进制数。每块表具有唯一的通信地址,且与物理层信道无关。当使用的地址码长度不足 6 字节时,高位用“0”补足 6 字节。
- 通信地址999999999999H为广播地址,只针对特殊命令有效,如广播校时、广播冻结等。广播命令不要求从站应答。
- 地址域支持缩位寻址,即从若干低位起,剩余高位补AAH作为通配符进行读表操作,从站应答帧的地址域返回实际通信地址。
- 地址域传输时低字节在前,高字节在后。
-
控制码 C
控制码格式如下:
-
数据域长度L
L 为数据域的字节数。读数据时 L≤200,写数据时 L≤50,L=0 表示无数据域。 -
数据域DATA
数据域包括数据标识、密码、操作者代码、数据、帧序号等,其结构随控制码的功能而改变。传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理。数据标识说明:
数据标识编码用四个字节区分不同数据项,四字节分别用DI3、DI2、DI1和DI0代表,每字节采用十六进制编码。数据类型分为七类:电能量、最大需量及发生时间、变量、事件记录、参变量、冻结量、负荷记录。
-
DI3
DI3标识符 对应数据类型 00 电能量 01 最大需量及发生时间 02 变量数据 (遥测等) 03 事件记录 04 参变量数据 05 冻结量 06 负荷记录
可以参考DLT645-2007多功能电能表通信协议数据标识编码表来确定每个标识的具体含义。
-
-
校验码 CS
从第一个帧起始符开始到校验码之前的所有各字节的模 256 的和,即各字节二进制算术和,不计超过 256 的溢出值。 -
结束符 16H
标识一帧信息的结束,其值为 16H=00010110B。
传输事项:
- 前导字节:在主站发送帧信息之前,先发送4个字节FEH,以唤醒接收方。
- 传输次序:所有数据项均先传送低位字节,后传送高位字节。数据传输的举例:电能量值为123456.78kWh,其传输次序如下
- 传输响应:每次通信都是由主站向按信息帧地址域选择的从站发出请求命令帧开始,被请求的从站接收到命令后作出响应。
- 差错控制:字节校验为偶校验,帧校验为纵向信息校验和,接收方无论检测到偶校验出错或纵向信息校验和出错,均放弃该信息帧,不予响应。
3.DLT645-1997与DLT645-2007的区别
DLT645-1997与DLT645-2007都是中国电力行业标准,用于规范多功能电能表的通信协议。尽管两者在很多方面相似,它们之间还是存在一些差异。
DLT645-1997与DLT645-2007之间的主要区别:
-
控制码不同:DLT645-1997和DLT645-2007的控制码有所不同。例如,在DLT645-1997中,控制码为0x01(发送)和0x81(接收),而在DLT645-2007中,相应的控制码为0x11(发送)和0x91(接收)。
-
标识码不同:在DLT645-1997中,用两个字节四个字段来区分不同的数据项。而在DLT645-2007中,用四个字节来区分不同的数据项。
-
前导码不同:DLT645-2007在传输帧信息前,一般需要有4个0xFE作为前导字节,而DLT645-1997发送1-4个字节0xFE作为前导字节
实际上,不同厂家不同型号的表前导码FE的个数是不同的,还有些厂家不会发送前导码FE,我们解析接收数据的一般方法是忽略前面N个FE,寻找数据帧中的68帧头作为一帧的起始位置
二、DLT645通信规约报文解析
1.DLT645-1997通信规约报文解析
读取当前反向有功总电量(发电量)–请求帧:
帧起始符 | 地址域 | 帧起始符 | 控制码 | 数据长度域 | 数据域 | 校验码 | 结束符 |
---|---|---|---|---|---|---|---|
68 | 61 45 69 00 00 00 | 68 | 01 | 02 | 53 C3 | 01 | 16 |
-
68
:标识一帧信息的开始 -
61 45 69 00 00 00
:电表地址,翻转后00 00 00 69 45 61
,得到实际电表地址694561 -
01
:对应格式为00000001
,即主站发出的命令帧、从站正确应答、无后续数据帧,且读数据 -
02
:数据域的字节数为2 -
53 C3
:数据项标识部分,这一部分先传送低位字节,后传送高位字节,传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理`53 C3` --减33H--> `20 90` --逆序--> `90 20` ---> `10010000 00100000`
对应于当前反向有功总电能
-
F8
:它的值是它前面从第一个68起始符开始到最后一字节数据段的单字节累加和(单字节累加,忽略溢出)
-
16
:标识一帧信息的结束
读取当前反向有功总电量(发电量)–返回帧:
帧起始符 | 地址域 | 帧起始符 | 控制码 | 数据长度域 | 数据域 | 校验码 | 结束符 |
---|---|---|---|---|---|---|---|
68 | 61 45 69 00 00 00 | 68 | 81 | 06 | 53 C3 33 78 34 66 | CE | 16 |
-
68
:标识一帧信息的开始 -
61 45 69 00 00 00
:电表地址,翻转后00 00 00 69 45 61
,得到实际电表地址694561 -
81
:对应格式为10000001`,即从站发出的应答帧、从站正确应答、无后续数据帧,且读数据 -
06
:数据域的字节数为6 -
53 C3
:数据项标识部分,这一部分先传送低位字节,后传送高位字节,传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理`53 C3` --减33H--> `20 90` --逆序--> `90 20` ---> `10010000 00100000`
对应于当前反向有功总电能
-
33 78 34 66
:数据项对应的数值部分处理方法:统一减去33H,再翻转,在指定位置加入小数点,即可得到当前正向有功电能(电量)
33 78 34 66 --------减33--------> 00 45 01 33 00 45 01 33 --------反转--------> 33 01 45 00 33 01 45 00 ------加小数点-----> 33 01 45.00
结果就是:330145.00 kWh (电表总反相有功电能量,也就是“发电量”。耗电量是“正相有功电能量”)
-
CE
:它的值是它前面从第一个68起始符开始到最后一字节数据段的单字节累加和(单字节累加,忽略溢出)
-
16
:标识一帧信息的结束
2.DLT645-2007通信规约报文解析
读取当前正向有功总电量(耗电量)–请求帧:
帧起始符 | 地址域 | 帧起始符 | 控制码 | 数据长度域 | 数据域 | 校验码 | 结束符 |
---|---|---|---|---|---|---|---|
68 | AA AA AA AA AA AA | 68 | 11 | 04 | 33 33 34 33 | AE | 16 |
-
68
:标识一帧信息的开始 -
AA AA AA AA AA AA
:地址域支持缩位寻址,即从若干低位起,剩余高位补AAH作为通配符进行读表操作 -
11
:对应格式为00010001
,即主站发出的命令帧、从站正确应答、无后续数据帧,且读数据 -
04
:数据域的字节数为4 -
33 33 34 33
:数据项标识部分,这一部分先传送低位字节,后传送高位字节,传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理`33 33 34 33` --减33H--> `00 00 01 00` --逆序--> `00 01 00 00`
对应于当前正向有功总电能
-
AE
:它的值是它前面从第一个68起始符开始到最后一字节数据段的单字节累加和(单字节累加,忽略溢出)
-
16
:标识一帧信息的结束
读取当前正向有功总电量(耗电量)–返回帧:
帧起始符 | 地址域 | 帧起始符 | 控制码 | 数据长度域 | 数据域 | 校验码 | 结束符 |
---|---|---|---|---|---|---|---|
68 | 72 00 32 09 17 20 | 68 | 91 | 08 | 33 33 34 33 B9 34 33 33 | 6D | 16 |
-
68
:标识一帧信息的开始 -
72 00 32 09 17 20
:表号字段,翻转后20 17 09 32 00 72
,得到实际表号
-
91:对应格式为
10010001
,即从站发出的应答帧、从站正确应答、无后续数据帧,且读数据 -
08
:数据域的字节数为8 -
33 33 34 33
:数据项标识部分,这一部分先传送低位字节,后传送高位字节,传输时发送方按字节进行加33H处理,接收方按字节进行减33H处理`33 33 34 33` --减33H--> `00 00 01 00` --逆序--> `00 01 00 00`
对应于当前正向有功总电能
-
B9 34 33 33
:数据项对应的数值部分
处理方法:统一减去33H,再翻转,在指定位置加入小数点,即可得到当前正向有功电能(电量)B9 34 33 33 --------减33--------> 86 01 00 00 86 01 00 00 --------反转--------> 00 00 01 86 00 00 01 86 ------加小数点-----> 00 00 01.86
小数点位置由DLT645-2007通信规约决定
-
6E
:它的值是它前面从第一个68起始符开始到最后一字节数据段的单字节累加和(单字节累加,忽略溢出)
-
16
:标识一帧信息的结束
三、C++代码组织报文与解析报文
-
首先,从数据库表中获取规约的编号,如:
ptl_645_serial_cj
-
然后,加载规约动态库,得到该规约对应的组织数据与解析数据的函数
-
通道通信发送数据处理
调用规约报文组织函数组织报文PTL_API void buildData(S_RecvSendData &sRecvSendData, CUnitBase* pUnit) //组织报文 { S_PtlVariant sVariant; memset(&sVariant, 0, sizeof(S_PtlVariant)); if(pUnit != NULL) { sVariant.pUnit = pUnit; sVariant.pBySendBuffer = sRecvSendData.pSendBuf; sVariant.uwSendDataLen = sRecvSendData.uiSendLen; sVariant.bMainChannel = sRecvSendData.bMainChannel; sVariant.byChannelMode = sRecvSendData.byChannelMode; sVariant.byChannelIndex = sRecvSendData.byChannelIndex; sVariant.pPtlFlag = (S_PtlFlag*)(pUnit->getTUFlagBuffer()); // 获取规约标志位使用的缓冲区 sendFrame(sVariant); //组织数据 sRecvSendData.uiSendLen = sVariant.uwSendDataLen; } }
基于数据库表
- 通讯装置表中的地址,确定报文帧的地址域
enum E_COLID_TU //通讯装置表 { COL_TU_ID = 1, //主键ID COL_TU_TUNAME, //装置名称 COL_TU_TUMODELID, //装置型号 COL_TU_TUMODELSN, //装置编号 COL_TU_TUTYPE, //装置类型 COL_TU_CHANNELGROUPID, //通道组ID COL_TU_TUADDR, //装置通讯地址 COL_TU_LOCALADDR, //装置通讯地址 COL_TU_TUADDR6, //地址6 COL_TU_TUADDR5, //地址5 COL_TU_TUADDR4, //地址4 COL_TU_TUADDR3, //地址3 COL_TU_TUADDR2, //地址2 COL_TU_TUADDR1, //地址1 COL_TU_TUADDR0, //地址0 COL_TU_UPCOMSTATE, //上行通讯状态 COL_TU_DOWNCOMSTATE, //下行通讯状态 COL_TU_YKCMDTIMEOUT, //遥控命令超时时间 COL_TU_DZCMDTIMEOUT, //定值命令超时时间 COL_TU_COMTRADETIMEOUT, //故障滤波超时时间 COL_TU_OTHRTCMDTIMEOUT, //其他命令超时时间 COL_TU_byISPublicZFTable, //是否共用转发表 COL_TU_uiPublicZFTUID, //共用转发装置 COL_TU_uiRelationReturnYK,//关联复归遥控 COL_TU_MQTTTUID, //MQTT设备ID COL_TU_MAX };
- 645规约表确定规约版本与前导字节数
struct S_Ptl645 //645规约表 { quint32 uiID; //主键ID quint32 uiPtlID; //通讯规约ID quint8 byVersion; //规约版本 quint32 uiPreByteNum; //前导字节数 quint32 uiPollingInterval; //轮询时间间隔 bool bIfCheckTime; //是否启用校时 S_Ptl645() { memset(this, 0, sizeof(S_Ptl645)); } }ALIGNS;
- 645数据信息表确定控制码、数据表示、数据长度等
struct S_Ptl645Data //645数据信息表 { quint32 uiID; //主键ID quint32 uiTUModelID; //装置型号 quint32 uiDataIndex; //数据点号,对应遥测遥信遥脉等点位的序号 quint32 uiDICID; //数据标识ID; quint8 byCTRLCode; //控制码 quint8 byDI3; //DI3 quint8 byDI2; //DI2 quint8 byDI1; //DI1 quint8 byDI0; //DI0 quint8 byLength; //数据长度 char chDataName[SIZE_NAME]; //数据名称 S_Ptl645Data() { memset(this, 0, sizeof(S_Ptl645Data)); } }ALIGNS;
组织97与07报文帧
struct S_Sendframe07 { quint8 byStart1; //启动字符 quint8 byA0; //地址域 每字节2位BCD码 quint8 byA1; //地址域 quint8 byA2; //地址域 quint8 byA3; //地址域 quint8 byA4; //地址域 quint8 byA5; //地址域 quint8 byStart2; //启动字符 quint8 bycontrol; //功能码 quint8 byDataLen; //数据域 quint8 byDI0; //数据标识 quint8 byDI1; //数据标识 quint8 byDI2; //数据标识 quint8 byDI3; //数据标识 quint8 byCS; //校验码 quint8 byEnd; //结束字符 S_Sendframe07() { memset(this, 0, sizeof (S_Sendframe07)); byStart1 = 0x68; byStart2 = 0x68; byEnd = 0x16; } }ALIGNS; struct S_Sendframe97 { quint8 byStart1; //启动字符 quint8 byA0; //地址域 每字节2位BCD码 quint8 byA1; //地址域 quint8 byA2; //地址域 quint8 byA3; //地址域 quint8 byA4; //地址域 quint8 byA5; //地址域 quint8 byStart2; //启动字符 quint8 bycontrol; //功能码 quint8 byDataLen; //数据域 quint8 byDI0; //数据标识 quint8 byDI1; //数据标识 quint8 byCS; //校验码 quint8 byEnd; //结束字符 S_Sendframe97() { memset(this, 0, sizeof (S_Sendframe97)); byStart1 = 0x68; byStart2 = 0x68; byEnd = 0x16; } }ALIGNS; void buildData(S_PtlVariant &sVariant) { quint8* pBySendBuffer = sVariant.pBySendBuffer + sVariant.uwSendDataLen; quint8* pSumCRC = pBySendBuffer; S_Sendframe07 sSendFrame07; S_Sendframe97 sSendFrame97; quint8 byA0 = 0; quint8 byA1 = 0; quint8 byA2 = 0; quint8 byA3 = 0; quint8 byA4 = 0; quint8 byA5 = 0; quint32 uiID = sVariant.pUnit->getTUID(); sVariant.pUnit->getValue(S_DataID(TABLE_TU, uiID, COL_TU_TUADDR0), &byA0); sVariant.pUnit->getValue(S_DataID(TABLE_TU, uiID, COL_TU_TUADDR1), &byA1); sVariant.pUnit->getValue(S_DataID(TABLE_TU, uiID, COL_TU_TUADDR2), &byA2); sVariant.pUnit->getValue(S_DataID(TABLE_TU, uiID, COL_TU_TUADDR3), &byA3); sVariant.pUnit->getValue(S_DataID(TABLE_TU, uiID, COL_TU_TUADDR4), &byA4); sVariant.pUnit->getValue(S_DataID(TABLE_TU, uiID, COL_TU_TUADDR5), &byA5); sSendFrame07.byA0 = valueToBCD(byA0); sSendFrame07.byA1 = valueToBCD(byA1); sSendFrame07.byA2 = valueToBCD(byA2); sSendFrame07.byA3 = valueToBCD(byA3); sSendFrame07.byA4 = valueToBCD(byA4); sSendFrame07.byA5 = valueToBCD(byA5); sSendFrame97.byA0 = valueToBCD(byA0); sSendFrame97.byA1 = valueToBCD(byA1); sSendFrame97.byA2 = valueToBCD(byA2); sSendFrame97.byA3 = valueToBCD(byA3); sSendFrame97.byA4 = valueToBCD(byA4); sSendFrame97.byA5 = valueToBCD(byA5); if(PTL645_VERSION_97 == sVariant.pPtlFlag->pSPtl645->pPtl645->byVersion) { if(sVariant.pPtlFlag->byQueryNum >= sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.size()) { sVariant.pPtlFlag->byQueryNum = 0; } sSendFrame97.bycontrol = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byCTRLCode; sSendFrame97.byDataLen = 2; /*sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byLength;*/ sSendFrame97.byDI0 = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byDI0 + 0x33; sSendFrame97.byDI1 = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byDI1 + 0x33; sVariant.pPtlFlag->byQueryNum++; memcpy(pSumCRC, &sSendFrame97, 12); sSendFrame97.byCS = getCheckCumlationSum(pSumCRC, 12); memcpy(pBySendBuffer, &sSendFrame97, sizeof(S_Sendframe97)); sVariant.uwSendDataLen += sizeof(S_Sendframe97); } else { if(sVariant.pPtlFlag->byQueryNum >= sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.size()) { sVariant.pPtlFlag->byQueryNum = 0; } sSendFrame07.bycontrol = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byCTRLCode; sSendFrame07.byDataLen = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byLength; sSendFrame07.byDI0 = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byDI0 + 0x33; sSendFrame07.byDI1 = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byDI1 + 0x33; sSendFrame07.byDI2 = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byDI2 + 0x33; sSendFrame07.byDI3 = sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum)->byDI3 + 0x33; sVariant.pPtlFlag->byQueryNum++; memcpy(pSumCRC, &sSendFrame07, 14); sSendFrame07.byCS = getCheckCumlationSum(pBySendBuffer, 14); memcpy(pBySendBuffer, &sSendFrame07, sizeof (S_Sendframe07)); sVariant.uwSendDataLen += sizeof (S_Sendframe07); } sVariant.pPtlFlag->bWaitDataReturn = 1; sVariant.pPtlFlag->t_LastSendTime = qGetCurrentTime();; }
组织好报文后,基于通道通讯类型,分别调用串口或网络的发送数据接口
switch(m_pChannel->byChannelCOMType) { case CHANNEL_TYPE_TCP_CLIENT: case CHANNEL_TYPE_TCP_SERVER: case CHANNEL_TYPE_UDP_CLIENT: case CHANNEL_TYPE_UDP_SERVER: m_SocketMutex.lock(); m_SPTLRecvSenddata.iSendedLen = m_pNetComm->sendData(m_pSocketHandle, m_SPTLRecvSenddata.pSendBuf, m_SPTLRecvSenddata.uiSendLen); m_SocketMutex.unlock(); if(m_SPTLRecvSenddata.iSendedLen > 0) { if(m_qsPtlLibName.contains("_zf")) { //ljx debug qMSleep(50); } else { // qMSleep(30); } } break; case CHANNEL_TYPE_COM_485: case CHANNEL_TYPE_COM_232: { m_SPTLRecvSenddata.iSendedLen = m_pSerial->sendData(m_SPTLRecvSenddata.pSendBuf,static_cast<int>(m_SPTLRecvSenddata.uiSendLen)); } break; case CHANNEL_TYPE_CAN: break; default: break; }
- 通讯装置表中的地址,确定报文帧的地址域
-
通道通信接收数据处理
基于通道类型,采用不同的处理方法将接收到的数据存放到接收缓存区中switch(m_pChannel->byChannelCOMType) //通道类型 { case CHANNEL_TYPE_TCP_CLIENT: case CHANNEL_TYPE_TCP_SERVER: case CHANNEL_TYPE_UDP_CLIENT: case CHANNEL_TYPE_UDP_SERVER: m_NewRecvedDataMutex.lock(); while(m_sNewRecvedDataList.size() > 0) { S_NewRecvedData& sNewRecvedData = m_sNewRecvedDataList.first(); quint32 uiFreeRecvBufLen = MAX_BUF_SIZE - m_SPTLRecvSenddata.uiRecvedLen; //剩余的接收缓冲区长度 if(uiFreeRecvBufLen >= sNewRecvedData.uiRecvedLen) //若剩余的缓冲区长度大于新接收的缓冲区长度,则新接收到的数据全部复制 { memcpy(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, sNewRecvedData.pRecvBuf, sNewRecvedData.uiRecvedLen); m_SPTLRecvSenddata.uiRecvedLen += sNewRecvedData.uiRecvedLen; m_sNewRecvedDataList.removeFirst(); } else if(uiFreeRecvBufLen > 0) //若剩余的接收缓冲区长度大于0,则只复制剩余的接收缓冲区长度 { memcpy(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, sNewRecvedData.pRecvBuf, uiFreeRecvBufLen); m_SPTLRecvSenddata.uiRecvedLen += uiFreeRecvBufLen; sNewRecvedData.uiRecvedLen -= uiFreeRecvBufLen; memmove(sNewRecvedData.pRecvBuf, sNewRecvedData.pRecvBuf + uiFreeRecvBufLen, sNewRecvedData.uiRecvedLen); break; } else { break; } } m_NewRecvedDataMutex.unlock(); break; case CHANNEL_TYPE_COM_485: case CHANNEL_TYPE_COM_232: m_uiMaxRecvLen = MAX_BUF_SIZE - m_SPTLRecvSenddata.uiRecvedLen; if(m_uiMaxRecvLen > 0) { m_SPTLRecvSenddata.iRecvedLen = m_pSerial->recvData(m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen, static_cast<int>(m_uiMaxRecvLen)); //从串口中接收数据 if(m_SPTLRecvSenddata.iRecvedLen > 0) { m_tRecvedTime = time(NULL); addCommMsg(true, static_cast<quint16>(m_SPTLRecvSenddata.iRecvedLen), m_SPTLRecvSenddata.pRecvBuf + m_SPTLRecvSenddata.uiRecvedLen); //添加通信报文 setUpCommFault(false); setDownCommFault(false); m_SPTLRecvSenddata.uiRecvedLen += static_cast<quint32>(m_SPTLRecvSenddata.iRecvedLen); } else if(m_SPTLRecvSenddata.iRecvedLen < 0) { setCommState(COMM_ERROR); } } break; case CHANNEL_TYPE_CAN: break; default: break; }
调用规约报文解析函数来解析报文
PTL_API bool parseData(S_RecvSendData &sRecvSendData, CUnitBase* pUnit) //解析报文 { S_PtlVariant sVariant; memset(&sVariant, 0, sizeof(S_PtlVariant)); if(pUnit != NULL) { sVariant.pUnit = pUnit; sVariant.pByReadBuffer = sRecvSendData.pRecvBuf; sVariant.uwRecvDataLen = sRecvSendData.uiRecvedLen; sVariant.bMainChannel = sRecvSendData.bMainChannel; sVariant.byChannelMode = sRecvSendData.byChannelMode; sVariant.byChannelIndex = sRecvSendData.byChannelIndex; memset(sVariant.pParsedCommMsg, 0, MAX_BUF_SIZE); sVariant.uwParsedCommMsgLen = 0; sVariant.pPtlFlag = (S_PtlFlag*)(pUnit->getTUFlagBuffer()); if(readFrame(sVariant)) //解析数据 { sRecvSendData.uiRecvedLen = sVariant.uwRecvDataLen; return true; } } sRecvSendData.uiRecvedLen = sVariant.uwRecvDataLen; return false; }
基于 控制码调用不同的解析函数。解析时,首先计算得到数据标识码,条件判断调用电度量、最大需量还是遥测量的解析函数
void ParseTUBackFrame(S_PtlVariant &sVariant) { if(NULL == sVariant.pPtlFlag->pSPtl645->pPtl645) { return; } int Ret = RECV_PROCRET_NO; //检测校验和 quint8 byCtlCode = sVariant.pByReadBuffer[8]; //控制码 quint8 byDataLen = sVariant.pByReadBuffer[9]; //数据长度 quint8 byCS = getCheckCumlationSum(sVariant.pByReadBuffer, byDataLen + 10); if(byCS != sVariant.pByReadBuffer[byDataLen + 10]) { return; } if(sVariant.pPtlFlag->pSPtl645->pPtl645->byVersion == PTL645_VERSION_07) { //获取数据标识码、检测数据标识 quint8 byDI0 = sVariant.pByReadBuffer[10] - 0x33; quint8 byDI1 = sVariant.pByReadBuffer[11] - 0x33; quint8 byDI2 = sVariant.pByReadBuffer[12] - 0x33; quint8 byDI3 = sVariant.pByReadBuffer[13] - 0x33; // for(int i = 0; i < sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.size(); ++i) // { if(byDI0 != sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum - 1 )->byDI0 || byDI1 != sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum - 1 )->byDI1 || byDI2 != sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum - 1 )->byDI2 || byDI3 != sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum - 1 )->byDI3) { return; } //检验控制字 if(0x91 == byCtlCode|| 0x92 == byCtlCode || 0xB1 == byCtlCode || 0xB2 == byCtlCode) { switch (byDI3) { case 0x00: // 电度量 ParseDDData(sVariant, sVariant.pByReadBuffer + FRAME_DATA_START, sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.at(sVariant.pPtlFlag->byQueryNum -1)); Ret = RECV_PROCRET_DDFRAME; break; case 0x01: //最大需量 ParseMDData(sVariant, sVariant.pByReadBuffer + FRAME_DATA_START, sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.at(sVariant.pPtlFlag->byQueryNum -1)); Ret = RECV_PROCRET_MAXDATA; break; case 0x02: //遥测量 ParseYCData(sVariant, sVariant.pByReadBuffer + FRAME_DATA_START, sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.at(sVariant.pPtlFlag->byQueryNum -1)); Ret = RECV_PROCRET_YCFRAME; break; default: Ret = RECV_PROCRET_NORMAL; break; } sVariant.pPtlFlag->uwSearchSyncTimes = 0; } else if (byCtlCode == 0xD1 || byCtlCode == 0xD2) //返回异常 { sVariant.pPtlFlag->uwSearchSyncTimes++; Ret = RECV_PROCRET_ABNORMAL; } Ret = RECV_PROCRET_NORMAL; // } } else if (PTL645_VERSION_97 == sVariant.pPtlFlag->pSPtl645->pPtl645->byVersion) { //获取数据标识码、检测数据标识 quint8 byDI0 = sVariant.pByReadBuffer[10] - 0x33; quint8 byDI1 = sVariant.pByReadBuffer[11] - 0x33; // for(int i = 0; i < sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.size(); ++i) // { if(byDI0 != sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum - 1)->byDI0 || byDI1 != sVariant.pPtlFlag->pSPtl645->pPtl645DICVec.at(sVariant.pPtlFlag->byQueryNum -1)->byDI1) { return; } //检验控制字 if(0x81 == byCtlCode || 0x82 == byCtlCode) { quint8 byCtl1 = byDI1 >> 4; switch (byCtl1) { case 0x09: //电度量 ParseDDData(sVariant, sVariant.pByReadBuffer + FRAME_DATA_START -2, sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.at(sVariant.pPtlFlag->byQueryNum -1)); Ret = RECV_PROCRET_DDFRAME; break; case 0x0a: ParseMDData(sVariant, sVariant.pByReadBuffer + FRAME_DATA_START - 2, sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.at(sVariant.pPtlFlag->byQueryNum -1)); Ret = RECV_PROCRET_MAXDATA; break; case 0x0b: // 遥测量 { if(0xb6 == byDI1) { ParseYCData(sVariant, sVariant.pByReadBuffer + FRAME_DATA_START - 2, sVariant.pPtlFlag->pSPtl645->pPtl645DataVec.at(sVariant.pPtlFlag->byQueryNum -1)); Ret = RECV_PROCRET_YCFRAME; } } break; default: // 对于其它类型的查询暂不处理 Ret = RECV_PROCRET_NORMAL; break; } sVariant.pPtlFlag->uwSearchSyncTimes = 0; } else if (byCtlCode == 0xc1 || byCtlCode == 0xc2) //返回异常 { sVariant.pPtlFlag->uwSearchSyncTimes++; Ret = RECV_PROCRET_ABNORMAL; } else { Ret = RECV_PROCRET_NORMAL; } } }
解析电度量帧
void ParseDDData(S_PtlVariant &sVariant, quint8 *pbyData, S_Ptl645Data *PInfoData) { quint64 ullDDData = 0; double dDDData = 0.0; quint8 byTempLength = 0; sVariant.pUnit->getValue(S_DataID(TABLE_PTL645DIC, PInfoData->uiDICID, COL_PTL645DIC_LENGTH), &byTempLength); ullDDData = ParseRecvData(pbyData, byTempLength); dDDData = ullDDData / 1.0; bool bIsCalc = false; sVariant.pUnit->getValue(S_DataIndex(TABLE_PREYM, PInfoData->uiDataIndex, COL_PREYM_ISCALC), &bIsCalc); if(!bIsCalc) { sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYM, PInfoData->uiDataIndex, COL_PREYM_YMVALUE),&dDDData); } }
解最需量帧
//解最大需量帧 void ParseMDData(S_PtlVariant &sVariant, quint8 *pbyData, S_Ptl645Data *PInfoData) { qDebug()<<"jinru jiexi zuida xliang"; quint64 ullDdData = 0; double fMDdata = 0.0; ullDdData = ParseRecvData(pbyData, 3); qDebug()<<"jirxiwancehng1111"; fMDdata = (float) (ullDdData / 1.0); bool bIsCalc = false; sVariant.pUnit->getValue(S_DataIndex(TABLE_PREYM, PInfoData->uiDataIndex, COL_PREYM_ISCALC), &bIsCalc); if(!bIsCalc) { qDebug()<<"22222222222222222222222"<<PInfoData->uiDataIndex; sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYM, PInfoData->uiDataIndex, COL_PREYM_YMVALUE), &fMDdata); qDebug()<<"3333333333333333"; } }
解析遥测帧
//解析遥测帧 void ParseYCData(S_PtlVariant &sVariant, quint8 *pbyData, S_Ptl645Data *PInfoData) { quint64 ullYCData = 0; double dYCData = 0.0; quint8 byTempLength = 0; sVariant.pUnit->getValue(S_DataID(TABLE_PTL645DIC, PInfoData->uiDICID, COL_PTL645DIC_LENGTH), &byTempLength); ullYCData = ParseRecvData(pbyData, byTempLength); dYCData = (double)(ullYCData / 1.0); bool bIsCalc = false; sVariant.pUnit->getValue(S_DataIndex(TABLE_PREYC, PInfoData->uiDataIndex, COL_PREYC_ISCALC), &bIsCalc); if(!bIsCalc) { sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, PInfoData->uiDataIndex, COL_PREYC_YCVALUE), &dYCData); } }
将解帧所得参变量的字符值转换成十进制原始值
//将解帧所得参变量的字符值转换成十进制原始值 quint64 ParseRecvData(quint8 *pbyData, int nlen) { quint64 ullTmpData = 0; quint8 byDataBuffer[256] = "0"; quint8 byTempData = 0; for(int i = 0; i < nlen; ++i) { byDataBuffer[i] = pbyData[i] - 0x33; byTempData = pbyData[i] - 0x33; } switch (nlen) { case 0x02: //每个信息占两个字节 ullTmpData = TwoBytesToUShort(byDataBuffer); break; case 0x03: //每个信息占三个字节 ullTmpData = ThreeByteToLong(byDataBuffer); break; case 0x04: //每个信息占4个字节 ullTmpData = FourBytesToLong(byDataBuffer); break; default: break; } return ullTmpData; }
参考文献:
- DLT645-97/07通信规约
- DLT645协议解析(二)—07协议数据帧结构解析