IEC60870-5-104通信规约 | 报文解析 | 组织报文与解析报文(C++)

文章目录

  • 一、IEC60870-5-104通信规约
    • 1.IEC104的报文结构
    • 2.IEC104的报文格式--I/U/S格式
      • 2.1 I帧
      • 2.2 U帧
      • 2.3 S帧
    • 3.应用服务数据单元ASDU
  • 二、IEC60870-5-104规约通信过程报文帧解析
  • 三、组织报文与解析报文(C++)

一、IEC60870-5-104通信规约

  IEC60870-5-104规约,简称IEC104,IEC104规约由国际电工委员会制定。IEC104规约把IEC101的应用服务数据单元(ASDU)用网络规约TCP/IP进行传输的标准,该标准为远动信息的网络传输提供了通信规约依据。采用104规约组合101规约的ASDU的方式后,可很好的保证规约的标准化和通信的可靠性。

TCP端口号为2404,站端为Server 控端为Client,平衡式传输

  IEC 104规约是一种电力自动化通信协议,通常采用的是平衡传输模式。在平衡传输模式下,所有站都可以启动报文传输,即主站和从站都可以独立地发起通信会话,进行数据的发送和接收。

平衡模式传输与非平衡模式传输

  • 平衡模式传输

    平衡方式传输是一种通信传输方式,它允许通信双方在没有规定谁先发起通信的情况下进行会话。在这种传输方式中,通信的两个实体都具备发起和接收信息的能力,从而实现双向通信。这种传输方式可以提高通信的灵活性和效率,因为它不依赖于固定的通信发起方。

  • 非平衡模式传输

    在非平衡传输模式下,主站可以顺序地召唤各被控站,而被控站只在被查询时才传输数据。这种模式适用于点对点、星形、多点等多种配置

  IEC 60870-5-104 标准中的平衡模式传输过程允许通信双方在没有规定谁先发起通信的情况下进行会话,即主站和从站都可以独立地发起通信会话。以下是平衡模式传输过程的一般步骤

  1. 建立连接:通信双方建立一个稳定的通信连接。在 TCP/IP 环境中,这涉及到建立一个 TCP 连接。
  2. 初始化:在开始数据交换之前,双方可能需要进行初始化过程,以确保它们都处于正确的工作状态。
  3. 数据传输:在平衡模式下,主站和从站都可以独立地发送数据。数据可以是请求、命令、状态更新或其他类型的信息。
  4. 确认和响应:接收方收到数据后,会发送确认信息或相应的响应。这确保了数据的可靠传输。
  5. 事件触发:在平衡模式下,任何站都可以独立地触发事件,例如,当检测到特定条件或异常时,从站可以主动向主站发送事件通知。
  6. 周期性更新:被控站可以周期性地向主站发送过程变量的最新值,以保持数据的时效性。
  7. 总召唤:控制站可以使用总召唤命令请求从站发送所有过程变量的实际值,以刷新控制站的数据。
  8. 时钟同步:为了确保时间一致性,控制站可以发送时钟同步命令给被控站,以同步它们的时钟。
  9. 错误处理:如果在传输过程中出现错误,比如数据包丢失或损坏,双方都可以采取措施进行重传或错误纠正。
  10. 通信结束:当通信双方完成数据交换后,可以结束通信会话。这通常涉及到关闭 TCP 连接。

1.IEC104的报文结构

  IEC 60870-5-104 协议的报文结构为应用规约数据单元(APDU)。主要包括两个部分,分别是应用规约控制信息(APCI)、应用服务数据单元(ASDU)。
在这里插入图片描述

  • APCI应用规约控制信息:它是所有发送、接收的报文头并且可以单独发送的。

  • ASDU 应用服务数据单元:

    • ASDU 是报文的有效载荷部分,包含了实际传输的数据,如遥信、遥测、遥控等信息。
    • ASDU 由类型标识、可变结构限定词、传送原因、ASDU 公共地址、信息对象地址等组成。
    • ASDU 的结构和内容取决于传输的数据类型和上下文。
  • APDU 是由 APCI 和 ASDU 组成的完整报文,是 IEC 60870-5-104 协议中传输的基本单元。

    APDU 应用规约数据单元(整个数据) = APCI 应用规约控制信息(固定6个字节) + ASDU 应用服务数据单元(长度可变)

  • APDU的长度域定义了APDU体的长度,它包括APCI的四个控制域八位位组和ASDU(ASDU长度加4)。APDU应用规约数据单元长度最大253(255减去启动符与本身)

  • 控制域定义了确保报文不丢失和重复传送的控制信息(也就是发送序列号和接收序列号),报文传输启动/停止,以及传输连接的监视等。

2.IEC104的报文格式–I/U/S格式

  控制域对应不同类型的格式(I帧、U帧、S帧),意义和格式都不相同。
在这里插入图片描述

2.1 I帧

I格式(信息传输格式类型—Information transmit format):

  I格式报文用于传递信息,包含了应用服务数据单元(ASDU)。==I帧为信息帧,用于传输具体的通信数据。==遥信、遥测、遥控、遥调、总召、对时等都需要使用I格式传送。I帧用于传输含有信息体的报文和确认对方I格式的信息报文

I帧的基本格式如下:

  • 启动字符:固定为 68H,用于标识报文的开始。

  • APDU的后续长度:这个长度是变长的

  • 控制域:

    I帧报文控制域格式如下:

    img

    • 包含ASDU

      后续会对应用服务数据单元ASDU进行详细分析。

2.2 U帧

U格式(不计数的控制功能类型—Unnumbered control function):

  U格式报文用于数据传输的过程控制主站实现子站进行数据传输(STARTPDT),停止子站的数据传输(STOPDT),和TCP链路测试(TESTER),它不包含ASDU(应用服务数据单元)。在同一时刻TESTFR、STOPDT、STARTDT中只能有一个功能可以被激活。U帧为控制帧,用于控制启动、停止和测试U帧是用于传输链路控制命令的报文

U帧的基本格式如下:

  • 启动字符:固定为 68H,用于标识报文的开始。

  • APDU的后续长度:这个长度通常是固定的,为4个字节。

  • 控制域:

    U帧报文控制域格式如下:

    img

    控制域1的六种情形:

    U格式报文控制域语义
    68 04 07 00 00 0000000111启动命令
    68 04 0B 00 00 0000001011启动确认
    68 04 13 00 00 0000010011停止命令
    68 04 23 00 00 0000100011停止确认
    68 04 43 00 00 0001000011测试命令
    68 04 83 00 00 0010000011测试确认

    控制域2~4为00 00 00

  • 不包含ASDU

U帧的主要用途:

  • 连接的建立和断开:U帧用于在通信开始前进行握手,建立连接,并在通信结束后进行断开。它可以用来发送连接请求、确认连接、发送心跳信号、请求断开连接等

  • 测试链路连通性:U帧还用于测试链路的连通性。在一段时间内如果没有数据传输,主站或子站可以发送测试U帧(TESTFR)来检查对方是否仍然在线,并期待收到测试确认U帧(TESTFR ACK)作为响应

  • 维护链路活动状态:当双方都没有数据发送时,U帧可以用来维持TCP连接的活动状态,防止因长时间无活动而导致的连接断开

U帧报文解析(举例):

当首次建立通信连接时,需要使用U帧来启动传输

  • 传输启动命令

    启动字符APDU后续长度控制域
    680407 00 00 00
  • 传输启动确认(响应)

    启动字符APDU后续长度控制域
    68040B 00 00 00

在数据传输过程中,为了确保链路的连通性,通信双方可以周期性地发送测试U帧。或者如果主站超过一定时间没有下发报文或者RTU也没有上送任何报文,双方都可以按频率发送U帧,测试帧(U帧)

  • 测试命令

    启动字符APDU后续长度控制域
    680443 00 00 00
  • 测试确认(响应)

    启动字符APDU后续长度控制域
    680483 00 00 00

当要断开通信连接时,需要使用U帧来停止传输

  • 停止命令

    启动字符APDU后续长度控制域
    680413 00 00 00
  • 停止确认(响应)

    启动字符APDU后续长度控制域
    680423 00 00 00

2.3 S帧

S格式(计数的监视功能类型—Numbered supervisory functions):

  S帧用于当没有I格式报文回应的情况下,回应确认报文的接收,它不包含ASDU(应用服务数据单元)。S帧为监视帧,主要作用是向发送方确认已经成功接收并处理了特定的I帧S帧传送的没有具体的信息内容,是用来对站端所发信息报文的确认。S帧的主要作用是对I帧进行确认,它不包含任何实际的数据负载,仅用于确保数据传输的可靠性和顺序性。通过S帧,接收方可以告知发送方已经成功接收到特定序列号的I帧,从而允许发送方进行流量控制和错误恢复。

S帧的基本格式如下:

  • 启动字符:固定为 68H,用于标识报文的开始。

  • APDU后续长度:这个长度通常是固定的,为4个字节。

  • 控制域:

    S帧报文控制域格式如下:

    img

    控制域1与控制域2:固定为01 00

    控制域3与控制域4 (接收序列号):已接收的I格式报文的发送序列号 + 2

  • 不包含ASDU

S帧的主要用途:

  • 确认接收:接收方通过发送S帧来确认已经成功接收并处理了发送方的I帧。
  • 流量控制:通过调整发送S帧的频率,接收方可以对发送方的数据流量进行控制,防止因接收方处理能力有限而导致的数据溢出。
  • 错误检测:如果发送方发现接收方的S帧中确认的序列号与预期不符,可以采取重传等措施。

S帧的使用策略可以根据实际的通信需求和系统设计来定制。例如:

  • 固定频率确认:无论接收到多少I帧,接收方都按照固定频率发送S帧。比如接收8帧I帧回答一帧S帧
  • 按需确认:接收方只有在接收到特定数量的I帧后,才发送一个S帧进行确认。
  • 即时确认:接收方在接收到每一个I帧后都立即发送S帧进行确认。

S帧报文解析(举例):

  • 已接收的I

    启动字符APDU后续长度控制域1~2(发送序列号)控制域3~4(接收序列号)ASDU
    68FA6C 6784 00...
  • 确认的S帧

    启动字符APDU后续长度控制域1~2控制域3~4(接收序列号)
    680401 00 6E 67

    已接收的I格式报文的发送序列号 + 2 = 0x676C+ 2 = 0x676E,表明接收方已经成功接收并处理了发送方的I帧。

3.应用服务数据单元ASDU

  应用服务数据单元(ASDU)是IEC 60870-5-104协议中用于传输具体应用数据的单元。ASDU是I帧(Information Frame)的有效载荷部分,包含了实际传输的数据,如遥信、遥测、遥控、遥调、总召、对时等信息。

其结构可以简化为:

类型标识可变结构限定词传送原因ASDU公共地址信息体地址数据和品质描述
1个字节1个字节2个字节2个字节3个字节变长
  • 类型标识(1个字节)

    标识出后面的信息体的数据类型,不同的类型标识对应不同的数据结构和意义。

    完整的类型标识与每一种类型标识特点如下:

    类型标识特点

    监视方向过程信息

    • 标度化值与归一化值占2个字节,短浮点数占4个字节
    • 一般为从站发送给主站

    控制方向过程信息

    • 标度化值与归一化值占2个字节,短浮点数占4个字节
    • 一般为主站发送给从站

    监视方向系统信息

    • 当厂站(从站)在重新上电、初始化参数、重新分配缓存区等情况下,厂站需要给主站发送该类型,而主站收到该类型的APDU包,主站一般会做一次总召唤;
    • 一般为从站发送给主站

    控制方向的系统信息

    • 一般为主站发送给从站

    常用的类型标识

    image-20240521163411555

    完整的类型标识

    在监视方向的过程信息(上行)

    <1> 0x01:M-SP-NA-1 =单点信息 (总召唤遥信、变位遥信)

    <2> 0x02:M-SP-TA-1 =带时标单点信息 (SOE事项)

    <3> 0x03:M-DP-TA-1 =双点信息 (遥信)

    <4> 0x04:M-DP-TA-1 =带时标双点信息

    <5> 0x05:M-ST-NA-1 =步位置信息

    <6> 0x06:M-ST-TA-1 =带时标步位置信息

    <7> 0x07:M-BO-NA-1 =32比特串

    <8> 0x08:M-BO-TA-1 =带时标32比特串

    <9> 0x09:M-ME-NA-1 =测量值,归一化值 (遥测)

    <10> 0x0A:M-ME-TA-1 =测量值,带时标归一化值

    <11> 0x0B:M-ME-NB-1 =测量值,标度化值 (遥测)

    <12> 0x0C:M-ME-TB-1 =测量值,带时标标度化值

    <13> 0x0D:M-ME-NC-1 =测量值,短浮点数(遥测)

    <14> 0x0E:M-ME-TC-1 =测量值,带时标短浮点数

    <15> 0x0F:M-IT-NA-1 =累计量 (电度量)

    <16> 0x10:M-IT-TA-1 =带时标累计量

    <17> 0x11:M-EP-TA-1 =带时标继电保护装置事件

    <18> 0x12:M-EP-TB-1 =带时标继电保护装置成组启动事件

    <19> 0x13:M-EP-TC-1 =带时标继电保护装置成组输出电路信息

    <20> 0x14:M-SP-NA-1 =具有状态变位检出的成组单点信息

    <21> 0x15:M-ME-ND-1 =测量值,不带品质描述的归一化值 (总召唤遥测量)

    <30> 0x1E:M-SP-TB-1 =带时标CP56TimE2A的单点信息(遥信带时标)

    <31> 0x1F:M-DP-TB-1 =带时标CP56TimE2A的双点信息(遥信带时标)

    <32> 0x20:M-ST-TB-1 =带时标CP56TimE2A的步位信息

    <33> 0x21:M-BO-TB-1 =带时标CP56TimE2A的32位串

    <34> 0x22:M-ME-TD-1 =带时标CP56TimE2A的归一化测量值

    <35> 0x23:M-ME-TE-1 =测量值,带时标CP56TimE2A的标度化值

    <36> 0x24:M-ME-TF-1 =测量值,带时标CP56TimE2A的短浮点数

    <37> 0x25:M-IT-TB-1 =带时标CP56TimE2A的累计值

    <38> 0x26:M-EP-TD-1 =带时标CP56TimE2A的继电保护装置事件

    <39> 0x27:M-EP-TE-1 =带时标CP56TimE2A的成组继电保护装置成组启动事件

    <40> 0x28:M-EP-TF-1 =带时标CP56TimE2A的继电保护装置成组输出电路信息

    <41~44> 为将来的兼容定义保留

    在控制方向的过程信息(下行)

    <45> 0x2D:C-SC-NA-1 =单命令 (遥控)

    <46> 0x2E:C-DC-NA-1 =双命令 (遥控)

    <47> 0x2F:C-RC-NA-1 =升降命令

    <48> 0x30:C-SE-NA-1 =设定值命令,归一化值 (遥调)

    <49> 0x31:C-SE-NB-1 =设定值命令,标度化值

    <50> 0x32:C-SE-NC-1 =设定值命令,短浮点数

    <51> 0x33:C-BO-NA-1 =32比特串

    <52~57> 为将来的兼容定义保留

    <58> 0x3A:C-SC-TA-1 =带时标CP56TimE2A的单命令

    <59> 0x3B:C-DC-TA-1 =带时标CP56TimE2A的双命令

    <60> 0x3C:C-RC-TA-1 =带时标CP56TimE2A的升降命令

    <61> 0x3D:C-SE-TA-1 =带时标CP56TimE2A的设定值命令,归一化值

    <62> 0x3E:C-SE-TB-1 =带时标CP56TimE2A的设定值命令,标度化值

    <63> 0x3F:C-SE-TC-1 =带时标CP56TimE2A的设定值命令,短浮点数

    <64> 0x40:C-BO-TA-1 =带时标CP56TimE2A的32比特串

    <65~69> 为将来的兼容定义保留

    在监视方向的系统信息(上行)

    <70> 046x:M-EI-NA-1 =初始化结束

    <71~99> 为将来的兼容定义保留

    在控制方向的系统信息(下行)

    <100> 0x64:C-IC-NA-1 =总召唤命令 (总召唤)

    <101> 0x65:C-CI-NA-1 =电能脉冲召唤命令 (召唤电度量)

    <102> 0x66:C-RD-NA-1 =读单个参数命令 (参数设置)

    <103> 0x67:C-CS-NA-1 =时钟同步命令 (校时)

    <104>0x68:C-TS-NA-1 =测试命令

    <105> 0x69:C-RP-NA-1 =复位进程命令

    <106> 0x6A:C-CD-NA-1 =延时传输命令

    <107> 0x6B:C-TS-TA-1 =带时标CP56TimE2A的测试命令

    <108~109> 为将来的兼容定义保留

    在控制方向的参数命令(下行)

    <110> 0x6E:P-ME-NA-1 =测量值参数,归一化值

    <111> 0x6F:P-ME-NB-1 =测量值参数,标度化值

    <112> 0x70:P-ME-NC-1 =测量值参数,短浮点数

    <113> 0x71:P-AC-NA-1 =参数激活

    <114~119> 为将来的兼容定义保留

    文件传输

    <120> 0x78:F-FR-NA-1 =文件准备好

    <121> 0x79:F-SR-NA-1 =节已准备好

    <122> 0x7A:F-SC-NA-1 =召唤目录,选择文件,召唤文件,召唤节

    <123> 0x7B:F-LS-NA-1 =最后的节,最后的度

    <124> 0x7C:F-AF-NA-1 =确认文件,确认节

    <125> 0x7D:F-SG-NA-1 =段

    <126> 0x7E:F-DR-TA-1 =目录{空白或×,只在监视(标准)方向有效}

  • 可变结构限定词(1个字节)

    bit7bit6~bit0
    SQ信息对象数目

    SQ控制信息对象地址是否连续

    • SQ=1表示连续,即当有多个信息对象时,可以依据信息体地址推导出所有信息对象的地址(只有第一个信息对象有地址,其他对象的地址就是累加1)
    • SQ=0表示不连续,即每个信息对象必须给出其地址

    总召唤时,为了压缩信息传输时间SQ=1;而在从站主动上传变化数据时,因为地址不连续,采用SQ=0

    信息对象数目:如:召唤的遥测、遥信等点位的数目,最多为127个

  • 传送原因(2个字节)

    bit7bit6bit5~bit0bit7~bit0
    TP/N传送原因源发地址

    传输原因可以是一个或两个字节,根据需要可以选择带或不带源发地址。

    • TT=0未实验,T=1实验(一般T=0)

    • P/NP/N=0肯定确认,P/N=1否定确认(正常为P/N=0;P/N=1 说明该报文无效)

    • 源发地址:用来表明来自哪个主站的召唤,一般情况下不适用。规定源发地址不使用时,应置零。

    • 传送原因:

      十进制十六进制cause方向
      10X01周期、循环(遥测)上行(从站->主站)
      20x02背景扫描(遥信)(遥测)上行
      30x03突发信息(遥信)(遥测)上行
      40x04初始化上行
      50x05请求、被请求(遥信被请求)(遥测被请求)上行、下行
      60x06激活(遥控、参数设置 控制方向)下行(主站->从站)
      70x07激活确认(遥控、参数设置 监视方向)上行
      80x08停止激活(遥控、参数设置 控制方向)下行
      90x09停止激活确认 (遥控、参数设置 监视方向)上行
      100x0A激活终止(遥控 监视方向)上行
      200x14响应站总召唤(遥信响应总召唤)(遥测响应总召唤)上行
      21-360x15-0x24响应第1组召唤-响应第16组召唤上行
      370x25响应计数量召唤上行
      38-410x26-0x29响应第1组计数量-响应第4组计数量上行
      440x2c未知的类型标识(遥控、参数设置 监视方向)上行
      450x2d未知的传送原因(遥控、参数设置 监视方向)上行
      460x2e未知的应用服务数据单元公共地址(遥控、参数设置 监视方向)上行
      470x2f未知的信息对象地址(遥控、参数设置 监视方向)上行
      480x30遥控执行软压板状态错误上行
      490x31遥控执行时间戳错误上行
      500x32遥控执行数字签名认证错误上行
  • ASDU公共地址–厂站地址(2个字节)

    一般为厂站地址,每一个配电终端对应唯一的一个值。规定:高位字节为0x00,低位字节中 ,1-254 为站地址;255为全局地址。

  • 信息体地址(3个字节)

    image-20240522094011637

    • 总召唤,信息地址为00 00 00

      主站向子站发送的总召唤命令帧、子站向总站发送的总召唤确认帧、子站信息传送完毕向主站发送的总召唤结束帧,这三个帧的信息体地址均为00 00 00。而这个过程中子站向主站发送的全遥测、全遥信等的信息体地址为后续遥测/遥信数据的起始地址,如:01 4C 00。每个遥测/遥信地址在该地址的基础上依次加1。

    • 时钟同步,信息体地址为00 00 00

      主站向子站发送的对时报文帧、主站收到来自子站的对时返回帧,这两个帧的信息体地址均为00 00 00

    • 复位进程,信息体地址为00 00 00

    • 初始化,信息体地址为00 00 00

    • 如果RTU有变化遥测数据时,主动上传,主站收到的变化遥测报文帧中的信息体地址为变化遥测对应的设备点位地址,如:01 4C 00

  • 数据与品质描述(变长)

    • 数据(变长,数据类型由前面的类型标识所决定)

      举例信息体的情形:

      连续信息传输

      • 带时标CP56TimE2A的遥测

        image-20240522134534134

      • 不带时标CP56TimE2A的遥测

        image-20240522134615738

      • 带时标CP56TimE2A的遥信

        image-20240522134749364

      • 不带时标CP56TimE2A的遥信

        image-20240522134819124

      非连续信息传输型

      • 带时标CP56TimE2A的遥测

        image-20240522134916095

      • 不带时标CP56TimE2A的遥测

        image-20240522134944924

      • 带时标CP56TimE2A的遥信

        image-20240522135031300

      • 不带时标CP56TimE2A的遥信

        image-20240522135056480

      遥控

      • 单点遥控

        image-20240522135154975

        单点遥控信息;

        image-20240522135225825

        • S/E = 0 遥控执行命令;S/E=1 遥控选择命令;
        • QU 被控站内部确定遥控输出方式
          • 1 短脉冲方式输出
          • 2 长脉冲方式输出
          • 3 持续脉冲方式输出
        • RES :保留位
        • SCS : 设置值; 0 = 控开 ;1 = 控合
      • 双点遥控

        image-20240522135507294

        双点遥控信息:

        image-20240522135542500

        • S/E = 0 遥控执行命令;S/E=1 遥控选择命令
        • QU 被控站内部确定遥控输出方式
          • 1 短脉冲方式输出
          • 2 长脉冲方式输出
          • 3 持续脉冲方式输出
          • 其他值没有定义
        • DCS 控制
          • 1 控分
          • 2 控合
          • 3 无效控制

      设定值(遥测):

      image-20240522135947934

      QOS:设定命令限定词

      image-20240522140037246

      • S/E : 0 设定执行;1 设定选择
      • 设定命令限定词: 基本就是 0 ,因为其他并没有定义

      这里需要关注的几个点:

      • 归一化值、标度化值、短浮点数对遥测信息体数据的长度的影响

        • 归一化值(占2个字节)

        • 标度化值(占2个字节)

        • 短浮点数(占4个字节)

      • 遥信数据用一个字节表示

      • 带时标CP56TimE2A与不带时标对数据的影响

        image-20240522130931740

        带时标CP56TimE2A的数据,

        • 如果是遥测,则会在重复信息体数据+品质描述词,并在最后加7个字节的时标
        • 如果是遥信,则信息体数据与品质描述词会合在一个字节中,再在最后加7个字节的时标
      • 连续数据上送(SQ=1)与变量数据上送(SQ=0)对数据的影响

        • SQ=1,连续数据上送,如:上送全遥测/全遥信报文,此时的数据部分不包含每个信息对象的地址,只要有前一个字段“信息体地址”就可以。例如:

          image-20240522132430353

        • SQ=0,离散数据上送,如:上送变化遥测/变化遥信报文,此时的数据部分会包含每个信息对象的地址,例如:

          image-20240522132633671

    • 品质描述词(1个字节)

      分为遥信品质描述词与遥测品质描述词

      image-20240522105418789

      • IV(有效标志):IV = 0 状态有效;IV = 1 状态无效;

        若值被正确采集就是有效,在采集功能确认了信息源的反常状态(例如:装置的启动过程中或者一些配置错误),那么值就是无效的。信息对象的值在这些条件下没有被定义。标上无效用以提醒使用者,此值不正确而不能使用。

      • NT(刷新标志):NT=0 刷新成功;NT=1 刷新未成功;

        若最近的刷新成功则值就称为当前值,若一个指定的时间间隔内刷新不成功或者其值不可用,值就称为非当前值。设备处于调试态或装置通讯中断都有可能造成非当前值。、

      • SB(取代标志位):SB=0 未被取代;SB = 1 被取代;

        当信息对象的值由值班员(调度员)输入(即人工置数)或者由当地自动原因(模拟遥信)所提供时,即为被取代。

      • BL(封锁标志位):BL=0 未被封锁;BL=1 封锁;

        信息对象的值为传输而被封锁,值保持封锁前被采集的状态。封锁和解锁可以由当地联锁机构或当地自动原因启动。

      • RES(保留位)

      • SPI(遥信状态位)

        单点遥信,0=开;1=合,占一个bit位。

        双点遥信,1=开,2=合,0和3为中间状态,占两个bit位。

      • OV(溢出标志):OV=0 未溢出;OV=1 遥测超出量程,发生溢出

        信息对象的值超出了预先定义值的范围(主要适用模拟量值)。仅在遥测品质结构词中出现。

二、IEC60870-5-104规约通信过程报文帧解析

  要想理解IEC60870-5-104规约通信过程,需要先了解多种时间间隔,这些时间间隔用于确保数据的完整性、系统的同步以及通信链路的监控。

  • 总召唤周期(遥测、遥信)

    控制站(主站)向所有受控站(从站)发送总召唤命令的周期,总召唤可以确保控制站获得所有相关设备的当前状态信息。

  • 站对时周期

    站对时周期是指控制站与受控站之间进行时钟同步的频率。这个周期确保了系统中所有设备的时间同步。

  • 电度召唤时间间隔

    召唤电度周期是指控制站请求受控站发送电度量(如电能表读数)信息的时间间隔。

  • 测试间隔

    测试间隔是指控制站或受控站发送测试帧(如TESTFR U帧)以检测和维护通信链路状态的时间间隔。如果在指定的测试间隔内没有收到对方的响应,可能表明通信链路存在问题。

主从站间的通信过程

传输启动过程

  • 主站向从站发送启动命令(STARTDT U帧),来启动数据传输

    报文帧:68 04 07 00 00 00

    解析:68(启动符) 04(长度4) 07(控制域0000 0111) 00 00 00

  • 主站收到来自从站的启动确认

    报文帧:68 04 0B 00 00 00

    解析:68(启动符) 04(长度4)0B(控制域0000 1011) 00 00 00

总召唤过程:总召唤功能是在初始化以后进行,或者是定期进行总召唤

  • 主站向从站发送的总召唤命令帧,会设置总召唤周期如:5分钟,来定时发送总召唤命令帧。总召唤的内容包括子站的遥测、遥信等。

    报文帧:68 0E 00 00 00 00 64 01 06 00 01 00 00 00 00 14

    解析:68(启动符) 0E(长度14) 00 00(发送序号) 00 00(接受序号) 64(类型标示召唤全部数据) 01(可变结构限定词 sq=0:离散的信息报告) 06 00 (传输原因 0006:激活)01 00 (公共地址即RTU地址)00 00 00(信息体地址) 14(区分是总召唤还是分组召唤,02年修改后的规约中没有分组召唤)

  • 子站收到后,如果确认,则主站会收到来自子站的总召唤确认帧;如果否认,则子站会送否定确认

    报文帧:68 0E 00 00 02 00 64 01 07 00 01 00 00 00 00 14

    解析:68(启动符) 0E(长度14) 00 00(发送序号) 02 00(接受序号) 64(类型标示召唤全部数据) 01(可变结构限定词 sq=0:离散的信息报告)07 00(传输原因 0007:激活确认)01 00(公共地址即RTU地址) 00 00 00 (信息体地址) 14(区分是总召唤还是分组召唤,02年修改后的规约中没有分组召唤)

  • 子站连续地向主站传送数据,包括遥测 、遥信等

    主站收到的全遥测报文

    报文帧:68 F3 02 00 02 00 0D AE 14 00 01 00 01 4C 00 第1点遥测的4字节浮点数值 第1点遥测的品质描述 第2点遥测的4字节浮点数值 第2点遥测的品质描述 ……

    解析:68(启动符) F3(长度243)02 00(发送序号)02 00 (接受序号)0D (带品质描述的浮点值,每个遥测值占5个字节)AE(可变结构限定词10101110 SQ=1:表示顺序的信息报文;101110=46:表示后面有46个遥测) 14 00(0014:响应总召唤) 01 00 (公共地址即RTU地址)01 4C 00 (3字节的第1点遥测信息体地址,每个遥测地址在该地址的基础上依次加1)

    9A 99 41 41 00 (yc1)

    34 33 97 41 00 (yc2)

    67 66 08 C2 00 (yc3)

    33 33 03 42 00 (yc4)

    2E 33 23 41 00 (yc5)

    67 66 92 C1 00 (yc6)

    66 66 AA C1 00(yc7)

    9A 99 19 B6 00(yc8)

    9A 99 11 C1 00(yc9)

    36 33 29 42 00(yc10)

    9A 19 47 43 00 (yc40)

    CE 4C 78 C3 00 (yc41)

    00 00 C9 42 00(yc42)

    35 33 7D C3 00 (yc43)

    9A 99 AE 42 00 (yc44)

    CA 2C 4B 44 00(yc45)

    CD CC 8C 36 00 (yc46)

    主站收到的全遥信报文

    报文帧:68 71 04 00 02 00 01 E4 14 00 01 00 01 00 00 01 00 00 01 00 00 01 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 01 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 01 00 00 01 00 00 00 01 00 00 00 00 00 01 00 00 00 00 00 00 00 0100 00 00 00 00 00 00 00 00 01

    解析:68(启动符) F3(长度243) 04 00(发送序号) 02 00(接受序号) 01(不带时标的单点遥信) E4可变结构限定词11100100 SQ=1:表示顺序的信息报文;1100100=100:表示后面有100个遥信 14 00(0014:相应总召唤) 01 00(公共地址即RTU地址) 01 00 00(3字节的第1点遥信信息体地址,每个遥信地址在该地址的基础上依次加1)

    01 (yx1)

    00 (yx2)

    00 01 00 00 01 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 01 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 01 00 00 01 00 00 00 01 00 00 00 00 00 01 00 00 00 00 00 00 00 0100 00 00 00 00 00 00 00 00 01

  • 子站信息传送完毕后,发送总召唤结束帧

    报文帧:68 0E 06 00 02 00 64 01 0A 00 01 00 00 00 00 14

    解析:68(启动符) 0E(长度14) 0C 00 (发送序号)02 00 (接受序号)64 (类型标示召唤全部数据)01(可变结构限定词 sq=0:离散的信息报告) 0A 00 (传输原因 000A:激活结束)01 00(公共地址即RTU地址) 00 00 00 (信息体地址)14(区分是总召唤还是分组召唤,02年修改后的规约中没有分组召唤)

召唤电度量(电能脉冲)过程

  • 主站向从站发送电度召唤命令,会设置电度召唤间隔,如:10分钟。召唤电度量的内容为电能脉冲数据

    报文帧:68 0E 02 00 00 00 65 01 06 00 01 00 00 00 00 05

    解析:68(启动符) 0E(长度14) 02 00(发送序号) 00 00(接受序号) 65(类型标识:召唤电度量) 01(可变结构限定词 sq=0:离散的信息报告) 06 00(传输原因 0006:激活) 01 00(公共地址即RTU地址) 00 00 00(信息体地址) 05

  • 子站收到后,如果确认,则主站会收到来自子站的召唤电度量确认帧;如果否认,则子站会送否定确认

    报文帧:68 0E 08 00 04 00 65 01 07 00 01 00 00 00 00 05

    解析:68(启动符) 0E(长度14) 08 00(发送序号) 04 00(接受序号) 65(类型标识:召唤电度量) 01(可变结构限定词 sq=0:离散的信息报告) 07 00(传输原因 0007:激活确认) 01 00(公共地址即RTU地址) 00 00 00(信息体地址) 05

  • 子站连续地向主站传送电能量数据

    报文帧:68 FD 0A 00 04 00 0F B0 25 00 01 00 01 64 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 02 00 00 00 00 03 00 00 00 00 04 00 00 00 00 05 00 00 00 00 06 00 00 00 00 07 00 00 00 00 08 00 00 00 00 09 00 00 00 00 0A 00 00 00 00 0B 00 00 00 00 0C 00 00 00 00 0D 00 00 00 00 0E 00 00 00 00 0F 00 00 00 00 10 00 00 00 00 11 00 00 00 00 12 00 00 00 00 13 00 00 00 00 14 00 00 00 00 15 00 00 00 00 16 00 00 00 00 17 00 00 00 00 18 00 00 00 00 19 00 00 00 00 1A 00 00 00 00 1B 00 00 00 00 1C 00 00 00 00 1D 00 00 00 00 1E 00 00 00 00 1F 00 00 00 00 20 00 00 00 00 21 00 00 00 00 22 00 00 00 00 23 00 00 00 00 24 00 00 00 00 25 00 00 00 00 26 00 00 00 00 27 00 00 00 00 28 00 00 00 00 29 00 00 00 00 2A 00 00 00 00 2B 00 00 00 00 2C 00 00 00 00 2D 00 00 00 00 2E 00 00 00 00 2F

    解析:68(启动符) FD(长度253) 0A 00(发送序号) 04 00(接受序号) 0F(累计量–电度量,每个占5个字节) B0(可变结构限定词10110000 SQ=1:表示顺序的信息报文;110000=48:表示后面有48个电度量) 25 00(0025:响应计数量召唤) 01 00(公共地址即RTU地址) 01 64 00(3字节的第1点电度量信息体地址,每个电度量地址在该地址的基础上依次加1)

    00 00 00 00 00 00 00 00 00 01 00 00 00 00 02 00 00 00 00 03 00 00 00 00 04 00 00 00 00 05 00 00 00 00 06 00 00 00 00 07 00 00 00 00 08 00 00 00 00 09 00 00 00 00 0A 00 00 00 00 0B 00 00 00 00 0C 00 00 00 00 0D 00 00 00 00 0E 00 00 00 00 0F 00 00 00 00 10 00 00 00 00 11 00 00 00 00 12 00 00 00 00 13 00 00 00 00 14 00 00 00 00 15 00 00 00 00 16 00 00 00 00 17 00 00 00 00 18 00 00 00 00 19 00 00 00 00 1A 00 00 00 00 1B 00 00 00 00 1C 00 00 00 00 1D 00 00 00 00 1E 00 00 00 00 1F 00 00 00 00 20 00 00 00 00 21 00 00 00 00 22 00 00 00 00 23 00 00 00 00 24 00 00 00 00 25 00 00 00 00 26 00 00 00 00 27 00 00 00 00 28 00 00 00 00 29 00 00 00 00 2A 00 00 00 00 2B 00 00 00 00 2C 00 00 00 00 2D 00 00 00 00 2E 00 00 00 00 2F

  • 子站信息传送完毕后,发送召唤电度量结束帧

    报文帧:68 0E 10 00 04 00 65 01 0A 00 01 00 00 00 00 05

    解析:68(启动符) 0E(长度14) 10 00(发送序号) 04 00(接受序号) 65(类型标识:召唤电度量) 01(可变结构限定词 sq=0:离散的信息报告) 0A 00(传输原因 000A:激活结束) 01 00(公共地址即RTU地址) 00 00 00(信息体地址) 05

站对时过程

  • 主站向从站发送对时报文,会设置站对时间隔,如:20分钟。

    报文帧:68 14 02 00 0E 00 67 01 06 00 01 00 00 00 00 8E 6D 2C 0B 2F 0B 0A

    解析:68(启动符)14(长度20) 02 00 (发送序号)0E 00 (接受序号)67 (类型标示:67时钟同步)01 (可变结构限定词 sq=0:离散的信息报告)06 00 (传输原因 0006:激活)01 00(公共地址即RTU地址)

    00 00 00 (信息体地址)

    8E(毫秒低位) 6D(毫秒高位)

    2C(分钟)

    0B(时)

    2F(日与星期)

    0B (月)

    0A(年)

  • 主站收到来自从站的对时返回

    报文帧:68 14 0E 00 04 00 67 01 07 00 01 00 00 00 00 8E 6D 2C 0B 2F 0B 0A

    解析:68 (启动符)14 (长度20) 0E 00(发送序号) 04 00 (接受序号)67(类型标示:67时钟同步) 01 (可变结构限定词 sq=0:离散的信息报告)07 00(传输原因 0007:激活确认)01 00(公共地址即RTU地址)

    00 00 00(信息体地址)

    8E(毫秒低位)

    6D(毫秒高位) 6D8E = 28046/1000 = 28秒 28046%1000 = 46毫秒

    2C(分钟) 0010 1100 取 0-5 01100 = 12分

    0B(时) 0000 1011 取 0-4 01011 = 11时

    2F(日与星期) = 101111 取0-4 01111 = 15日

    0B (月)= 0000 1011 取0-3 1011 = 11月

    0A(年)= 0000 1010 取0-6 001010 = 10年

变化数据主动上传过程:如果RTU有变化数据则主动上传(从站向主站主动上传变化数据)

  • 主站收到的变化遥测报文

    报文帧:68 EA 04 00 00 00 0D 1C 03 00 01 00

    变化遥测的3字节信息体地址 第1个变化遥测的4字节浮点数值 第1个变化遥测的品质描述

    变化遥测的3字节信息体地址 第2个变化遥测的4字节浮点数值 第2个变化遥测的品质描述

    ……

    解析:68(启动符) EA(长度234) 04 00 (发送序号)00 00(接受序号) 0D (带品质描述的浮点值,每个遥测值占5个字节)1C (可变结构限定词(00011100 sq=0: 离散的信息报告; 11100 = 28:表示有28个遥测值;) 03 00 (传输原因 0003:突发)01 00 (公共地址即RTU地址)

    01 4C 00 (信息体地址)CE CC 64 41 00 ——1

    02 4C 00 (信息体地址)CE CC B8 C1 00 ——2

    04 4C 00 (信息体地址)33 33 03 C2 00 ——3

    4C 00 67 (信息体地址) 66 92 C1 00 07 ——4

    00 32 33 (信息体地址) 63 41 00 08 4C ——5

    00 97 99 (信息体地址) 01 41 00 0A 4C——6

    CE 8C C6 (信息体地址)C3 00 26 4C 00——24

    00 E0 0E (信息体地址) 44 00 29 4C 00 ——25

    34 F3 B8 (信息体地址)C3 00 2A 4C 00 ——26

    9B 99 FC (信息体地址)C3 00 2C 4C 00 ——27

    6C 66 B0(信息体地址) C2 00 4C 06 41——28

  • 主站收到的SOE报文

    SOE是为厂站端RTU采集的现场遥信变位信息,它包含了遥信变位的准确发生时间(精细到毫秒),RTU将其打包成SOE报文发送给主站系统,主站系统将规约报文解析成SOE报表

    报文帧:68 20 12 00 04 00 1E 02 03 00 01 00

    03 00 00 00 99 AF 3A 13 1E 03 00

    03 00 01 00 99 AF 3A 13 1E 03 00

    解析:68(启动符)20 (长度32)12 00(发送序号)04 00 (接受序号)1E (类型标示:带时标的遥信)02 (可变结构限定词 sq=0:离散的信息报告,2个SOE)03 00 (传输原因 0003:突发)01 00 (公共地址即RTU地址)

    03 00 00 (信息体地址)

    00 (分)99 AF(毫秒) 3A(分钟)13(时) 1E(日与星期) 03(月)00 (年)03 00 01 (信息体地址)

    00 (分)99 AF(毫秒) 3A (分钟)13 (时)1E(日与星期) 03 (月)00(年)

补充

  • 在总召唤时间间隔内,会用测试帧(TESTDT帧,U帧)来检测与维护通信链路状态,如:

    24-05-22 14:22:16 : 104子站–U帧:测试
    68 04 43 00 00 00
    24-05-22 14:22:16 : 104主站–U帧:测试确认
    68 04 83 00 00 00
    24-05-22 14:22:56 : 104子站–U帧:测试
    68 04 43 00 00 00
    24-05-22 14:22:56 : 104主站–U帧:测试确认

  • 主站会发送S帧,告诉从站已经成功接收并处理了特定的I帧(主站已接收并处理的I帧,S帧)。

    24-05-22 14:21:56 : 104主站–S帧
    68 04 01 00 06 00
    24-05-22 14:21:56 : 104主站–S帧
    68 04 01 00 0C 00
    24-05-22 14:21:56 : 104主站–S帧
    68 04 01 00 12 00

三、组织报文与解析报文(C++)

  • 首先,从数据库表中获取规约的编号,如:ptl_104_cj

  • 然后,加载规约动态库,得到该规约对应的组织数据与解析数据的函数

  • 通道通信发送数据处理

    • 调用规约报文组织函数组织报文

      m_pFuncbuildData(m_SPTLRecvSenddata, dynamic_cast<CUnitBase*>(m_pCurrentUnit));  //调用规约报文组织函数组织报文
      
      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;}
      }
      

      基于IEC60870-5-104的通信过程来组织报文

      • 发送STARTDT(U帧)是在规约初始化完成后、重新建立连接后、三分钟内没有通信默认通道是断的时。发送TESTDT(U帧)是在15秒内无数据通信时

        调用buildU()接口来组织U帧

      • 发送S帧是在主站解析完来自从站的I帧报文后

        调用buildS()接口来组织S帧

      • 针对遥控与遥调类型,首先这些数据的来源是站端解析来自云端的MQTT报文得到的

        遥控

        • 单点遥控,调用buildYKDataASDU45()接口
        • 双点遥控,调用buildYKDataASDU46()接口

        遥调:

        • 遥调模式-归一化值,调用buildYTCmdASDU48()接口
        • 遥调模式-标度化值,调用buildYTCmdASDU49()接口
        • 遥调模式-短浮点数,调用buildYTCmdASDU50()接口
      • 超过总召时间间隔,调用buildCallAllASDU100()接口组织发送总召命令

      • 超过遥脉总召时间间隔,调用buildDDASDU101()接口组织发送遥脉总召时间间隔

      • 超过校时时间间隔,调用buildSyncTimeASDU103()接口组织发送校时报文

      void sendFrame(S_PtlVariant &sVariant)  //组织数据
      {sVariant.uwMaxSendLen = MAX_FRAME_LEN;if(!sVariant.pPtlFlag->bPtlInitOK){if(initPtl(sVariant)){sVariant.pPtlFlag->bPtlInitOK = true;qDebug() << "initptl";}else{return;}}if(sVariant.pUnit->isNeedRestartLink()){sVariant.pPtlFlag->bPtlInitOK = false;sVariant.pPtlFlag->bLinkOK = 0;sVariant.pUnit->setRestartLink(false);return;}S_DateTime nowTime = qGetCurrentTime();//轮询周期quint32 uiScancYC = sVariant.pPtlFlag->pSPtl104->uiScancYCLen;uiScancYC = 0 ? 5: uiScancYC;//规约初始化if(!sVariant.pPtlFlag->bPtlInitOK){sVariant.pPtlFlag->bPtlInitOK = true;}//规约初始化完成if(!sVariant.pPtlFlag->bLinkOK){sVariant.pPtlFlag->bLinkOK = true;sVariant.pPtlFlag->byUStart_V = 1;}S_DateTime time = qGetCurrentTime();sVariant.pPtlFlag->tLastRecvTime = time;    //上次接收数据时间//作为数据接收方,要控制链路if(sVariant.pPtlFlag->bLinkOK){if(time - sVariant.pPtlFlag->tLastRecvTime > NODATA_LMT){//三分钟没收到数据,通道是断的,发启动帧sVariant.pPtlFlag->bLinkOK = 0;sVariant.pPtlFlag->byUStart_V = 1; }//15秒无数据发送测试帧else if(time - sVariant.pPtlFlag->tLastRecvTime > TESTDATA_LMT){//测试ASDU超时if(time - sVariant.pPtlFlag->tLastSendTime > T1){sVariant.pPtlFlag->byTest_V = 1;}}}else//重新建立连接{sVariant.pPtlFlag->uwRecvSN = 0;sVariant.pPtlFlag->uwSendSN = 0;if(time - sVariant.pPtlFlag->tLastSendTime >T0){sVariant.pPtlFlag->byUStart_V = 1;}}//判断链路连接成功,是否发送总召if((sVariant.pPtlFlag->bLinkOK)&& (sVariant.pPtlFlag->bSendCallAll)&& sVariant.pPtlFlag->byDowmFrame == FRAME_NULL){sVariant.pPtlFlag->byDowmFrame = ASDU_100;  //站总召命令}//链路成功,设置时钟if((sVariant.pPtlFlag->bLinkOK) && (sVariant.pPtlFlag->bSendTimeSynchro)&& sVariant.pPtlFlag->byDowmFrame == FRAME_NULL){sVariant.pPtlFlag->byDowmFrame = ASDU_103;  //时钟同步命令}if ((sVariant.pPtlFlag->bLinkOK) && (sVariant.pPtlFlag->bSendDDCall)&& sVariant.pPtlFlag->byDowmFrame == FRAME_NULL){sVariant.pPtlFlag->byDowmFrame = ASDU_101;  //电能量召唤命令}if(uiScancYC > 0){if(sVariant.pPtlFlag->bSendSFrame || (sVariant.pPtlFlag->bSendSFrame && nowTime - sVariant.pPtlFlag->tLastSFrameTime >= 1000)){buildS(sVariant);}if(sVariant.pPtlFlag->byUStart_V){sVariant.pPtlFlag->byDowmFrame = UFRAME_TYPE_STARTDT_V; //启动生效}if(sVariant.pPtlFlag->byUStart_C){sVariant.pPtlFlag->byDowmFrame = UFRAME_TYPE_STARTDT_C;}if(sVariant.pPtlFlag->byStop_V){sVariant.pPtlFlag->byDowmFrame = UFRAME_TYPE_STOPDT_V;}if(sVariant.pPtlFlag->byStop_C){sVariant.pPtlFlag->byDowmFrame = UFRAME_TYPE_STOPDT_C;}if(sVariant.pPtlFlag->byTest_V){sVariant.pPtlFlag->byDowmFrame = UFRAME_TYPE_TESTFR_V;}if(sVariant.pPtlFlag->byTest_C){sVariant.pPtlFlag->byDowmFrame = UFRAME_TYPE_TESTFR_C;}if(sVariant.pPtlFlag->byDowmFrame != FRAME_NULL){switch (sVariant.pPtlFlag->byDowmFrame) //根据启动控制信息,组织不同形式的帧{case UFRAME_TYPE_STARTDT_V:buildU(sVariant,UFRAME_TYPE_STARTDT_V);sVariant.pPtlFlag->byUStart_V = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case UFRAME_TYPE_STARTDT_C:buildU(sVariant,UFRAME_TYPE_STARTDT_C);sVariant.pPtlFlag->byUStart_C = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case UFRAME_TYPE_STOPDT_V:buildU(sVariant,UFRAME_TYPE_STOPDT_V);sVariant.pPtlFlag->byStop_V = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case UFRAME_TYPE_STOPDT_C:buildU(sVariant,UFRAME_TYPE_STOPDT_C);sVariant.pPtlFlag->byStop_C = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case UFRAME_TYPE_TESTFR_V:buildU(sVariant,UFRAME_TYPE_TESTFR_V);sVariant.pPtlFlag->byTest_V = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case UFRAME_TYPE_TESTFR_C:buildU(sVariant,UFRAME_TYPE_TESTFR_C);sVariant.pPtlFlag->byTest_C = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;default:break;}}if(sVariant.pPtlFlag->bLinkOK){if(sVariant.pUnit->getHaveCmd()&&!sVariant.pUnit->isCmdProcing()){sVariant.pUnit->setCmdProcing(true);switch (sVariant.pUnit->getCmdType()) //采集装置:命令类型;转发装置:命令响应的类型{case CMD_TYPE_YK:  //遥控{S_YKCmd* pYKCmd = static_cast<S_YKCmd*>(sVariant.pUnit->getCmd(CMD_TYPE_YK));quint8 byType = 0;sVariant.pUnit->getValue(S_DataID(TABLE_PREYK, pYKCmd->uiZFYKID, COL_PREYK_YKTYPE), &byType);  //遥控类型:单点遥信与双点遥信if(byType == EYKT_SP){sVariant.pPtlFlag->byDowmFrame = ASDU_45;}else if (byType == EYKT_DP){sVariant.pPtlFlag->byDowmFrame = ASDU_46;}}break;case CMD_TYPE_YT:  //遥调{switch(sVariant.pPtlFlag->pSPtl104->byYTMode){case YT104_MODE_ASDU48:  //104遥调模式-归一化值buildYTCmdASDU48(sVariant);break;case YT104_MODE_ASDU49:  //遥调模式-标度化值buildYTCmdASDU49(sVariant);break;case YT104_MODE_ASDU50:  //遥调模式-短浮点数buildYTCmdASDU50(sVariant);break;default:break;}}break;case CMD_TYPE_SET_TIME:  //时钟同步命令sVariant.pPtlFlag->byDowmFrame = ASDU_103;break;case CMD_TYPE_CALL_DATA:  //站总召命令sVariant.pPtlFlag->byDowmFrame = ASDU_100;break;default:break;}}}if(nowTime - sVariant.pPtlFlag->tLastSendCallAllTime >= sVariant.pPtlFlag->pSPtl104->uwAllCallInterval){buildCallAllASDU100(sVariant);}if(nowTime - sVariant.pPtlFlag->tLastSendCallDDTime >= sVariant.pPtlFlag->pSPtl104->uwYMInterval){
      //            sVariant.pPtlFlag->bSendDDCall = 1;buildDDASDU101(sVariant);}if(nowTime - sVariant.pPtlFlag->tLastCheckTime >= sVariant.pPtlFlag->pSPtl104->uwCheckTimeInterval){buildSyncTimeASDU103(sVariant);}switch (sVariant.pPtlFlag->byDowmFrame){case S_FRAME:buildS(sVariant);break;case ASDU_100:buildCallAllASDU100(sVariant);break;case ASDU_103:buildSyncTimeASDU103(sVariant);break;case ASDU_101:buildDDASDU101(sVariant);break;case UFRAME_TYPE_TESTFR_V:buildU(sVariant,UFRAME_TYPE_TESTFR_V);sVariant.pPtlFlag->byTest_V = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case UFRAME_TYPE_TESTFR_C:buildU(sVariant,UFRAME_TYPE_TESTFR_C);sVariant.pPtlFlag->byTest_C = 0;sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;break;case ASDU_45:buildYKDataASDU45(sVariant);break;case ASDU_46:buildYKDataASDU46(sVariant);break;default:break;}}sVariant.pPtlFlag->byDowmFrame = FRAME_NULL;putSendData(sVariant);   //把发送数据放入RTU发送缓冲区中
      }
      

      以组织总召唤命令为例:

      void buildCallAllASDU100(S_PtlVariant &sVariant)
      {if(sVariant.uwMaxSendLen - sVariant.uwSendDataLen < 16){return;}S_IframeHead *pIframeHead = (S_IframeHead*)(sVariant.pBySendBuffer + sVariant.uwSendDataLen);S_ASDUHead *pASDUHead = (S_ASDUHead *)(sVariant.pBySendBuffer + sVariant.uwSendDataLen+6);pIframeHead->byStart = 0x68;pIframeHead->byLen = 0x0E;pIframeHead->uwDoublens = sVariant.pPtlFlag->uwSendSN;pIframeHead->uwDoublens = qBigLittleEndianConvert(pIframeHead->uwDoublens);pIframeHead->uwDoublenr = sVariant.pPtlFlag->uwRecvSN;pIframeHead->uwDoublenr = qBigLittleEndianConvert(pIframeHead->uwDoublenr);pASDUHead->byType = ASDU_100;pASDUHead->bSq = 0;pASDUHead->bNum = 1;pASDUHead->uwReason = 6;pASDUHead->uwReason = qBigLittleEndianConvert(pASDUHead->uwReason);pASDUHead->uwCa = sVariant.pUnit->getTUAddress();pASDUHead->uwCa = qBigLittleEndianConvert(pASDUHead->uwCa);memset(sVariant.pBySendBuffer + sVariant.uwSendDataLen+12,0,3);sVariant.pBySendBuffer[sVariant.uwSendDataLen + 15] = 20;sVariant.uwSendDataLen += 16;sVariant.pPtlFlag->uwSendSN += 2;sVariant.pPtlFlag->bSendCallAll = 0;S_DateTime time = qGetCurrentTime();sVariant.pPtlFlag->tLastSendCallAllTime = time;sVariant.pPtlFlag->tLastSendTime = time;//sVariant.pPtlFlag->tLastSendCallDDTime = time;sVariant.pPtlFlag->bSendDDCall = 1;}
      

      组织好报文后,基于通道通讯类型,分别调用串口或网络的发送数据接口

      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 debugqMSleep(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;
    }
    

    调用规约报文解析函数来解析报文

    m_pFuncParseData(m_SPTLRecvSenddata, dynamic_cast<CUnitBase*>(m_pCurrentUnit)
    
    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;
    }
    

    解析报文

    • 首先,判断当前主站接收到的报文帧是U帧S帧还是I帧,并设置对应的帧状态与帧类型。若帧的第一个字符不是启动字符,则偏移缓冲区中 的数据

    • 接着,根据帧类型调用通道报文添加函数,将报文转发给上位机。此时帧状态为接收帧已同步

    • 最后,根据帧类型调用不同的报文解析函数

      U帧S帧:调用parseUOrSFrame()接口

      I帧:调用parseIFrame()接口

    bool readFrame(S_PtlVariant &sVariant) //解析数据
    {bool bResult = false;while(sVariant.uwRecvDataLen >= sizeof (S_Uframe)){S_IframeHead sIFrame;memcpy(&sIFrame, sVariant.pByReadBuffer, sizeof (S_IframeHead));if(FRAME_STATE_NO == sVariant.byCurrentRecvedFrameState)    //当前接收帧的状态为无有效帧{quint16 uwAddr;S_ASDUHead sASDUHead;uwAddr = sVariant.pUnit->getTUAddress();int i;for(i = 0;i <= sVariant.uwRecvDataLen - 6; i++){//找到启动符68 和第二个字长度为04,确定为短帧:U帧或S帧if(0x68 == sVariant.pByReadBuffer[i] && 0x04 == sVariant.pByReadBuffer[i+1]&& 0x00 == sVariant.pByReadBuffer[i+3] && 0x00 == sVariant.pByReadBuffer[i+5])  //短帧{if(sVariant.uwRecvDataLen >= i+6){//为u或s帧sVariant.byCurrentRecvedFrameState = FRAME_STATE_SYNING;sVariant.byCurrentRecvedFrameType = FRAME_TYPE_UORS;break;}else{if(i > 0){offsetRecvBuffer(sVariant,static_cast<quint16>(i));}return bResult;}}else    //长帧:I帧{if(0x68 == sVariant.pByReadBuffer[i]){if(sVariant.uwRecvDataLen > i + 10 && uwAddr == sVariant.pByReadBuffer[i + 10]){sVariant.byCurrentRecvedFrameState = FRAME_STATE_SYNING;sVariant.byCurrentRecvedFrameType = FRAME_TYPE_I;break;}else{if(i > 0){offsetRecvBuffer(sVariant,static_cast<quint16>(i));}return bResult;}}}}if(i > 0){offsetRecvBuffer(sVariant,static_cast<quint16>(i));}}if(FRAME_STATE_SYNING == sVariant.byCurrentRecvedFrameState){if(FRAME_TYPE_UORS == sVariant.byCurrentRecvedFrameType){if(sVariant.uwRecvDataLen >= 6){sVariant.byCurrentRecvedFrameState = FRAME_STATE_SYN;sVariant.uwCurrentRecvedFrameLen = 6;sVariant.pUnit->addCommMsg(true, sVariant.uwCurrentRecvedFrameLen, sVariant.pByReadBuffer, sVariant.byChannelMode, sVariant.byChannelIndex, sVariant.bMainChannel);// qDebug()<< "装置通信短帧" <<sVariant.uwCurrentRecvedFrameLen << (quint8*)pCommMsg->chCommMsg;}}if(FRAME_TYPE_I == sVariant.byCurrentRecvedFrameType){if(sVariant.uwRecvDataLen >= sVariant.pByReadBuffer[1]){sVariant.byCurrentRecvedFrameState = FRAME_STATE_SYN;sVariant.uwCurrentRecvedFrameLen = sVariant.pByReadBuffer[1] + 2;sVariant.pUnit->addCommMsg(true, sVariant.uwCurrentRecvedFrameLen, sVariant.pByReadBuffer, sVariant.byChannelMode, sVariant.byChannelIndex, sVariant.bMainChannel);  //添加通信报文到队列(包括转发给上位机的队列,以及要保存到本地的队列)//qDebug()<< "装置通信长帧" <<sVariant.uwCurrentRecvedFrameLen << (quint8*)pCommMsg->chCommMsg;}}}if(FRAME_STATE_SYN != sVariant.byCurrentRecvedFrameState){return bResult;}S_DateTime time = qGetCurrentTime();sVariant.pPtlFlag->tLastRecvTime = time;switch (sVariant.byCurrentRecvedFrameType){case FRAME_TYPE_UORS:parseUOrSFrame(sVariant); //解析U帧或S帧break;case FRAME_TYPE_I:parseIFrame(sVariant); // 解析I帧数据sVariant.pPtlFlag->uwRecvSN = sIFrame.uwDoublens;sVariant.pPtlFlag->uwRecvSN += 2;sVariant.pPtlFlag->uwRecvSN = qBigLittleEndianConvert(sVariant.pPtlFlag->uwRecvSN);break;default:break;}sVariant.pPtlFlag->bWaitDataReturn = 0;sVariant.byCurrentRecvedFrameState = FRAME_STATE_NO;offsetRecvBuffer(sVariant,sVariant.uwCurrentRecvedFrameLen);sVariant.uwCurrentRecvedFrameLen = 0;bResult = true;}return bResult
    

    解析U帧、S帧、I帧的函数接口分别为:

    void parseUFrame(S_PtlVariant &sVariant)
    {S_Uframe* pSUFrame = (S_Uframe *)sVariant.pByReadBuffer;//主站发送→激活传输启动if(pSUFrame ->byCtrlOctets1 == 0x07){sVariant.pPtlFlag->uwRecvSN = 0;       //接收计数sVariant.pPtlFlag->uwSendSN = 0;       //发送计数sVariant.pPtlFlag->bLinkOK = true;     //链路状态}else if(pSUFrame ->byCtrlOctets1 == 0x43){sVariant.pPtlFlag->byTest_C = 1;}//从站发送→确认激活传输启动else if(pSUFrame ->byCtrlOctets1 == 0x0B){sVariant.pPtlFlag->uwRecvSN = 0;       //接收计数sVariant.pPtlFlag->uwSendSN = 0;       //发送计数sVariant.pPtlFlag->bLinkOK = 1;     //链路状态sVariant.pPtlFlag->bSendCallAll = 1;//总召状态}//主站发送→停止链路else if(pSUFrame ->byCtrlOctets1 == 0x13){sVariant.pPtlFlag->bLinkOK = 0;     //链路状态}//从站发送→确认停止链路else if(pSUFrame ->byCtrlOctets1 == 0x23){sVariant.pPtlFlag->bLinkOK = 0;     //链路状态}//重新同步sVariant.byCurrentRecvedFrameState = FRAME_STATE_NO;
    }void parseSFrame(S_PtlVariant &sVariant)
    {quint16 uwRecv = 0;memcpy(&uwRecv,&sVariant.pByReadBuffer[4],sizeof(quint16));sVariant.pPtlFlag->uwConfirmRecvSn = uwRecv;sVariant.pPtlFlag->byRecvSFrameSendFlag = 1;S_DateTime time = qGetCurrentTime();sVariant.pPtlFlag->tLastRecvSFrame = time;//重新同步sVariant.byCurrentRecvedFrameState = FRAME_STATE_NO;}
    void parseIFrame(S_PtlVariant &sVariant)
    {S_ASDUHead sASDUHead;memcpy(&sASDUHead,sVariant.pByReadBuffer + sizeof (S_IframeHead),sizeof(S_ASDUHead));//判断公共地址sASDUHead.uwCa = qBigLittleEndianConvert(sASDUHead.uwCa);if(sASDUHead.uwCa != sVariant.pUnit->getTUAddress()){return;}switch (sASDUHead.byType){case ASDU_01:   //单点信息(遥信)parseYXDataASDU1(sVariant);break;case ASDU_03:   //双点信息(遥信)parseYXDataASDU3(sVariant);break;case ASDU_09:   //解析遥测归一化值parseYCDataASDU9(sVariant);break;case ASDU_21:   //解析遥测归一化值  测试 debug 白parseYCDataASDU21(sVariant);break;case ASDU_11:   //解析遥测标度化值parseYCDataASDU11(sVariant);break;case ASDU_13:   //解析遥测短浮点数parseYCDataASDU13(sVariant);break;case ASDU_30:   //解析带时标单点遥信parseYXDataASDU30(sVariant);break;case ASDU_31:   //解析带时标单点遥信parseYXDataASDU31(sVariant);break;case ASDU_45:   //解析遥控单命令if(sVariant.pByReadBuffer[1] != 0x0E){return;}parseYKDataASDU45(sVariant);break;case ASDU_46:   //解析遥控单命令if(sVariant.pByReadBuffer[1] != 0x0E){return;}parseYKDataASDU46(sVariant);break;case ASDU_48:if(sVariant.pByReadBuffer[1] != 16){return;}parseYTCmdResultASDU48(sVariant);break;case ASDU_49:if(sVariant.pByReadBuffer[1] != 16){return;}parseYTCmdResultASDU49(sVariant);break;case ASDU_50:if(sVariant.pByReadBuffer[1] != 18){return;}parseYTCmdResultASDU50(sVariant);break;case ASDU_100:  //解析总召唤数据if(sVariant.pByReadBuffer[1] != 0x0E){return;}parseZHDataASDU100(sVariant);break;case ASDU_15:  //解析遥脉值parseYMDataASDU206(sVariant);break;case ASDU_206:  //解析遥脉值parseYMDataASDU206(sVariant);break;case ASDU_207:  //解析遥脉值parseYMDataASDU207(sVariant);break;case ASDU_103:  //解析时钟同步parseTimeSynASDU103(sVariant);default:break;}sVariant.pPtlFlag->bSendSFrame = 1;//debug白11/26
    }
    

    在解析I帧中,以解析遥测归一化值为例

    void parseYCDataASDU9(S_PtlVariant &sVariant)
    {S_ASDUHead sASDUHead;S_Addr sAddr;S_ASDU9YC sYCValue;quint16 uwIndex;bool bIsCalc = false;memcpy(&sASDUHead,sVariant.pByReadBuffer + sizeof (S_IframeHead),sizeof(S_ASDUHead));sASDUHead.uwReason = qBigLittleEndianConvert(sASDUHead.uwReason);//类型不为9,原因不为1/2/3/5/20
    //    if(    sASDUHead.uwReason != 1 &&sASDUHead.uwReason != 2 &&sASDUHead.uwReason != 3
    //           && sASDUHead.uwReason != 5 &&sASDUHead.uwReason != 20)
    //    {
    //        return;
    //    }if(sASDUHead.bSq)//连续{memcpy(&sAddr,&sVariant.pByReadBuffer[12],sizeof (sAddr));sAddr.uwAddrOrder = qBigLittleEndianConvert(sAddr.uwAddrOrder);//地址合法性if((sAddr.uwAddrOrder > sVariant.pPtlFlag->pSPtl104->uwYCEndAddr)||(sAddr.uwAddrOrder < sVariant.pPtlFlag->pSPtl104->uwYCStartAddr)){return;}uwIndex = sAddr.uwAddrOrder - sVariant.pPtlFlag->pSPtl104->uwYCStartAddr;for(quint8 i = 0;i < sASDUHead.bNum; i++){memcpy(&sYCValue,&sVariant.pByReadBuffer[15 + i*2],sizeof (S_ASDU9YC));sYCValue.wValue = qBigLittleEndianConvert(sYCValue.wValue);double dValue = (double)sYCValue.wValue;sVariant.pUnit->getValue(S_DataIndex(TABLE_PREYC, uwIndex + i, COL_PREYC_ISCALC), &bIsCalc);if(!bIsCalc){sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC,uwIndex + i,COL_PREYC_YCVALUE) ,&dValue);}}}else //不连续{for(quint8 i = 0;i < sASDUHead.bNum; i++){memcpy(&sAddr,&sVariant.pByReadBuffer[12 + i*6],sizeof (sAddr));sAddr.uwAddrOrder = qBigLittleEndianConvert(sAddr.uwAddrOrder);//合法性检查if((sAddr.uwAddrOrder > sVariant.pPtlFlag->pSPtl104->uwYCEndAddr)||sAddr.uwAddrOrder < sVariant.pPtlFlag->pSPtl104->uwYCStartAddr){return;}uwIndex = sAddr.uwAddrOrder - sVariant.pPtlFlag->pSPtl104->uwYCStartAddr;memcpy(&sYCValue,&sVariant.pByReadBuffer[15 + i*6],sizeof (sYCValue));sYCValue.wValue = qBigLittleEndianConvert(sYCValue.wValue);double dValue = (double)sYCValue.wValue;sVariant.pUnit->getValue(S_DataIndex(TABLE_PREYC, uwIndex, COL_PREYC_ISCALC), &bIsCalc);if(!bIsCalc){sVariant.pUnit->setValue(S_DataIndex(TABLE_PREYC, uwIndex, COL_PREYC_YCVALUE) ,&dValue);}}}}
    

    解析报文后,依据连续数据点位与非连续数据点位来讨论分别设置数据库表中对应点位的值。


参考文献:

  • IEC60870协议是什么?IEC60870协议介绍

  • 从零开始理解IEC104协议之一——104规约帧格式

  • IEC60870开源库

  • IEC 60870-5-104 详细解读

  • IEC104规约(一)协议结构阐述

  • IEC104/101 主站/客户端 模拟器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/18135.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是GPT-4o,推荐GPT-4o的获取使用方法,使用GPT4o模型的最新方法教程(2024年5月16更新)

2024年5月最新GPT-4o模型使用教程和简介 2024年5月最新GPT-4o模型使用教程和简介 2024 年 5 月 13 日&#xff0c;openai 发布了最新的模型 GPT4o。 很多同学还不知道如何访问GPT-4、GPT-4 Turbo和GPT-4o等模型&#xff0c;这篇文章介绍如何在ChatGPT中访问GPT-4o&#xff0…

无人机侦察:雷达系统概述

一、雷达基本原理 无人机侦察中的雷达系统主要基于无线电波的传播和反射原理。雷达发射机产生特定频率的电磁波&#xff0c;并通过天线以定向波束形式向空间发射。当这些电磁波遇到目标时&#xff0c;部分能量会被反射回来&#xff0c;被雷达接收机捕获。通过测量发射和接收电…

基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】

前言 该系统采用SpringBootVue前后端分离开发&#xff0c;前端是一个单独的项目&#xff0c;后端是一个单独的项目。   技术栈&#xff1a;SpringBootVueMybatisRedisMysql   开发工具&#xff1a;IDEA、Vscode   浏览器&#xff1a;Chrome   开发环境&#xff1a;JDK1…

Pytorch 笔记

执行下面这段代码后&#xff0c;为什么返回的是 2 &#xff1f; vector torch.tensor([7, 7]) vector.shape为什么返回的是 torch.Size([2])&#xff1f; 当你创建一个PyTorch张量时&#xff0c;它会记住张量中元素的数量和每个维度的大小。在你的代码中&#xff0c;torch.t…

通过 js 调起微信官方的微信支付api

通过 js 调起微信官方的微信支付api function onBridgeReady() {WeixinJSBridge.invoke(getBrandWCPayRequest, { "appId": "wx2421b1c4370ec43b", // 公众号ID&#xff0c;由商户传入 "timeStamp": "1395712654", // 时间戳&quo…

使用canarytokens进行入侵检测

canarytokens 基本概念 canarytokens是一种用于识别网络入侵的工具。它们是一种虚拟的“蜜罐”&#xff0c;可以在网络上放置&#xff0c;当有人尝试访问它们时&#xff0c;可以立即触发警报&#xff0c;以便及时发现潜在的安全威胁。这些token可以是各种形式&#xff0c;可以…

项目管理基础知识

项目管理基础知识 导航 文章目录 项目管理基础知识导航一、项目相关概念二、时间管理三、人员管理四、风险管理 一、项目相关概念 项目定义的三层意思 一定的资源约束:时间资源、经费资源、人力资源一定的目标一次性任务 里程碑 是项目中的重要时点或事件持续时间为零&…

深度神经网络——什么是迁移学习?

1.概述 在练习机器学习时&#xff0c;训练模型可能需要很长时间。从头开始创建模型架构、训练模型&#xff0c;然后调整模型需要大量的时间和精力。训练机器学习模型的一种更有效的方法是使用已经定义的架构&#xff0c;可能具有已经计算出的权重。这是背后的主要思想 迁移学习…

makefile一些特殊且常用的符号

$^&#xff1a;表示所有的依赖文件列表&#xff0c;多个文件以空格分隔。 $&#xff1a;表示目标文件的名称。 $<&#xff1a;表示第一个依赖文件的名称。 $*&#xff1a;表示目标文件的主文件名&#xff08;不包括扩展名&#xff09;。 $?&#xff1a;表示所有比目标文件更…

Linux shell命令

cat 文件名 查看文件内容&#xff0c; tac文件名 倒着显示。 more 文件名 显示内容 less文件名 和more的功能一样&#xff0c;按上下左右键&#xff0c;按Q键结束。 head文件名&#xff0c;只显示前10行内容。 ln是一个默认创建硬链接的命令 ln 文件名 ls -i文件名…

SpringBoot整合RabbitMQ的快速使用教程

目录 一、引入依赖 二、配置rabbitmq的连接信息等 1、生产者配置 2、消费者配置 三、设置消息转换器 四、生产者代码示例 1、配置交换机和队列信息 2、生产消息代码 五、消费者代码示例 1、消费层代码 2、业务层代码 在分布式系统中&#xff0c;消息队列是一种重要…

00Java准备工作

目录 JDK的安装目录 JAVA环境变量的配置 JAVA小知识 JDK的安装目录 目录名称说明bin该路径下存放了JDK的各种工具命令,javac和java就放在这个目录conf该路径下存放了JDK的相关配置文件include该路径下存放了一些平台特定的头文件jmods该路径下存放了JDK的各种模块legal该路…

简单随机数据算法

文章目录 一&#xff0c;需求概述二&#xff0c;实现代码三、测试代码四、测试结果五、源码传送六、效果演示 一&#xff0c;需求概述 系统启动时&#xff0c;读取一组图片数据&#xff0c;通过接口返回给前台&#xff0c;要求&#xff1a; 图片随机相邻图片不重复 二&#…

进程互斥经典问题(读写者问题、理发店问题)

目录 读写者问题 问题描述 问题分析 进程互斥问题三部曲 读者写者算法实现 一、找进程——确定进程关系 二、找主营业务 三、找同步约束 a.互斥 b.资源 c.配额 理发店问题 问题描述 问题分析 进程互斥问题三部曲 理发店问题算法实现 一、找进程——确定进程…

SB-OSC,最新的 MySQL Schema 在线变更方案

目前主流的 MySQL 在线变更方案有两个&#xff1a; 基于 trigger 的 pt-online-schema-change基于 binlog 的 gh-ost 上周 Sendbird 刚开源了他们的 MySQL Schema 在线变更方案 SB-OSC: Sendbird Online Schema Change。 GitHub 上刚刚 25 颗星星&#xff0c;绝对新鲜出炉。 …

Qt Creator(2)【如何在Qt Creator中创建新工程】

阅读导航 引言一、Qt Creator开始界面介绍二、如何在Qt Creator中创建新工程1. 新建项目2. 选择项目模板3. 选择项目路径4. 选择构建系统5. 填写类信息设置界面6. 选择语言和翻译文件7. 选择Qt套件8. 选择版本控制系统9. 最终效果 三、认识Qt Creator项目内容界面1. 基本界面2.…

go语言初识别(五)

本博客内容涉及到&#xff1a;切片 切片 1. 切片的概念 首先先对数组进行一下回顾&#xff1a; 数组定义完&#xff0c;长度是固定的&#xff0c;例如&#xff1a; var num [5]int [5]int{1,2,3,4,5}定义的num数组长度是5&#xff0c;表示只能存储5个整形数字&#xff0c…

检索模型预训练方法:RetroMAE

论文title&#xff1a;https://arxiv.org/pdf/2205.12035RetroMAE: Pre-Training Retrieval-oriented Language Models Via Masked Auto-Encoder 论文链接&#xff1a;https://arxiv.org/pdf/2205.12035 摘要 1.一种新的MAE工作流&#xff0c;编码器和解器输入进行了不同的掩…

华为OD机试【计算最接近的数】(java)(100分)

1、题目描述 给定一个数组X和正整数K&#xff0c;请找出使表达式X[i] - X[i1] … - X[i K 1]&#xff0c;结果最接近于数组中位数的下标i&#xff0c;如果有多个i满足条件&#xff0c;请返回最大的i。 其中&#xff0c;数组中位数&#xff1a;长度为N的数组&#xff0c;按照元…

软件性能测试有哪些测试类型和方法?

软件性能测试是一种通过模拟真实用户使用情况&#xff0c;评估软件系统在各种压力和负载下的表现的测试方法。在今天这个讲究效率的时代&#xff0c;软件性能测试是不可或缺的一环。它能帮助开发人员和企业发现潜在的性能问题&#xff0c;提前优化改进&#xff0c;保证软件系统…