下面這是我們要控制的設備,它本身包含RS485的通訊界面,並使用Modbus RTU作為通訊協定,
我們可以用電腦或一個開發版,例如Raspberry pi作為溝通的主機。以下程式說明如何連上設備、並對設備進行讀取,然後將設備的slaveID從01改成02,注意,此設備要求進行寫入動作時,需要先寄送一個驗證碼,並於驗證成功後才能進行寫入,因此在此範例中,我們會用到3(讀取寄存器)、16(寫入多個寄存器)、22(Mask寫入操作、這邊用來作為驗證用)這三個功能碼。
輸入的Modbus RTU 命令 Hex形式 分別如下 (關於Modbus RTU 命令請參考這裡)
讀取日期時間: 01 03 00 12 00 04 e4 0c
發送驗證: 01 16 08 08 00 02 04 1d 2c 3b 4a 20 b4
修改表號(從01變成02): 01 10 00 0e 00 01 02 00 02 26 bf
golang的程式碼如下:
package main
import (
"fmt"
"time"
modbus "github.com/thinkgos/gomodbus/v2"
)
func main(){
//根據規格,先設定連線
p := modbus.NewRTUClientProvider()
p.Address = "COM3"
p.BaudRate = 9600
p.DataBits = 8
p.Parity = "N"
p.StopBits = 1
p.Timeout = 1 * modbus.SerialDefaultTimeout
//產生client並連線
client := modbus.NewClient(p)
client.LogMode(true)
err := client.Connect()
if err != nil {
fmt.Println("connect failed, ", err)
return
}
defer client.Close()
//進行讀取日期時間: 01 03 00 12 00 04 e4 0c
fmt.Println("讀取日期時間 start")
result, err := client.ReadHoldingRegisters(1, 18, 4)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Printf("讀取日期時間結果 %#v\r\n", result)
}
fmt.Println("確認日期時間 finished")
//進行發送驗證: 01 16 08 08 00 02 04 1d 2c 3b 4a 20 b4
fmt.Println("寄送金鑰start")
aduRequest := []byte{1, 22, 8, 8, 0, 2, 4, 29, 44, 59, 74, 32, 180}
aduResponse, err := client.SendRawFrame(aduRequest)
if err != nil {
fmt.Println(err.Error()) // 會出現timeout,但實際上成功了
} else {
fmt.Println(aduResponse)
}
fmt.Println("金鑰驗證 finished")
//進行修改表號(從01變成02): 01 10 00 0e 00 01 02 00 02 26 bf
fmt.Println("修改表號start")
err = client.WriteMultipleRegisters(1, 14, 1, []uint16{2})
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("WriteMultipleResigers finished")
}
說明:
- 對於功能碼03、16的部分,可以直接使用gomodbus的函數,分別是ReadHoldingRegisters及WriteMultipleRegisters,根據原始命令轉乘函數的輸入參數即可達成(如上述程式碼,須注意函數的輸入參數多為byte 或是uint16的格式,我們需要將原本的hex digit的命令轉成byte或是uint16,例如0e轉成14、b4轉成180 etc.)。
- 關於發送驗證的部分,使用的功能碼為22( hex digit: 16),我們查詢功能碼可得知為Mask Write Register(關於此功能碼較為複雜,包含了又AND Mask及 OR Mask對寄存器進行操作寫入,這邊不降息說明,若有興趣可以google一下囉),gomodbus的函數為 client.MaskWriteRegister(),但使用後發現會timeout,研究後發現原來此款巧力電表中的驗證方式與原本的Mask Write Register一般的定義稍有不同。
- 一般的Mask Write Register的cmd為 01 16 08 08 1d 2c 3b 4a CRC CRC,但我們這邊電表的驗證命令為01 16 08 08 00 02 04 1d 2c 3b 4a CRC CRC,可看出多了中間三碼,導致原本的gomodbus MaskWriteRegister函數無法達成我們的需求,無法傳送出正確的命令。
- 因此,我們使用gomodbus的另一個函數 SendRawFrame(),來直接傳送我們的Modbus RTU cmd給設備,這邊要注意的是需要我們自己處理CRC的計算(也可以直接copy gomodbus中的 CRC.go內的函數)
- 另外,由於設備回傳的訊息格式也跟一般的不一樣,因此SendRawFrame後會出現timeout,這是因為gomodbus的SendRawFrame要去讀取回復的訊息,但因為回復訊息的長度不一樣,導致一直等不到要讀的內容,以我們的使用情境來講,此時實際上已經完成驗證,不需要去處理回覆訊息長度的問題,可直接進行下一步。
- 若不希望有上述timeout的產生,就必須回過頭來修改gomodbus的source code了,若有需求或有興趣的朋友再留言給我,我可以再詳細說明~~
- 提醒一下,表號從01改成02後,再使用上述的程式時記得修改呼叫的命令的slaveID(第一碼),以及因為命令改過後的CRC,不要像筆者一樣第二次測試時不會動覺得很奇怪。