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-02-01: pythonでModbus機器と通信する(2) レスポンスの受信
2019-03-29: pythonでModbus機器と通信する(3) レジスタ読取り用ツール
2019-08-28: pythonでModbus機器と通信する(4) レジスタ書込み用ツール(Force Single Coil)
“pythonでModbus機器と通信する(1) リクエストの送信” への1件の返信