pymodbusでModbus/TCPスレーブ(サーバー)を構築する

お久しぶりです。IMAXおじさんです。 今日は今までのWEB系の話とは少し変わって、PyModbusでModbus/TCPのサーバーを構築する話をします。

参考文献

HMSのサイト M-system技研のModbus/TCP通信仕様書

Modbus/TCPとは

Modbus/TCPとは、Modicon社(現Schneider Electric社)が1979年に策定した産業用Ethernetプロトコルです。 主にPLCやリモートIOとの通信に使用される産業用Ethernetプロトコルであり、世界的なシェアとしては5%程度を占めています(2020年現在)。

Ethernet系フィールドバスのシェア

通信の概要

サーバー(フィールド側,  PLCやリモートIO)
xxx.yyy.zzz.100
    |
    |
クライアント(PC)
xxx.yyy.zzz.101

Modbus/TCPはサーバー/クライアント方式の通信プロトコルです。 PLCやリモートIOなどがスレーブ(==サーバー)、データ収集を行うPCなどがマスター(==クライアント)としてデータのやり取りを行います。 名前の通り、各サーバーとクライアントがTCPでペイロードをやり取りします。 物理レイヤーの仕様(端子形状など)は指定されておらず、あくまで通信のレイヤーのみの仕様となります。

ペイロードの仕様に関しては、下記のM-system技研発行の通信仕様書がよくまとまっており参考になります。

M-system技研 Modbus通信仕様書

PyModbusとは

PyModbusとは上記のModbusサーバー・クライアントを擬似的にPythonで構築できるライブラリです。 対向機をわざわざ購入するとなると、数万円かかってしまいますが、PyModbusを使えば無料で手軽に対向機を用意できます。

PyPIのPyModbusページ

Modbus/TCPによる通信デモ

さて、Modbus/TCPの説明が終わったところで早速デモの構築に移りましょう。 PC上にPyModbusで構築したサーバーまでデータを取りに行くデモを構築します。

検証環境

  1. PC: DELL Inspiron 7370
  2. OS: Ubuntu20.04(LTS)
  3. Python: 3.8.10
  4. PyModbus: 2.5.3

Modbus/TCPスレーブ(サーバー)のPyModbusによる構築

PyModbusのサーバーを早速構築します。ローカルホストにサーバーを立てることにするため、特にIPアドレスは気にしなくてもよいです。 実際にデバイス間通信を試したい人は、Ethernetポートを備えたデバイスを2個用意して固定IPを振ってあげるとよいかと思います。

今回はinput_registerに値を入れることにします。 サーバーがレジスタ(input_register)に保持する値は、以下の通りとしましょう。

----------------------------
register1 |   int16
----------------------------
register2 |   float
register3 |   (32bit)
----------------------------

Modbusでは、1レジスタ=2byte(16bit)で扱われることに注意してください。なので、32bitのfloatなど16bitを超えるようなデータはレジスタをまたがって保持されることになります。

さて、このような値を返してくれるサーバーをPyModbusで構築していきます。まずは必要ライブラリのimportを。

# sample_server.py
from pymodbus.constants import Endian
from pymodbus.version import version
from pymodbus.server.asynchronous import StartTcpServer

from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.payload import BinaryPayloadBuilder

サーバー側でアクセス・送受信しているパケットの内容を確認するため、ログの設定をします。

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

次に、レジスタに入れる値を作っていきます。 PyModbusではpymodbus.BinaryPayloadBuilderを使ってペイロードのオブジェクトを作ることになります。

builder = BinaryPayloadBuilder(byteorder=Endian.Big) #ペイロードビルダーのインスタンスを作成。バイトオーダーは再現したいリモートIOの仕様に準ずること。

# 最初のint_16のペイロードを作成
builder.add_16bit_int(100)

# 2つ目のfloatのペイロードを作成
builder.add_32bit_float(123.45)

# ペイロードをModbusSequentialDataBlockへ変換
block = ModbusSequentialDataBlock(1, builder.to_registers())

# レジスタへの登録を行う
store = ModbusSlaveContext(ir=block, zero_mode=False)
context = ModbusServerContext(slaves=store, single=True)

最後に、サーバーを起動しておしまいです。

StartTcpServer(context, address=("0.0.0.0, 502")) # ローカルホストで起動

Modbus/TCPマスター(クライアント)のPyModbusによる構築

サーバーの構築はできたので、サーバーにためている情報を取りに行くクライアントを作ります。

Modbusではクライアントからのみ通信を始めることができます。 クライアントから打てるリクエストにはいくつか種類(ファンクションコード)があります。 詳しくはModbus/TCPの通信仕様書を見ていただきたいのですが、今回はファンクションコード4(FC4)を使います。 FC4はread_input_registersというリクエストになっており、その名の通りinput_registersの値を読み取るものになります。

(余談ですが、Modbus/TCPではread_input_registersで読まれるレジスタアドレスは300001から始まると決まっているようです。FC4なのに開始アドレスが3xxxなのは気持ちが悪い...)

サーバーの設定では、レジスタ3つにまたがってデータが溜まっているのでread_input_registersで3つ分レジスタを読んであげましょう。

from pymodbus.client.sync import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder

# Modbusクライアントのインスタンス。ローカルホストにサーバーが立っているので、アクセス先をlocalhostと指定。
client = ModbusTcpClient("localhost", port=502)

# FC4のread_input_registersでレジスタアドレス300001+0 ~ 300001+2を読み取る。
# 第一引数が開始アドレス(相対)、第二引数がレジスタ数
res = client.read_input_registers(0, 3, unit=1)

# 読み取ったレジスタ値をデコーダインスタンスへ渡す
# サーバー側がビッグエンディアン設定なので、こちらも合わせる。
decoder = BinaryPayloadDecoder.fromRegisters(res.registers, byteorder=Endian.Big)

# 最初のint_16bitをデコード
int16 = decoder.decode_16bit_int()

# 次のfloat_32bitをデコード
float32 = decoder.decode_32bit_float()

print(int16) # -> 100
print(float32) # -> 123.45...

これで、サーバーのinput_registersに溜まったデータをModbus/TCPで読み取ることができました。

まとめ

産業用ネットワーク難しい


戻る

About

IMAXおじさんが(主に)技術系記事を備忘録として残していくブログです.

Category

  1. Tech
  2. Daily
  3. Job
  4. Other