pythonでModbus機器と通信する(1) リクエストの送信

Modbus/TCPプロトコルをpythonでsocketを使って通信する方法のメモです。
Modbus用のライブラリにはpyModbusなどがありますが、専用ライブラリを使用せずにsocketで通信してみます。

Modbus

FAやBAデバイス通信に使用されるModbusは産業用に広く使用されているシリアル通信プロトコルです。IT機器ではSNMPが使用されることが多いですが、Modbus/TCPをサポートしている機器もあります。
MIBでアクセスするパラメータが明確なSNMPと異なり、Modbusはレジスタ単位のアクセスになりレジスタ情報を機器ごとに判別する必要があります。

  • Modbus/RTU : RS-485などのシリアル接続で機器を接続通信
  • Modbus/TCP : Eternet上のTCP/IPで機器を接続。

Modbus参考サイト

本家です。

Modbus Organization
http://www.modbus.org/

Modbus Protocol Specification
http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf
Modbus/TCP Specification
http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf

日本語ではこちらのMSYSTEMの資料が解りやすいです。
http://www.m-system.co.jp/mssjapanese/kaisetsu/nmmodbus.pdf

Modbusデバイスはクライアントかサーバか

Master-Slave

Modbus機器はマスタースレーブ方式です。マスターがスレーブに対してリクエストを送信し、スレーブがレスポンスして通信を行います。つまりマスター側がスレーブの各種センサー値や状態を問い合わせてスレーブが回答します。
Modbus/RTUではマスター1台に対して複数のスレーブがRS-485などで接続されている形態が典型的な構成です。

Client-Server

リクエスト送信する側がクライアントで応答する側がサーバーです。PCのネットワークを思い描くと理解しやすいですが接続を待つ方がサーバーで接続して行く方がクライアントです。

Modbus/TCPの通信方式 Client/Serverモデル

Modbus/TCPの場合、仕様書にクライアントサーバーの形で定義されています。
繰り返しますが、リクエスト送信する側がクライアントで応答する側がサーバーです。

pythonプログラムでModbus機器のデータをread/writeするので、プログラム側つまりPCやラズパイがClientになります。
Modbus/TCP機器、例えば照明のコントローラだったり空調のコントローラだったり電力計だったりがServerになります。

補足ですが、Modbus/RTUのSlave機器に対するゲートウェイ装置としてのMaster機器があり、そのMaster機器がEternetポートを有してModbus/TCPをサポートしていると、Eternet側からはそのゲートウェイ装置はModbus/TCPのServerにあたります。

Modbus/TCPのポート

TCP port 502

Modbus/TCPのデータ構造

バイナリ列です。ADUと呼ばれます。ADUはMBAP Header部とPDU部から構成されています

ADU (Application Data Unit):

MBAP Header + PDU

MBAP Header:

Transaction ID : 2byte
Protocol ID: 2byte : 0x0000
Length: 2byte : PDUのサイズ

PDU (Protocol Data Unit):

Function Code: 1byte: 0x02, 0x04, 0x05, 0x06 など
0x02: Read Discretes Input: Read Single bit
0x03: Read Holding Registers
0x04: Read Input Registers
0x05: Write Single Coil
0x06: Write Single Register など

Unit ID: 1byte: slaveのidなど

(1) Function Code 0x02 (Read Discrete Input)でUnit ID 0xffのユニットのレジスタ0x000fから1bitデータをreadする場合

リクエスト(TX): ‘020000000006ff02000f0001’
正常応答(RX) : ‘020000000004ff020101’

TX
Transaction, Protocol, length, Id, FC, Address, Num
0200, 0000, 0006, ff, 02, 000f, 0001

RX
Transaction, Protocol, length, Id, FC, size, bit data
0200, 0000, 0004, ff, 02, 01, 01

(2) Function Code 0x04 (Read Input Registers)でUnit ID 0xffのユニットのレジスタ0x0000から1レジスタをreadする

TX: ‘020200000006ff0400000001’
RX: ‘020200000005ff0402003e’

TX:
Transaction, Protocol, length, Id, FC, Address, Num
0202, 0000, 0006, ff, 04, 0000, 0001

RX:
Transaction, Protocol, length, Id, FC, Size, Register value
0202, 0000, 0005, ff, 04, 02, 003e

Function Code 0x02の場合のpythonコード

import socket
import struct
import time
import codecs
import sys

TARGET_IP = '192.168.1.123'
TARGET_PORT = 502
BUFFER_SIZE = 512

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((TARGET_IP, TARGET_PORT))
sock.settimeout(3.0)
try:
    print("Send modbus request ...")
    transactionId = 512
    protocolId = 0
    len = 6
    unitId = 0xff
    functionCode = 2 # Read Input Status (0x02)
    startRegister = 0x0000
    data = 0x0001  # bit count
    req = struct.pack('>3H 2B 2H', int(transactionId), int(protocolId), int(len), int(unitId), int(functionCode), int(startRegister), int(data))
    sock.send(req)
    rec = sock.recv(BUFFER_SIZE)

    print("TX: {0}".format(codecs.encode(req, 'hex_codec')))
    print("RX: {0}".format(codecs.encode(rec, 'hex_codec')))
    time.sleep(1)
finally:
    sock.close()

(3) Function Code 0x05 (Write Single Coil)でUnit ID 0xffのユニットのレジスタ0x0000のsetをwriteする

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

TX: ‘3f9500000006ff050000ff00’
RX: ‘3f9500000006ff050000ff00’

TX:
Transaction, Protocol, length, Id, FC, Address, Value
3f95, 0000, 0006, ff, 05, 0000, ff00
RX:
Transaction, Protocol, length, Id, FC, Address, Value
3f95, 0000, 0006, ff, 05, 0000, ff00

Writeリクエストに対する正常レスポンスはリクエストクエリがそのまま返ります。

エラーの場合

0xff00ではなく、0x0001をリクエストしてみました。 PDUに Error Code 0x85 と Exception Code 0x03 が返ってきます。
TX: ‘3f9600000006ff0500000100’
RX: ‘3f9600000003ff8503’

ID, Error Code, Exception Code
ff, 85, 03

受信レスポンスのデータを切り分けるプログラムやModbus/TCPのサーバ側プログラムはまた次回に書きます。

関連記事

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

コメントを残す

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