pythonでModbus機器と通信する(4) レジスタ書込み用ツール(Force Single Coil)

pythonでModbus/TCPによりデバイスのレジスタにアクセスするスクリプトに書込みスクリプトを追加しました。
Function Code 0x05 (Force Single Coil)のみ対応ですが、GitHubにも追加しています。

レジスタ書込みツール: modbus_writer.py

読取りツールmodbus_reader.pyと同じく、modbus用のライブラリは使用せずsocket通信でコマンド/レスポンスを送受信しています。

GitHub:
https://github.com/tyamazoe/modbus

使用方法

$python modbus_writer.py [ip address]

デバイスのIPを指定して実行すると、Unit ID, Function Code, アドレス等のパラメータの入力を対話式で要求されます。

パラメータは 16進の場合 0xを付けて指定します(例: 0x04)。10進数の場合はそのままです(例: 4)。

制限事項

現状はFunction Code は 0x05 (Force Single Coil)のみの対応です。従って1個のレジスタへの値のセットOn(0xff00)/Off(0x0000)を行います。

注意:

FC 0x05の場合書き込む値は
0xff00 または 0x0000 (On/Off)の2択です。0xff00/0x0000 以外をリクエストするとエラーが返ります。

プログラム

# -*- coding: utf-8 -*-
# Modbus/TCP writer
#
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

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 == 0x05:
        startRegister = get_param(" Start Register: ")
        pack_data = 0xff00 if get_param(" On(1) or Off(0):") == 1 else 0x0000
        numRegister = 2
    else:
        raise Exception("FC only support  0x05")

    # 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, pack_data)
    sock.send(req)

    # Receive response
    buffer_size = len(req)
    res = sock.recv(buffer_size)
    print("\nTX: {0}".format(codecs.encode(req, 'hex_codec')))
    print("RX: {0}".format(codecs.encode(res, 'hex_codec')))

    if res == req:
        print("\nOK, write completed.")
    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])
        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")

例:Off(0x0000) 正常応答

192.168.1.101のデバイスに対して
Unit ID: 1, Force Single Coil (FC 0x05)によりアドレス0x0101のSingle Coilに対して 0x0000 (Off)を指定した場合です。

$ python modbus_writer.py 192.168.1.101

Enter Modbus Params in [0xnn (Hex)] or [nn (Dec)]
 Unit Identifier: 1
 Function Code: 5
 Start Register: 0x101
 On(1) or Off(0):0

結果

0x0000のセットに成功したために同じModbus ADU (Application Data Unit):が返ってきています。
例えばデバイスの接点やリレーがOffになります。

TX: b'000000000006010501010000'
RX: b'000000000006010501010000'

OK, write completed.
Done

例:On(0xff00) 正常応答

192.168.1.101のデバイスに対して
Unit ID: 1, Force Single Coil (FC 0x05)によりアドレス0x0101のSingle Coilに対して 0xff00 (On)を指定した場合です。

$ python modbus_writer.py 192.168.1.101

Enter Modbus Params in [0xnn (Hex)] or [nn (Dec)]
 Unit Identifier: 1
 Function Code: 5
 Start Register: 0x101
 On(1) or Off(0):1

結果

0xff00のセットに成功したために同じModbus ADU (Application Data Unit):が返ってきています。
例えばデバイスの接点やリレーがOnになります。

TX: b'00000000000601050101ff00'
RX: b'00000000000601050101ff00'

OK, write completed.
Done

おわりに

Single Coilのみですが、これでModbus/TCPデバイスのレジスタも操作ができるようになりました。

関連記事

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

コメントを残す

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