pythonでModbus機器と通信する(3) レジスタ読取り用ツール

これまでに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件の返信

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です