基於Java的101/104電網規約解析詳述

前言

好久之前去年寫了一篇基於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幀 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,其實這裏和上面的毫秒的解析方式一樣。
在這裏插入圖片描述

現在就先說這麼多,各位有什麼問題可以在底下留言或者vx(mujave)一起交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章