Tips
Modbus設(shè)備可以使用云端輪詢和邊緣網(wǎng)關(guān)兩種方案
一、云端輪詢實(shí)現(xiàn)
- 方案實(shí)現(xiàn)原因:普通的dtu或模組一般只有實(shí)現(xiàn)mqtt或tcp的透?jìng)鞴δ?,如果設(shè)備使用modbus協(xié)議,不外加邊緣網(wǎng)關(guān)的情況,modbus協(xié)議的設(shè)備數(shù)據(jù)不會(huì)主動(dòng)上報(bào),因此云端根據(jù)產(chǎn)品對(duì)應(yīng)的采集點(diǎn)模板,組裝modbus讀指令下發(fā)到對(duì)應(yīng)的設(shè)備,進(jìn)行modbus輪詢。
- 由于設(shè)備上報(bào)modbusRTU報(bào)文沒有寄存器標(biāo)記位,需要標(biāo)記報(bào)文。
- 目前采用的標(biāo)記方式是加給線程加redis鎖,,等待設(shè)備返回報(bào)文。報(bào)文如下:

實(shí)現(xiàn)方式如下:

Modbus協(xié)議的解碼編碼
1. 平臺(tái)代碼
在fastBee平臺(tái)中,后端對(duì)該報(bào)文的解碼編碼在下圖所示位置

2.注解實(shí)現(xiàn)解碼編碼
在平臺(tái)中 ,封裝了一套以注解方式解碼編碼硬件設(shè)備報(bào)文的基礎(chǔ)包,針對(duì)各種各樣的硬件設(shè)備報(bào)文,都可以使用這種注解方式去實(shí)現(xiàn)解碼編碼,
減少后端開發(fā)人員在報(bào)文數(shù)據(jù)解碼編碼中繁瑣的問(wèn)題。

下面看下對(duì)應(yīng)封裝了一層的 **“包裝的modbus協(xié)議” ,**使用注解方式如何做到解碼編碼
首先看下注解 @Column的字段如何定義:
這里使用字段
- length 指定類型長(zhǎng)度
- version 這里用于區(qū)分是下發(fā)的報(bào)文還是上報(bào)的報(bào)文
- totalUnit 該字段的前置數(shù)量單位 (用于解析批量讀返回的報(bào)文)

注解定義解碼編碼modbus協(xié)議
如果你不關(guān)心報(bào)文如何解析,可以跳過(guò)這里
代碼如下:
int: 使用INT類型表示 1個(gè)字節(jié)的報(bào)文長(zhǎng)度 ,這里INT類型相當(dāng)于byte類型(在Java,INT類型占用字節(jié)為4個(gè)字節(jié),這里INT類型會(huì)被按照2個(gè)字節(jié)進(jìn)行解析)
totalUnit: 表示該字段的前置數(shù)量單位,例如 totalUnit = 1 ,總數(shù)據(jù)長(zhǎng)度為 n,數(shù)據(jù)列表數(shù)為 BYTE[2 * n]
version : 此處用于控制指令類型:
a.當(dāng)有version值時(shí),需要符合條件時(shí)該字段才會(huì)被解碼編碼 b. 沒有version標(biāo)記時(shí),都參與解碼編碼。 version =0 :表示設(shè)備上報(bào)時(shí),該字段才會(huì)被解析 version = 1 :表示讀寄存器報(bào)文時(shí),該字段才會(huì)被解析 version =2 :表示寫單個(gè)保持寄存器時(shí), 該字段才會(huì)被解析


3. 后端實(shí)現(xiàn)云端輪詢
后端使用定時(shí)器方式實(shí)現(xiàn)modbus輪詢。

modbus云端輪詢的后端入口,如下圖所示
篩選在線的網(wǎng)關(guān)設(shè)備,根據(jù)產(chǎn)品定義的采集點(diǎn),批量下發(fā)讀指令。
使用線程池方式,把每一個(gè)子設(shè)備所有要下發(fā)的指令放在同一個(gè)子線程下發(fā),使用redis緩存作為線程的鎖也同時(shí)作為寄存器的標(biāo)記內(nèi)容,對(duì)于每一個(gè)子線程對(duì)應(yīng)一個(gè)子設(shè)備,一個(gè)子設(shè)備對(duì)應(yīng)多條需要批量讀的下發(fā)指令,當(dāng)下發(fā)指令時(shí),子線程會(huì)不斷加鎖輪詢,直到超過(guò)redis緩存設(shè)定的緩存時(shí)間,或者收到設(shè)備上報(bào)的數(shù)據(jù)時(shí),才會(huì)繼續(xù)下發(fā)該子設(shè)備的下一條指令。具體請(qǐng)參考后端代碼。

二、邊緣網(wǎng)關(guān)
Tips
邊緣網(wǎng)關(guān)配合云端采集(實(shí)時(shí))
1. 方案介紹
由于modbus協(xié)議的特性,設(shè)備上報(bào)到云平臺(tái)的報(bào)文,缺少可識(shí)別的寄存器地址值,所以平臺(tái)識(shí)別不了寄存器地址所對(duì)應(yīng)的物模型采集點(diǎn),因此為了提高采集效率,和采集數(shù)據(jù)的實(shí)時(shí)性,再考慮到通用性問(wèn)題,這里方案考慮使用外加MCU芯片的做法,實(shí)現(xiàn)邊緣網(wǎng)關(guān)。
實(shí)現(xiàn)硬件:
- 用戶自身的設(shè)備(MCU)
- dtu或模組 (透?jìng)鳎?/li>
- 外加單片機(jī)芯片 (modbus輪詢,變化上報(bào) (成本低))
dtu或者模組可以
2. 報(bào)文數(shù)據(jù)
為了解決modbus原始報(bào)文寄存器地址的問(wèn)題,外加MCU模塊會(huì)對(duì)modbus協(xié)議再封裝一層,設(shè)備數(shù)據(jù)組成:
- a. 設(shè)備主動(dòng)上報(bào)數(shù)據(jù)組成:
FFAA : 外加的報(bào)文頭,保證消息完整
0D : 外加報(bào)文尾,保證消息完整
0001:寄存器地址,用于標(biāo)識(shí)消息
010302030578B7 : 原始modbus報(bào)文

- b. 服務(wù)下發(fā)數(shù)據(jù)組成

- c. 設(shè)備應(yīng)答服務(wù)下發(fā)數(shù)據(jù)組成
modbus協(xié)議設(shè)備在收到寫指令執(zhí)行完成時(shí),會(huì)將下發(fā)的寫指令主動(dòng)上報(bào)給平臺(tái)表示已經(jīng)執(zhí)行成功。

3. 總體流程圖如下:

4. 平臺(tái)代碼
在fastBee平臺(tái)中,后端對(duì)該報(bào)文的解碼編碼在下圖所示位置

注解實(shí)現(xiàn)解碼編碼
在平臺(tái)中 ,封裝了一套以注解方式解碼編碼硬件設(shè)備報(bào)文的基礎(chǔ)包,針對(duì)各種各樣的硬件設(shè)備報(bào)文,都可以使用這種注解方式去實(shí)現(xiàn)解碼編碼,
減少后端開發(fā)人員在報(bào)文數(shù)據(jù)解碼編碼中繁瑣的問(wèn)題。

下面看下對(duì)應(yīng)封裝了一層的 **“包裝的modbus協(xié)議” ,**使用注解方式如何做到解碼編碼
首先看下注解 @Column的字段如何定義:
這里使用字段
- length 指定類型長(zhǎng)度
- version 這里用于區(qū)分是下發(fā)的報(bào)文還是上報(bào)的報(bào)文
- totalUnit 該字段的前置數(shù)量單位 (用于解析批量讀返回的報(bào)文)

下面看下這個(gè)報(bào)文的定義
在java中,所有的基礎(chǔ)數(shù)據(jù)類型是不存在有符號(hào)無(wú)符號(hào),
int: 使用INT類型表示 1個(gè)字節(jié)的報(bào)文長(zhǎng)度 ,這里INT類型相當(dāng)于byte類型(在Java,INT類型占用字節(jié)為4個(gè)字節(jié),這里INT類型會(huì)被按照2個(gè)字節(jié)進(jìn)行解析)
在下面定義中 FFDD 是兩個(gè)字節(jié),length=2 ,用int類型定義表示一個(gè)字節(jié),那么這個(gè)字段的解析長(zhǎng)度為: 2個(gè)字節(jié)
short:表示兩個(gè)字節(jié),modbus數(shù)據(jù)位的組成是2個(gè)字節(jié),用 short[] 表示是short數(shù)組,這里兼容了單個(gè)和批量解析報(bào)文里面的數(shù)據(jù)位。
最終解析出來(lái)的每個(gè)數(shù)據(jù)存放在 data數(shù)組中
totalUnit: 表示該字段的前置數(shù)量單位,例如 totalUnit = 1 ,總數(shù)據(jù)長(zhǎng)度為 n,數(shù)據(jù)列表數(shù)為 BYTE[2 * n]

注解方式解碼編碼測(cè)試
后端代碼測(cè)試類為**:PakModbusTest **,如下圖所示。

結(jié)果如下:
單個(gè)上報(bào)解析=讀保持寄存器(字節(jié)讀寫模式)[,slaveId[從機(jī)地址]=1,code[功能碼]=3,length[返回?cái)?shù)據(jù)個(gè)數(shù)]1,data[上報(bào)數(shù)據(jù)]=[773],address[寄存器地址]=1,writeData[下發(fā)數(shù)據(jù)]=0]
批量上報(bào)報(bào)文解析=讀保持寄存器(字節(jié)讀寫模式)[,slaveId[從機(jī)地址]=1,code[功能碼]=3,length[返回?cái)?shù)據(jù)個(gè)數(shù)]80,data[上報(bào)數(shù)據(jù)]=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16384, 0, 0, 0, 3327, 924, 32, 0, -1, -1, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 0, -55, 16, 25, 30, 16, 25, 25, 87, 7],address[寄存器地址]=1,writeData[下發(fā)數(shù)據(jù)]=0]