前言

好久之前去年写了一篇基于Java的101/104电网规约解析的文章,在去年我把项目代码整理了一下,放到了github上面。然后有好多朋友找我,有的是因为代码有几处bug,有的是不太明白其中的一些逻辑。我也想了好久,趁今天周末写了这一篇文章,和大家聊一下其中的一些常见问题。

概述

101/104规约是什么我就不说了,今天这篇文章报文解析相关的问题会围绕104相关说明,因为104基于TCP协议传输,不像101也可以基于串口传输。而且我认为104的解析难度在101之上。好了下面我会按照网友的经常问到的一些问题详细解释一下。

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帧000
  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,其实这里和上面的毫秒的解析方式一样。
在这里插入图片描述

现在就先说这么多,各位有什么问题可以在底下留言或者vx(mujave_)一起交流

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐