电网101、104规约解析(Java)「建议收藏」

电网101、104规约解析(Java)「建议收藏」国家电网101规约解析(Java)最近在研究国家电网的101与104规约,也就是DL/T634.5101-2002和DL/T634.5104-2009。因为要做一个规约解析的软件(基于Android平台的),刚开始接触的也是一头雾水,因为没有接触过这方面的知识,所以就在网上搜索各种技术帖,大神经验什么的。后来在网上找到了一个软件–IEC8705(报文翻译工具).exe,这个##可以解析一…

大家好,又见面了,我是你们的朋友全栈君。

1. 前言

最近在研究广东电网的101与104规约,也就是DL/T634.5101-2002DL/T634.5104-2009。因为要做一个规约解析的软件(基于Android平台的),刚开始接触的也是一头雾水,因为没有接触过这方面的知识,所以就在网上搜索各种技术帖,大神经验什么的。

后来在网上找到了一个软件–IEC8705(报文翻译工具).exe,这个可以解析一些101(平衡式)的实例,效果图贴一下。

但有些还是解析不了,并且在网上也找不到他的源码,所以就很苦恼。也找到一些C++的源码,但是由于技术有限,我看不懂。下面就和大家来看看我写的这个简单的Java语言的规约解析。先贴一下效果图(我就在控制台简单演示一下,没有做可视化界面)。

GitHub地址:https://github.com/mujave/iec 欢迎star哦

2. Java版效果图

3. 规约详细解释

先看一下我总结的关于规约解析的几个图
101的规约总结
在这里插入图片描述
104规约总结
在这里插入图片描述具体的104协议解释也可以参考 https://blog.csdn.net/wojiuguowei/article/details/79413142

4. 名词解释

这里所说的名词解释,并不是对一些名词的定义的解释,因为这不是我的专业领域,下面我主要针对报文所代表的名词在代码里怎样解释(解析)说一下。

  1. 控制域(平衡式)
    在这里插入图片描述
    在报文解析中,我就是按照图里面的取值方法对对应的字节进行取值,然后进行逻辑判断,将结果加到解析结果里面,由于后四位所代表的平衡链路功能码在这里是固定的,所以可以采取枚举或者反射(常量)的方式进行解析
  2. 地址域 (以104的信息体地址为例)
    在规约中地址才用的低前高后(低位在前高位在后)的规则
    例如:68 10 08 00 02 00 09 01 03 00 01 00 01 40 00 2B 02 00(黄色部分为信息体地址)
    在这里插入图片描述
    所以 这个信息体的地址为01 40 00 >> 0x004001=16385
  3. 时标CP56Time2a
    在这里插入图片描述
    解析代码如下
	/**
     * 时标CP56Time2a解析
     *
     * @param b 时标CP56Time2a(长度为7 的int数组)
     * @return 解析结果
     */
    public static String TimeScale(int b[]) {

        StringBuilder result = new StringBuilder();

        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];

        result.append("时标CP56Time2a:20");
        result.append(year).append("-");
        result.append(String.format("%02d", month)).append("-");
        result.append(String.format("%02d", day)).append(",");
        result.append(hour).append(":").append(minute).append(":");
        result.append(second / 1000 + "." + second % 1000).append("\n");

        return result.toString();
    }
  1. 遥测量的解析
    在101的遥测中定义到有三种类型的值,分别是归一化值,标度值,短浮点数,具体的定义大家可以参考“电力101/104规约中遥测量类型转换”,下面我说一下在Java中怎么解析这三种类型的值。直接上代码了。
public class Main {

    public static void main(String[] args) {
        Telemetry telemetry = new Telemetry();
        System.out.println(telemetry.Bytes2Float_NVA(0xa4, 0x6a));// 报文数据的为A4 6A(低位在前,高位在后) ,为0x6AA4
        System.out.println(telemetry.Bytes2Float_SVA(0xa4, 0x6a));// 报文数据的为A4 6A(低位在前,高位在后) ,为0x6AA4

        System.out.println(telemetry.Bytes2Float_IEEE754("3e0779a6"));
        //报文数据 A6 79 07 3E = 0x3e0779a6 = 0.1323(IEEE754 短浮点数)http://lostphp.com/hexconvert/
    }
}

/**
 * 遥测量解析
 *
 * @author zhangyu
 */

public class Telemetry {

    /**
     * 归一化值
     */
    public static float Bytes2Float_NVA(int low, int high) {
        float fVal;
        int nva = (high << 8) + low;
        // 符号位1位,0为正数,1为负数,后面的为补码表示,
        // 正数的补码和原码相同不需要转换。对于负数,先取反码再加1得到补码的补码,就是原码了。
        int symbol = (high & 0x80);//符号位:0表示正数,1表示负数
        if (symbol == 0x80)
            fVal = -1 * (((nva ^ 0xffff) + 1) & 0x7fff);//  (nva ^ 0xffff) + 1 :补码的补码  ,取出后面的15位数据乘上-1得到值
        else fVal = nva;
        return fVal / 32767;//这里的32767值的是一个量纲,一般这个参数是主站和从站商定的
    }

    /**
     * 标度化值
     */
    public static float Bytes2Float_SVA(int low, int high) {
        float fVal;
        int nva = (high << 8) + low;
        int symbol = (high & 0x80);//0表示正数,1表示负数
        if (symbol == 0x80)
            fVal = -1 * (((nva ^ 0xffff) + 1) & 0x7fff);
        else fVal = nva;
        return fVal;
    }

    /**
     * 短浮点数
     *
     * @param data 从低位到高位按顺序
     * @return
     */
    public static float Bytes2Float_IEEE754(String data) {

        return Float.intBitsToFloat(Integer.valueOf(data, 16));
    }
}

对于短浮点数的计算,在Java的文档里有这样的说明。
在这里插入图片描述
还有一些ASDU里的信息体的解析过程代码,有点多,就不一一贴出来了
代码已经发布到GitHub,需要的伙伴可以直接下载

5.104规约解析遇到的一些难题

  1. 报文类型的区分
    101报文区分定长和变长两种,而104规约都是变长的报文。但是对于104来说,其控制域决定了104报文分别属于三种类型(I帧S帧U帧)。所以在解析104到控制域的时候,是在解析104的第一个难题。我先说一下我自己的区分逻辑。
    在这里插入图片描述
  • 我是用控制域的第一字节去和3做位运算(因为和3做位运算就可以得到第一字节的D1/D0两位),从上面的图中我们可以看到,结果如果是1的话就是S格式,如果是3的话就是U格式,其他 的结果就是I格式的报文。
  • 第二种方式是我在和一个网友交谈中得到的,他是运用奇数或者偶数来区分的,这里我列一个表格 ,这样的话如果第一个字节是偶数就是I帧,否则判断第三字节是0就是U帧。这样也可以区分报文的类型

但是我认为还是做位运算效率会更高,计算奇偶性还需要用到%这样的运算。

类型 第一字节 第二字节 第三字节 第四字节
S帧 奇(1) 0
I帧
U帧 0 0 0
  1. 位运算的运用

在解析规约的时候,我一般都是运用的位运算(&),这样既可以提高程序的效率,也有助于理解。首先在规约的定义中有很多的一个字节中的不同位代表一个含义,这种时候就运用到了位运算,这样就可以直接将对应位的值提取出来,例如那可变结构限定词这个字节来举个例子。最高位的D7代表的是信息元素地址的连续性,其中0不连续1连续,所以我们只需要拿这个字节和128(0x80)做位运算就可以得到D7这一位,如果结果是10000000就说明是1连续。这个地方更直观一点的判断方法就是((B&0x80)>>7)==1?连续 : 不连续。
在这里插入图片描述

  1. 时标的解析

在这里插入图片描述
首先时标这里一共有7个字节,下面对这几个字节的含义以及解析方法做一下解释:

  • 年(byte 7):这里只有后面的7位有效,但是第8位填充的是0,所以这一个字节直接转换形成int就可以了。
  • 月(byte 6):同上
  • 小时(byte 4):同上
  • 分钟(byte 3):同上
  • 日(byte 5[bit 1~5]):第五字节的后5位表示的是日,byte5 & 0x1F 就可以得到(0x1F = 0001 1111),例如第五字节是87(87 = 0x57 = 0101 0111),其中010指的是星期,10111指的是day。这里使用87 & 0x1F就可以得到23。使用87 & 0xE0就可以得到64,然后再用这个结果除以32就可以得到结果,如果说除以32不太理解的话,也可以用结果右移运算64>>5也可以得到结果 。
  • 毫秒(byte 1 and byte 2):这个地方首先是低前高后的问题(这个为题不太明白的可以先翻一下下面的第四个问题),( byte 2 << 8 ) + byte 1 ,这个地方我用一个表格说明一下(例如第一字节是6E,第二字节是2A)。

在这里插入图片描述

/** * 时标CP56Time2a解析 */
public static 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 week = (b[4] & 0xE0) >> 5;
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进制字符串 */
public static String date2HStr(Date date) { 

Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
StringBuilder builder = new StringBuilder();
String milliSecond = String.format("%04X", (calendar.get(Calendar.SECOND) * 1000) + calendar.get(Calendar.MILLISECOND));
builder.append(milliSecond.substring(2, 4));
builder.append(milliSecond.substring(0, 2));
builder.append(String.format("%02X", calendar.get(Calendar.MINUTE) & 0x3F));
builder.append(String.format("%02X", calendar.get(Calendar.HOUR_OF_DAY) & 0x1F));
int week = calendar.get(Calendar.DAY_OF_WEEK);
if (week == Calendar.SUNDAY)
week = 7;
else week--;
builder.append(String.format("%02X", (week << 5) + (calendar.get(Calendar.DAY_OF_MONTH) & 0x1F)));
builder.append(String.format("%02X", calendar.get(Calendar.MONTH) + 1));
builder.append(String.format("%02X", calendar.get(Calendar.YEAR) - 2000));
return builder.toString();
}
  1. 低前高后的问题

这里讲一下低前高后的问题,拿这个104规约的信息对象地址举例。例如34 12 00这是原报文的字节,再具体解析的时候我们要把它转成0x001234=4660,其实这里和上面的毫秒的解析方式一样。
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/151343.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(2)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号