跳到主要内容

电压监测通讯规约

  • 参考文档:Q_GDW1819-2013电压监测装置技术规范

一 专业术语

  • 电压偏差:由于电力系统正常运行状态的缓慢变化,使电压发生偏移,其电压变化率小于每秒1%时的实际运行电压值与系统标称电压的偏差相对值
  • 整定电压(标准)值 ( Ub):按 GB/T12325 规定的供电电压偏差的上限电压标准值与下限电压标准值。
  • 起动电压(Uq) :刚好驱动电压监测装置超限计时,并稳定指示超限时的被监测电压值。
  • 整定电压值基本误差 (rz):在正常使用条件下,电压监测装置上、下限整定电压的起动电压 Uq 和相应的整定电压(标准)值,Ub 之差与整定电压(标准)值 Ub 的比值(以百分数表示)。

image.png

  • 被监测的额定电压 (Un) :被监测系统的额定电压。其值为 AC220V、380V、3kV、6kV、10kV、20kV、35kV、66 kV、10 kV 、220 kV 、330 kV 、500kV 、750 kV 和1000 kV 等。
  • 工作电源额定电压 (Ug):电压监测装置的工作电源的额定电压值。其值为AC100V、220V 、380V 等。
  • 综合测量误差 (rc) :在正常使用条件下,被测量的综合测量值 CX (如电压合格率,电压超上限率,电压超下限率),对应于被测量的预置值Cy 的相对误差(以百分数表示)。

image.png

  • 电压合格率:实际运行电压偏差在限值范围内累计运行时间与对应的总运行统计时间的百分比。电压监测统计的时间单位为“min”。
  • 电压超上限率 :实际运行电压偏差在上限电压标准值以上范围内累计运行时间与对应的总运行统计时间的百分比。电压监测统计的时间单位为 “min”。
  • 电压超下限率 :实际运行电压偏差在下限电压标准值以下范围内累计运行时间与对应的总运行统计时间的百分比。电压监测统计的时间单位为“min”。
  • 时钟误差 :在规定的时间间隔内,以时间指示偏差表示的增量或减量。
  • 状态接入控制器:复用状态监测接入层设备,以规定的数据格式对电压监测装置进行电压采集信息获取、转发及控制的一种装置。

二 基本功能

2.1 基础功能

  • 电压值采样:电压监测装置对被监测电压采用有效值采样,每秒至少1次,并作为预处理值贮存,1min 作为一 个统计单元,取0秒时刻开始的1min 内电压预处理值的平均值,作为被监测系统的即时实际运行电压,不足1min 的值不进行统计计算
  • 电压监测数据冻结:电压监测装置根据实际运行电压(1min 平均值)及被监测电压额定值、整定电压上限值和整定电 压下限值来统计日(月)电压监测数据,包括总运行统计时间、越上限累计时间、越下限累计时间、电 压合格率、电压越上限率、电压越下限率、电压最大值及其发生时间、电压最小值及其发生时间(以下统称电压监测统计数据)
  • 结算日设置:电压监测装置月统计数据默认自然月为统计时间,可以设置1 日至28日中任意一天为月统计结算日,月统计时间为月统计结算日的当日零点至下月的月统计结算日当日零点止。

2.2 数据存储

  • 应至少保存最近45天内每天的电压监测日统计数据
  • 应至少保存本月及上月的电压监测月统计数据
  • 应至少保存最近45天被监测的实际运行电压(1min 平均值),存储的间隔5min
  • 应至少保存本月及上月的电压越限、越限复归事件记录

2.3 显示功能(设备能够看到的)

  • 实时电压值,其刷新周期为2s, 能显示5位有效数字,小点数后显示2位
  • 电压合格率、电压越上限率、电压越下限率,能显示5位有效数字,小点数后显示2位; 实时时间,显示年、月、日、时、分、秒(例如:2010年01月01 日16时58分59秒)
  • 整定电压上限值和整定电压下限值、月统计结算日等参数
  • 前一日及当日的电压监测日统计数据
  • 前一月及当月的电压监测月统计数据
  • 电压监测装置的生产序列号(17位),以及电压监测装置 ID(17 位)、 APN 、IP 地址等通信参数
  • 通信模块的通信状态以及与CAC 的连接状态
  • 前一月及当月的告警事件信息记录

2.4 参数设置与查询

  • 日期与时间
  • 通信参数:包括APN 、APN 用户名和密码、心跳间隔等
  • 监测点参数:包括被监测电压额定值、电压上、下限整定值,电压1min 平均值存储间隔和主动上送周期、月统计结算日、数据主动上送标记等
  • 装置事件主动上送标记:包括来电、停电(含异常失压)、电压越限与复归事件;CAC 装置 IP 地址和端口号
  • 装置基本信息:包括型号、软硬件版本、通信规约版本、生产厂家、生产日期、出厂编号、SIM 卡串号等
  • 装置工作状态信息:包括装置累计运行时间、最近持续运行时间等
  • 装置ID

2.5 事件检测与告警功能

  • 检测电压越限、越限复归事件功能, 一条完整的事件记录应包括事件发生时刻及当时电压值
  • 停电、来电事件检测功能,停电、来电事件记录应包括事件发生时刻
  • 检测到事件后主动向CAC 上报

三 其他内容

3.1 通信方式

电压监测装置应根据需求选择无线通信方式 ( GPRS 、CDMA 、3G 等)或以太网通信方式,作为与 CAC 通信的通道。应提供一个 RS232 串口作为维护或校验通道,并至少提供一个 RS485 串口作为本地通信接口。

3.2 通信串口

RS485 和 RS232 串口波特率可在1200pbs 、2400pbs 、4800pbs 、9600pbs 内选择。

四 帧结构解析

image.png 网络通信规范的作用就是为了以上大三方面、无论是我们上面提到的基础功能,还是显示功能的部分,或者运程升级维护通过约定的协议内容进行采集,下面介绍数据帧

4.1 传输方式

  • 数据通信的传输方式采用基于公共无线网络 (G PRS 、CDMA 、3G 等)的虚拟专网和短信 (SMS) 方式。
  • 基于无线虚拟专网传输时,采用TCP 协议,CAC 为 TCP 服务端。
  • 基于短信传输时,采用8位编码的 PDU 方式。

4.2 数据帧格式

报文使用数据帧模式,帧的基本单元为8位字节。数据帧包括报文头、报文长度、电压监测装置ID、帧类型、报文类型、帧序列号、报文内容、校验位、报文尾。

报文头报文长度电压监测装置ID帧类型报文类型帧序列号报文内容校验位报文尾
2 Byte2 Byte17 Byte1 Byte1 Byte1 Byte变长2 Byte1Byte

前提知识:字节运算,数据传输顺序

  • 位:也叫比特位,数据存储的最小单位。每个二进制数字0或者1就是1个位
  • 字节:8个位构成一个字节。即:1 byte (字节)= 8 bit (位)
  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
  • 小端字节序:低位字节在前,高位字节在后,即以0x6622形式储存。
  • 计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

    案例说明:

0x1234567大端字节序和小端字节序的写法如下图:

4.2.1 报文头(2字节)

标识电压监测数据报开始,以16进制整型值5AA5(10 进制值23205)表示,这个没啥说的,固定格式

4.2.2 报文长度(由内容区确定)

帧结构中报文内容数据的长度,单位:字节 (Byte) 。通过TCP 协议传输时,报文 长度应小于等于1453;通过SMS 协议传输时,报文长度应小于等于113。

4.2.3 电压监测装置ID(17个字节)

电压监测装置唯一标识,遵循国家电网公司“SG186 工程”生产管理系统 设备17位编码规范。

4.2.4 帧类型(1个字节)

按功能对数据帧进行区分、标识:

序号类型值含 义
10x01心跳数据报文(电压监测装置→CAC)
20x02心跳数据确认报文(CAC→ 电压监测装置)
30x03监测数据报文(电压监测装置→CAC)
40x04监测数据确认报文(CAC→ 电压监测装置)
50x05数据请求报文(CAC→ 电压监测装置)
60x06数据请求确认报文(电压监测装置→CAC)
70x07配置/状态交互数据报文(CAC→ 电压监测装置)
80x08配置/状态交互响应报文(电压监测装置→CAC)
90x09流量数据报文(电压监测装置→CAC)
100x10流量数据确认报文(CAC→ 电压监测装置)
110x11事件信息报文(电压监测装置→CAC)
120x12事件信息确认报文(CAC→ 电压监测装置)
130x13远程升级数据报文(CAC→ 电压监测装置)
140x14远程升级数据确认报文(电压监测装置→CAC)

4.2.5 报文类型(1个字节)

序号报文分类类型值含 义
1心跳数据报文(0x01~0x03)0x01心跳数据报
20x02-0x03预留
3监测数据报文(0x04~0xA0)0x04电压数据报
40x05日统计数据报
50x06月统计数据报
60x07-0xA0预留
7数据请求报文(0xA1~0xA3)0xA1数据请求报
80xA2-0xA3预留
9配置/状态交互数据报文(0xA4~0xCO)0xA4装置时间查询/设置
100xA6装置通信参数查询/设置
110xA7监测点参数查询/设置
120xA8装置事件参数查询/设置
130xA9装置所属CAC的信息查询/设置
14OxAA装置基本信息查询
150xAB装置工作状态信息查询
16OxAC装置通信流量信息查询
17OxAD装置ID查询/设置
18OxAE装置复位
19OxAF装置调试命令交互
200xB0-0xC0预留
21流量数据报文(0xC1~0xC3)0xC1流量数据报
220xC2-0xC3预留
23事件信息报文(0xC4~0xC6)0xC4事件信息报
240xC5-0xC6预留
25远程升级报文(0xC7~0xCF)OxC7升级启动数据报
260xC8升级过程数据报
270xC9升级结束数据报
280xCA-OxCF预留

4.2.6 帧序列号(1字节)

电压监测装置或者 CAC 主动发送的报文的顺序流水号,在确认或者响应报文中应 返回该帧序列号。

4.2.7 报文内容(不确定)

数据的字节长度不固定,具体定义参考 A.2.3、A.2.4、A.2.5 、A.2.6 、A.2.7 、A.2.8、A.2.9 等章节。

4.2.8 校验位(2字节)

校验位:数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以 任意选定。本协议中,校验位通过A.4 章节中所列CRC16 校验算法换算得出,校验的内容包 括报文中除报文头、校验位、报文尾外所有报文数据,包括报文长度、电压监测装置ID、帧类型、报文类型、帧序列号以及报文内容。

4.2.9 报文尾(1字节)

报文尾:标识电压监测数据报结束,以16进制整型值96(10进制值150)表示。

4.3 帧数据排列格式

字符串采用顺序存储,以二进制0结尾;除特殊说明,整型(占2Byte)、长整型和浮点型(占4 Byte)、 双精度浮点(占8Byte) 均采用低位字节在前方式存储:即字节由低 B1 到高 Bn 上下排列,字节位由,高 b7 到b0 左右排列,格式如下表所示。

b7 b6 b5 b4 b3 b2 bl b0B1 字 节
b7 b6 b5 b4 b3 b2 bl b0B2字节
b7 b6 b5 b4 b3 b2 bl b0B3 字 节

案例一:字符串规则:字符串采用顺序存储,以二进制0结尾 如电压监测装置 IP 地址为“192.168.1.1

image.png 顺序存储,二进制0结尾,推算过程:

  • 192 十进制转换成16进制 0xC0 ,转换成二进制 1100 0000
  • 168 十进制转换成16进制 oxA8 , 转换成二进制 10110 1000
  • 1 十进制转换成16 进制 0x01 , 转换成二进制 0000 0001
  • 1 十进制转换成16 进制 0x01 , 转换成二进制 0000 0001

帧传输时:0xco, 0xa8, 0x01, 0x01

案例二:整型,采集时间以世纪秒表示,即从当地时间1970年1月1 日0时0分到指定时间过去的秒数,为长整型,占用四个字节。如16进制01020304世纪秒

image.png 低字节在前,高字节在后,推算过程

  • 04 十进制转换成16进制 0x04 ,转换成二进制 0000 0100
  • 03 十进制转换成16进制 0x03 ,转换成二进制 0000 00011
  • 02 十进制转换成16进制 0x02 ,转换成二进制 0000 00010
  • 01 十进制转换成16进制 0x01 ,转换成二进制 0000 00001

帧传输时:0x04, 0x03, 0x02, 0x01 注意:进制转换参考2468 方法

4.4 报文类型

  • 心跳数据报文:电压监测装置向CAC发送心跳数据以及CAC 向电压监测装置返回确认信息的报文,报文类型包括:心跳数据报。
  • 监测数据报文:电压监测装置向CAC发送监测数据以及CAC 向电压监测装置返回确认信息的报文,报文类型包括:电压数据报、日统计数据报、月统计数据报。
  • 数据请求报文:CAC 向电压监测装置发送数据请求以及电压监测装置向CAC 返回确认信息的报文,报文类型包括:数据请求报。
  • 配置/状态交互报文:CAC 向电压监测装置发送控制指令以及电压监测装置向CAC 响应控制指令的报文,报文类型包括: 装置时间查询/设置、装置通信参数查询/设置、监测点参数查询/设置、装置事件参数查询/设置、装置所 属 CAC 的信息查询/设置、装置基本信息查询、装置工作状态信息查询、装置通信流量信息查询、装置ID 查询/设置、装置复位、装置调试命令交互等
  • 流量数据报文:电压监测装置向 CAC 发送当月通信流量数据以及CAC 返回确认信息的报文,报文类型包括:流量数据报。
  • 事件信息报文:电压监测装置向 CAC 发送事件信息以及CAC 返回确认信息的报文,报文类型包括:事件信息报。
  • 远程升级数据报文:CAC 向电压监测装置发送远程升级数据以及电压监测装置返回确认信息的报文,报文类型包括:升级启动数据报、升级过程数据报、升级结束数据报。
序号报文分类类型值含 义
1心跳数据报文(0x01~0x03)0x01心跳数据报
20x02-0x03预留
3监测数据报文(0x04~0xA0)0x04电压数据报
40x05日统计数据报
50x06月统计数据报
60x07-0xA0预留
7数据请求报文(0xA1~0xA3)0xA1数据请求报
80xA2-0xA3预留
9配置/状态交互数据报文(0xA4~0xCO)0xA4装置时间查询/设置
100xA6装置通信参数查询/设置
110xA7监测点参数查询/设置
120xA8装置事件参数查询/设置
130xA9装置所属CAC的信息查询/设置
14OxAA装置基本信息查询
150xAB装置工作状态信息查询
16OxAC装置通信流量信息查询
17OxAD装置ID查询/设置
18OxAE装置复位
19OxAF装置调试命令交互
200xB0-0xC0预留
21流量数据报文(0xC1~0xC3)0xC1流量数据报
220xC2-0xC3预留
23事件信息报文(0xC4~0xC6)0xC4事件信息报
240xC5-0xC6预留
25远程升级报文(0xC7~0xCF)OxC7升级启动数据报
260xC8升级过程数据报
270xC9升级结束数据报
280xCA-OxCF预留

其余报文格式参考文档有相当详细的报文规范,一般有上报报文,和确认报文

4.5 案例分析

我们以心跳帧:

  • a5 5a 04 00 56 30 31 30 30 30 37 39 39 37 35 31 31 30 31 30 31 01 01 01 db 6d 78 65 7d 77 96
package voltage;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

import static voltage.CRC16.*;


/**
* @description: 登录帧案例说明
* @author: shu
* @createDate: 2023/12/12 12:57
* @version: 1.0
*/
public class VoltageTest {
public static void main(String[] args) {
short loginFrame[] = {
0xa5, 0x5a,
0x04, 0x00,
0x56, 0x30, 0x31, 0x30, 0x30, 0x30, 0x37, 0x39, 0x39, 0x37, 0x35, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31,
0x01,
0x01,
0x01,
0xdb, 0x6d, 0x78, 0x65,
0x7d, 0x77,
0x96
};

// 报文长度 2个字节
short[] length = {loginFrame[2], loginFrame[3]};
System.out.println("报文长度:" + parseLength(length));
// 电压监测统计仪ID
short[] addr = new short[17];
for (int i = 0; i < 17; i++) {
addr[i] = loginFrame[4 + i];
}
String addrStr = addressToString(addr);
System.out.println("电压监测统计仪ID:" + addrStr);
// 帧类型 1个字节
short frameType = loginFrame[21];
String frameTypeStr = parseFrameType(frameType);
System.out.println("帧类型:" + frameTypeStr);
// 1 个字节的报文类型
short messageType = loginFrame[22];
String messageTypeStr = parseMessageType(messageType);
System.out.println("报文类型:" + messageTypeStr);
// 1 个字节的报文序号: 0X01~0XFF
short messageNumber = loginFrame[23];
System.out.println("报文序号:" + messageNumber);
// 上面的数据都是固定的,下面的数据是变化的
// 上面报文长度
int length1 = parseLength(length);
System.out.println("内容长度:" + length1);
// 数据标识 4个字节
short[] dataIdentifier = new short[length1];
for (int i = 0; i < length1; i++) {
dataIdentifier[i] = loginFrame[24 + i];
}
// 这个是是时间格式为世纪秒
long centurySecond = parseTimeToCenturySecond(dataIdentifier);
System.out.println("电压监测装置当前时间:" + centurySecond);
// 世纪秒转换成时分秒
Instant instanta = Instant.ofEpochSecond(centurySecond);
LocalDateTime dateTimea = LocalDateTime.ofInstant(instanta, ZoneOffset.UTC);
System.out.println("转换后的日期时间为: " + dateTimea);
// 2个字节的校验位
byte[] checkSum = new byte[2];
for (int i = 0; i < 2; i++) {
checkSum[i] = (byte) loginFrame[24 + length1 + i];
}
int i = bytesToInt(checkSum);
System.out.println("校验位01:" + i);
byte[] checkedSum = checkSum(loginFrame);
int i1 = getCRCS(checkedSum);
System.out.println("校验位02:" + i1);
if (i == i1) {
System.out.println("校验成功, 接受该数据帧");
} else {
System.out.println("错误数据帧, 丢弃该数据帧");
}


}


/**
* 解析长度域
*
* @param length
* @return
*/
private static int parseLength(short[] length) {
return length[1] * 256 + length[0];
}


/**
* 将17个字节的地址域,转换成ASCII码
*/
public static String addressToString(short[] arrAddr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 17; i++) {
int parseInt = Integer.parseInt(String.valueOf(arrAddr[i]));
char asciiChar = (char) parseInt;
sb.append(asciiChar);
}
return sb.toString();
}


/**
* 解析帧类型
*/
public static String parseFrameType(short frameType) {
String frameTypeStr = "";
switch (frameType) {
case 0x01:
frameTypeStr = "心跳数据报文";
break;
case 0x02:
frameTypeStr = "心跳数据确认报文";
break;
case 0x03:
frameTypeStr = "监测数据报文";
break;
case 0x04:
frameTypeStr = "监测数据确认报文";
break;
case 0x05:
frameTypeStr = "监测数据请求报文";
break;
case 0x06:
frameTypeStr = "监测数据请求确认报文";
break;
case 0x07:
frameTypeStr = "配置/状态交互数据报文";
break;
case 0x08:
frameTypeStr = "配置/状态交互数据确认报文";
break;
case 0x09:
frameTypeStr = "流量数据报文";
break;
case 0x0a:
frameTypeStr = "流量数据确认报文";
break;
case 0x0b:
frameTypeStr = "流量数据请求报文";
break;
case 0x0c:
frameTypeStr = "事件信息报文";
break;
case 0x0d:
frameTypeStr = "事件信息确认报文";
break;
case 0x0e:
frameTypeStr = "远程升级数据报文";
break;
case 0x0f:
frameTypeStr = "远程升级数据确认报文";
break;
default:
frameTypeStr = "";
break;
}
return frameTypeStr;
}

/**
* 解析报文类型
*/
public static String parseMessageType(short messageType) {
String messageTypeStr = "";
switch (messageType) {
case 0x01:
messageTypeStr = "心跳数据报";
break;
case 0x04:
messageTypeStr = "电压数据报";
break;
case 0x05:
messageTypeStr = "日统计数据报";
break;
case 0x06:
messageTypeStr = "月统计数据报";
break;
case 0xA1:
messageTypeStr = "数据请求报";
break;
case 0xA4:
messageTypeStr = "装置时间查询/设置";
break;
case 0xA6:
messageTypeStr = "装置通信参数查询/设置";
break;
case 0xA7:
messageTypeStr = "监测点参数查询/设置";
break;
case 0xA8:
messageTypeStr = "装置事件参数查询/设置";
break;
case 0xA9:
messageTypeStr = "装置所属CAC的信息查询/设置";
break;
case 0xAA:
messageTypeStr = "装置基本信息查询";
break;
case 0xAB:
messageTypeStr = "装置工作状态信息查询";
break;
case 0xAC:
messageTypeStr = "装置通信流量信息查询";
break;
case 0xAD:
messageTypeStr = "装置ID查询/设置";
break;
case 0xAE:
messageTypeStr = "装置复位";
break;
case 0xAF:
messageTypeStr = "装置调试命令交互";
break;
case 0xC1:
messageTypeStr = "流量数据报";
break;
case 0xC4:
messageTypeStr = "事件信息报";
break;
case 0xC7:
messageTypeStr = "升级启动数据报";
break;
case 0xC8:
messageTypeStr = "升级过程数据报";
break;
case 0xC9:
messageTypeStr = "升级结束数据报";
break;
default:
messageTypeStr = "";
break;
}
return messageTypeStr;
}


/**
* 解析世纪秒
*/
public static long parseTimeToCenturySecond(short[] dataIdentifier) {
long seconds = 0;
for (int i = dataIdentifier.length - 1; i >= 0; i--) {
seconds = (seconds << 8) + (dataIdentifier[i] & 0xFF);
}
return seconds;
}


// 差错校验
public static byte[] checkSum(short[] arr) {
byte[] dataIdentifier = new byte[arr.length - 5];
// 第三个字节到倒数第三个字节
for (int i = 2; i < arr.length - 3; i++) {
dataIdentifier[i - 2] = (byte) arr[i];
}
return dataIdentifier;
}

}



image.png

  • CRS16 帧校验算法
package voltage;

public class CRC16 {


/**
* 计算CRC16校验码
*
* @param arr_buff
* @return
*/
public static int getCRCS(byte[] arr_buff) {
int len = arr_buff.length;
// 预置 1 个 16 位的寄存器为十六进制FFFF, 称此寄存器为 CRC寄存器。
int crc = 0xFFFF;
int i, j;
for (i = 0; i < len; i++) {
// 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
for (j = 0; j < 8; j++) {
// 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位
if ((crc & 0x0001) > 0) {
// 如果移出位为 1, CRC寄存器与多项式A001进行异或
crc = crc >> 1;
crc = crc ^ 0xA001;
} else {
// 如果移出位为 0,再次右移一位
crc = crc >> 1;
}
}
}
return crc;
}



/**
* 将int转换成byte数组,低位在前,高位在后
* 改变高低位顺序只需调换数组序号
*/
private static byte[] intToBytes(int value) {
System.out.println("value:" + value);
byte[] src = new byte[2];
src[0] = (byte) ((value >> 8) & 0xFF);
src[1] = (byte) (value & 0xFF);
return src;
}


/**
* 将字节数组转换成int
*/
static int bytesToInt(byte[] src) {
int value;
value = (int) ((src[0] & 0xFF) << 8);
value |= (int) (src[1] & 0xFF);
return value;
}

}