版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZhangYu971014/article/details/79841555
国家电网101规约解析(Java)
最近在研究国家电网的101与104规约,也就是DL/T634.5101-2002和DL/T634.5104-2009。因为要做一个规约解析的软件(基于Android平台的),刚开始接触的也是一头雾水,因为没有接触过这方面的知识,所以就在网上搜索各种技术帖,大神经验什么的。
后来在网上找到了一个软件–IEC8705(报文翻译工具).exe,这个## 可以解析一些101(平衡式)的实例,效果图贴一下。
但有些还是解析不了,并且在网上也找不到他的源码,所以就很苦恼。也找一些C++的源码,但是由于技术有限,我看不懂。下面就和大家来看看我写的这个简单的Java语言的规约解析。先贴一下效果图(我就在控制台简单演示一下,没有做可视化界面)。
看一下代码
程序入口
public class main {
//完成报文的输入和打印解析结果
public static void main(String[] args) {
while (true) {
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
if (message == null) {
System.out.println("报文为空!请正确输入!");
}
// 去掉字符串里的空格
String mes = message.replaceAll(" ", "");
// 如果报文去掉空格后,字符串的长度是奇数或者0的话,说明报文是错误的
if ((mes.length() == 0) || (mes.length() % 2) == 1) {
System.out.println("报文为16进制数据,报文数据间用空格隔开!");
continue;
}
// 将字符串转化成字节数组
int b[] = new Util().toIntArray(mes);
// System.out.println(message);
System.out
.println("--------------------------------------------------");
String analysis = "";
try {
analysis = new Analysis().analysis(b);
System.out.println(analysis);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
上面一段代码主要用来完成接受控制台输入的报文,然后判断报文是否符合规定的格式,然后转换成int数组(16进制转换成10进制的保存在int数组里,刚开始想过byte数组,单byte只能表示-128~127,所以就用了int数组),然后发到Analysis类进行具体的解析。
Analysis类
public class Analysis {
private String content;
private Util util;
public String analysis(int message[]) throws Exception {
int lenth = message.length;
content = "";
util = new Util();
if (message[0] == 0x68) {
content += "启动符:68 变长报文\n";
if ((message[1] + 6 != message.length)
|| (message[1] != message[2])) {
if (message[1] != message[2]) {
throw new Exception("帧长度错误:报文第一字节和第三字节不一致!");
} else {
throw new Exception("帧长度错误:帧长度" + message.length
+ ",期望的帧长度" + (message[1] + 6));
}
}
content += "报文长度:" + message[1] + "字节\n";
content += "链路地址:";
content += util.address(message[5], message[6]);
content += "控制域:" + util.toHexString(message[4]) + " ";
// 解析控制域
content += new Control().control(message[4]);
content += "\n";
content += "-----------------ASDU解析内容-----------------------\n";
// 解析ASDU内容
int[] asdu = new int[message.length - 9];
for (int j = 0, i = 7; i < message.length - 2; i++, j++) {
asdu[j] = message[i];
}
content += new ASDU().asdu_analysis(asdu);
// 校验CS校验
int CS[] = new int[message.length - 6];
for (int i = 4, j = 0; i < message.length - 2; i++, j++) {
CS[j] = message[i];
}
content += util.variableCS(CS, message[message.length - 2]);
} else if (message[0] == 0x10) {
content += "启动符:10 定长报文\n";
if (message.length != 6) {
throw new Exception("帧长度错误:定长报文长度应为6,当前报文长度为" + message.length);
}
content += "dhugvcdgvg";
content += new Control().control(message[1]);
content += "\n地址域:" + util.address(message[2], message[3]);
content += util.variableCS(new int[] { message[1], message[2],
message[3] }, message[4]);
} else {
content += "报文头格式错误!\n";
}
return content;
}
}
ASDU类
解析变长报文的ASDU
public class ASDU {
public final String TI1 = "单点信息";
public final String TI3 = "双点信息 ";
public final String TI9 = "测量值,归一化值 ";
public final String TI11 = "测量值,标度化值 ";
public final String TI13 = "测量值,短浮点数 ";
public final String TI30 = "带CP56Time2a时标的单点信息 ";
public final String TI31 = "带CP56Time2a时标的双点信息 ";
public final String TI45 = "单命令(遥控)";
public final String TI46 = "双命令(遥控)";
public final String TI48 = "预置/激活单个参数命令";
public final String TI102 = "读单个参数命令";
public final String TI70 = "初始化结束 ";
public final String TI100 = "召唤命令 ";
public final String TI103 = "时钟同步/读取命令 ";
public final String TI104 = "测试命令 ";
public final String TI105 = "复位进程命令";
public final String TI132 = "读多个参数命令";
public final String TI136 = "预置/激活多个参数命令";
public Util util = new Util();
public String str = "";
/**
* 解析ASDU应用服务数据单元
*
* @param asdu
* @return
* @throws Exception
* @throws Exception
*/
public String asdu_analysis(int[] asdu) throws Exception {
// 解析ASDU中的类型标识符TI
str += TI_Analysis(asdu[0]);
// 解析ASDU中的可变结构限定词
str += VariTureDete(asdu[1]);
// 解析传送原因
String transfer_reason = str += new TransferReason()
.transfer_reason(asdu[2]);
// 解析应用服务数据单元公共地址
str += "应用服务数据单元公共地址:";
str += util.address(asdu[3], asdu[4]);
// 解析信息元素
int num = asdu[1] & 0x7F;// 信息元素的个数
// 将信息元素集撞到数组中
int infoElement[] = new int[asdu.length - 5];
for (int i = 0; i + 5 < asdu.length; i++) {
infoElement[i] = asdu[i + 5];
}
int SQ = (asdu[1] & 0x80) / 128;
str += new InfoElement().info_element(infoElement, num, asdu[0], SQ);
return str;
}
/**
* 解析ASDU中的可变结构限定词
*
* @param asdu中的第二个字节
* @return 返回SQ和信息元素的个数
*
*/
private String VariTureDete(int b) {
String vvString = "可变结构限定词:";
vvString += util.toHexString(b);
vvString += "[bit7:";
if ((b & 0x80) == 128) {
vvString += "SQ=1-信息元素顺序]";
} else if ((b & 0x80) == 0) {
vvString += "SQ=0-信息元素非顺序]";
} else {
vvString += "SQ解析错误!";
}
vvString += " [bit0~bit6:信息元素个数:";
vvString += b & 0x7f;
return vvString + "]\n";
}
/**
* 解析ASDU中的类型标识符TI
*
* @param asdu中的第一个字节
* @return 返回解析结果
* @throws Exception
*
*/
private String TI_Analysis(int asdu) throws Exception {
String str = "";
String name = "TI";
name += asdu;
Field field;
try {
field = this.getClass().getField(name);
str += "类型标识TI=" + asdu + " " + field.get(this).toString()
+ "\n";
} catch (Exception e) {
throw new Exception("未找到对应的类型标识!");
}
return str;
}
}
Control类
用来解析控制域
public class Control {
public final String PI0 = "复位远方链路";
public final String PI1 = "复位用户进程";
public final String PI2 = "发送/确认链路测试功能 ";
public final String PI3 = "发送/确认用户数据";
public final String PI4 = "发送/无回答用户数据";
public final String PI9 = "请求/响应请求链路状态";
public final String PI11 = "响应:链路状态";
public String control(int b) throws Exception {
String str = "";
str += "平衡链路功能码:";
str += b & 0x0f;
str += " ";
String name = "PI";
name += b & 0x0f;
Field field;
try {
field = this.getClass().getField(name);
str += field.get(this).toString() + "\n";
} catch (Exception e) {
throw new Exception("未找到相应链路功能!");
}
if ((b & 0x80) == 0) {
str += "DIR=0 传输方向:下行报文";
str += upriver(b);
} else if ((b & 0x80) == 128) {
str += "DIR=1 传输方向:上行报文";
str += downriver(b);
}
return str;
}
/**
* 上行报文
*
* @param 控制域
* @return PRM RES DFC
*/
private String downriver(int b) {
String ddString = "";
if ((b & 0x40) == 64) {
ddString += "\nPRM=1 报文来自启动站";
} else if ((b & 0x40) == 0) {
ddString += "\nPRM=0 报文来自从动站";
}
if ((b & 0x20) == 32) {
ddString += "\nRES=1 ";
} else if ((b & 0x20) == 0) {
ddString += "\nRES=0 ";
}
if ((b & 0x10) == 16) {
ddString += "\nDFC=1 从动站不能接收后续报文 ";
} else if ((b & 0x10) == 0) {
ddString += "\nDFC=0 从动站可以接收后续报文 ";
}
return ddString;
}
/**
* 下行报文
*
* @param 控制域
* @return PRM FCB FCV
*/
private String upriver(int b) {
String uuString = "";
if ((b & 0x40) == 64) {
uuString += "\nPRM=1 报文来自启动站";
} else if ((b & 0x40) == 0) {
uuString += "\nPRM=0 报文来自从动站";
}
if ((b & 0x20) == 32) {
uuString += "\nFCB=1 ";
} else if ((b & 0x20) == 0) {
uuString += "\nFCB=0 ";
}
if ((b & 0x10) == 16) {
uuString += "\nFCV=1 FCB有效 ";
} else if ((b & 0x10) == 0) {
uuString += "\nFCV=0 表示FCB无效 ";
}
return uuString;
}
}
TransferReason类
解析传送原因
public class TransferReason {
public final String TR0 = "未用";
public final String TR1 = "周期、循环 ";
public final String TR2 = "背景扫描";
public final String TR3 = "突发(自发)";
public final String TR4 = "初始化完成";
public final String TR5 = "请求或者被请求";
public final String TR6 = "激活";
public final String TR7 = "激活确认";
public final String TR8 = " 停止激活 ";
public final String TR9 = "停止激活确认";
public final String TR10 = "激活终止 ";
public final String TR13 = "文件传输 ";
public final String TR20 = "响应站召唤(总召唤)";
public final String TR44 = "未知的类型标识";
public final String TR45 = "未知的传送原因";
public final String TR46 = "未知的应用服务数据单元公共地址";
public final String TR47 = "未知的信息对象地址";
public final String TR48 = "遥控执行软压板状态错误";
public final String TR49 = "遥控执行时间戳错误";
public final String TR50 = "遥控执行数字签名认证错误";
/**
* @param 传送原因
* @return 解析结果
* @throws Exception
*
*/
public String transfer_reason(int asdu) throws Exception {
String trstring = "传送原因:";
String name = "TR";
name += asdu;
Field field;
try {
field = this.getClass().getField(name);
trstring += field.get(this).toString() + "\n";
} catch (Exception e) {
throw new Exception("未找到对应的传送原因!");
}
return trstring;
}
}
工具类
另外将报文转换成数组,解析地址域,解析时标,校验CS等我写成了一个util类
public class Util {
/**
* 时标CP56Time2a解析
*
* @param b
* 时标CP56Time2a(长度为7 的int数组)
* @return 解析结果
*/
public String TimeScale(int b[]) {
String str = "";
int year = b[6] & 0x7F;
int month = b[5] & 0x0F;
int day = b[4] & 0x1F;
int week = (b[4] & 0xE0) / 32;
int hour = b[3] & 0x1F;
int minute = b[2] & 0x3F;
int second = (b[1] << 8) + b[0];
str += "时标CP56Time2a:" + "20" + year + "-"
+ String.format("%02d", month) + "-"
+ String.format("%02d", day) + "," + hour + ":" + minute + ":"
+ second / 1000 + "." + second % 1000;
return str + "\n";
}
/**
* 16进制表示的字符串转换为字节数组
*
* @param s
* 16进制表示的字符串
* @return byte[] 字节数组
*/
public static int[] hexStringToIntArray(String s) {
int len = s.length();
int[] b = new int[len / 2];
for (int i = 0; i < len; i += 2) {
// 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
b[i / 2] = (int) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return b;
}
/**
* 16进制的字符串表示转成字节数组
*
* @param hexString
* 16进制格式的字符串
* @return 转换后的字节数组
**/
public static int[] toIntArray(String hexString) {
if (hexString.isEmpty()) {
}
hexString = hexString.toLowerCase();
final int[] byteArray = new int[hexString.length() / 2];
int k = 0;
for (int i = 0; i < byteArray.length; i++) {// 因为是16进制,最多只会占用4位,转换成字节需要两个16进制的字符,高位在先
int high = (int) (Character.digit(hexString.charAt(k), 16) & 0xff);
int low = (int) (Character.digit(hexString.charAt(k + 1), 16) & 0xff);
byteArray[i] = (int) (high << 4 | low);
k += 2;
}
return byteArray;
}
/**
* int转换成16进制字符串
*
* @param b
* 需要转换的int值
* @return 16进制的String
*/
public String toHexString(int b) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
return "0x" + hex.toUpperCase();
}
/**
* 检验CS校验和
*
* @param vCS
* 需要检验的部分
* @param i
* CS校验和
* @return 检验结果(字符串格式)
*/
public String variableCS(int[] vCS, int i) {
String str = "";
str += "校验和CS=";
str += toHexString(i);
int sum = 0;
for (int j = 0; j < vCS.length; j++) {
sum += vCS[j];
}
if ((sum % 256) == i) {
str += " 校验无误!";
} else {
str += " 经校验,报文有误!";
}
return str;
}
/**
*
*解析地址域
*
* @param a
*第一个地址
* @param b
*第二个地址
* @return
*/
public String address(int a, int b) {
String aString = Integer.toHexString(a & 0xFF);
String bString = Integer.toHexString(b & 0xFF);
if (aString.length() == 1) {
aString = '0' + aString;
}
if (bString.length() == 1) {
bString = '0' + bString;
}
return bString + aString + "H" + "\n";
}
}
还有一些ASDU里的信息体的解析过程代码,有点多,就不一一贴出来了,代码我已经上传了,有想参考的直接下载