これまでにpythonでModbus/TCPのリクエストとレスポンスの送受信について書いていますが、
それをまとめてデバイスのレジスタの値を読み取るスクリプトをGitHubで公開しました。
レジスタ読取りツール: modbus_reader.py
簡単に使いたかったので、modbus用のライブラリは使用せずsocket通信で送受信したbyte列を切り分けて値を読み取っています。
GitHub:
https://github.com/tyamazoe/modbus
使用方法
$python modbus_reader.py [ip address]
デバイスのIPを指定して実行すると、Unit ID, Function Code, アドレス等のパラメータの入力を対話式で要求されます。
パラメータは 16進の場合 0xを付けて指定します(例: 0x04)。10進数の場合はそのままです(例: 4)。
制限事項
現状はFunction Code は 0x03 (Read Holding Registers) と 0x04 (Read Input Registers)のみの対応です。
プログラム
# -*- coding: utf-8 -*-
#
# Modbus/TCP reader
#
import socket
import struct
import time
import traceback
import codecs
import sys
TARGET_IP = '192.168.1.99'
try:
TARGET_IP = sys.argv[1]
except IndexError:
pass # use defaults
TARGET_PORT = 502
buffer_size = 0
SUPPORTED_FC = (0x03, 0x04)
def get_param(msg):
param = input(msg)
hex_dec = 16 if "0x" in param.lower() else 10
return int(param, hex_dec)
try:
print("\nEnter Modbus Params in [0xnn (Hex)] or [nn (Dec)]")
unitId = get_param(" Unit Identifier: ")
functionCode = get_param(" Function Code: ")
if functionCode not in SUPPORTED_FC:
raise Exception("FC only support {}".format(SUPPORTED_FC))
startRegister = get_param(" Start Register: ")
numRegister = get_param(" Num of Registers: ")
# Send request
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((TARGET_IP, TARGET_PORT))
req = struct.pack('>3H 2B 2H', 0, 0, 6, unitId, functionCode, startRegister, numRegister)
sock.send(req)
# Receive response
buffer_size = 3*2 + 3 + numRegister*2
res = sock.recv(buffer_size)
print("\nTX: {0}".format(codecs.encode(req, 'hex_codec')))
print("RX: {0}".format(codecs.encode(res, 'hex_codec')))
if len(res) == buffer_size:
# normal response
s = struct.Struct('>3H 3B %sH' %(numRegister))
else:
# error response
s = struct.Struct('>3H 3B')
data = s.unpack(res)
print("\nModbus Application Data Unit (ADU)")
print(" Transaction Identifier : %s" %data[0])
print(" Protocol Identifier : %s" %data[1])
print(" Length : %s" %data[2])
print(" Unit Identifier : %s" %data[3])
if len(data) > 6:
print(" Function Code : %s" %data[4])
print(" Byte Count : %s" %data[5])
print("\nRegister: Value")
for i in range(6, 6+numRegister):
currentRegister = i-6 + startRegister
print(" #0x{0:x} : 0x{1:04x} : {1}".format(currentRegister, data[i]))
else:
print(" Error Code : 0x{0:02x} : {0}".format(data[4]))
print(" Exception Code : 0x{0:02x} : {0}".format(data[5]))
sock.close()
except:
print(traceback.format_exc())
finally:
print("\nDone")
例:正常応答
192.168.1.101のデバイスに対して
Unit ID: 2, Input register (FC 0x04)のアドレス0x0290から4 wordのデータを取得した場合です。
$python modbus_reader.py 192.168.1.101
Enter Modbus Params in [0xnn (Hex)] or [nn (Dec)]
Unit Identifier: 0x2
Function Code: 0x4
Start Register: 0x290
Num of Registers: 0x4
結果
0x03eb, 0x0208, 0x0208, 0x0000 の4 word分のInput Register値を読み取ることができました。
TX: b'000000000006020402900004'
RX: b'00000000000b02040803eb020802080000'
Modbus Application Data Unit (ADU)
Transaction Identifier : 0
Protocol Identifier : 0
Length : 11
Unit Identifier : 2
Function Code : 4
Byte Count : 8
Register: Value
#0x290 : 0x03eb : 1003
#0x291 : 0x0208 : 520
#0x292 : 0x0208 : 520
#0x293 : 0x0000 : 0
Done
例:エラー応答
192.168.1.101のデバイスに対して
Unit ID: 4, Input register (FC 0x04)のアドレス0x0290から10 wordのデータを取得しようとした場合です。
$python modbus_reader.py 192.168.1.101
Enter Modbus Params in [0xnn (Hex)] or [nn (Dec)]
Unit Identifier: 4
Function Code: 4
Start Register: 656
Num of Registers: 4
結果
Function Code 0x04に対するエラー 0x84 が発生しました。
Exception Code 0x0a 即ち Gateway Path Unavailable
となっています。このデバイスはUID 4のスレーブデバイスを持っていないためにこのエラーが発生していました。
TX: b'000000000006040402900004'
RX: b'00000000000304840a'
Modbus Application Data Unit (ADU)
Transaction Identifier : 0
Protocol Identifier : 0
Length : 3
Unit Identifier : 4
Error Code : 0x84 : 132
Exception Code : 0x0a : 10
Done
おわりに
これでModbus/TCPデバイスのレジスタをガンガン読めるはず。
追記
2019-03-29:
入力パラメータを16進数と10進数の両方に対応するようにGitHubのコードをアップデートしたので、この記事にも反映しました。
関連記事
2019-08-28:pythonでModbus機器と通信する(4) レジスタ書込み用ツール(Force Single Coil)
“pythonでModbus機器と通信する(3) レジスタ読取り用ツール” への2件の返信