电网101规约解析(Java)

版权声明:本文为博主原创文章,未经博主允许不得转载。 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里的信息体的解析过程代码,有点多,就不一一贴出来了,代码我已经上传了,有想参考的直接下载

猜你喜欢

转载自blog.csdn.net/ZhangYu971014/article/details/79841555