Quick Debugging Tips
1. Test Connection
client = ModbusTcpClient('192.168.1.100', port=502)
if client.connect():
print("✓ Connection successful")
client.close()
else:
print("✗ Connection failed - check IP and port")
このコードは、Modbus TCP機器との接続可否を確認するための最小テストです。ModbusTcpClient にIPアドレス 192.168.1.100 と標準ポート 502 を指定してクライアントを生成します。connect() はTCP接続が成功した場合に True を返し、失敗した場合は False になります。
接続に成功すると「Connection successful」と表示し、すぐに close() を呼んで通信を終了します。これは機器の生存確認やネットワーク疎通テストに使われる典型的な書き方です。一方、失敗時にはIPアドレスやポート番号の誤り、機器の電源OFF、ネットワーク設定不備などが疑われます。Modbus通信前の事前チェックとして非常に重要なコードです。
2. Check Slave ID
# Try different slave IDs if communication fails
for slave_id in [0, 1, 247]:
result = client.read_holding_registers(
address=0,
count=1,
slave=slave_id
)
if not result.isError():
print(f"✓ Slave ID {slave_id} works")
break
このコードは、Modbus通信がうまくいかない場合に、Slave ID(Unit ID)を切り替えて有効なIDを探索する例です。for slave_id in [0, 1, 247] のように候補となるIDを順に指定し、保持レジスタの読み取りを試します。Modbus TCPでは通常Slave IDは1が使われますが、ゲートウェイ配下の機器や設定次第では0や247が必要になることがあります。isError() がFalseであれば通信が成立しているため、そのIDが有効と判断し、break でループを終了します。接続はできるが応答がない場合の切り分けに有効なデバッグ手法です。
3. Verify Register Address
# Some devices use different addressing
# Try both 0-based and 1-based addressing
addresses_to_try = [0, 1, 40000, 40001]
for addr in addresses_to_try:
result = client.read_holding_registers(addr, 1)
if not result.isError():
print(f"✓ Address {addr} works")
break
このコードは、Modbus機器ごとに異なるレジスタ番号の解釈を確認するためのデバッグ用例です。Modbusでは本来、保持レジスタは0始まりで指定しますが、機器のマニュアル表記や実装によっては 1始まり(1-based) や 40001表記 をそのまま受け付ける装置も存在します。
そこで addresses_to_try = [0, 1, 40000, 40001] のように複数の候補アドレスを順に指定し、read_holding_registers を実行しています。isError() がFalseになったアドレスは、その機器で有効な指定方法であることを意味します。「通信はできるが値が読めない」場合の切り分けとして非常に有効な確認手法です。
Project Structure Template
my_modbus_project/
├── config.py # Configuration settings
├── client.py # Modbus client wrapper
├── devices/
│ ├── __init__.py
│ ├── sensor.py # Sensor device class
│ └── controller.py # Controller device class
├── utils.py # Helper functions
└── main.py # Main application
このコード(構成例)は、Modbus TCPを使ったPythonアプリケーションを実運用向けに整理するためのプロジェクト設計テンプレートを示しています。小規模な検証コードから、保守可能な業務システムへ発展させる際に重要な考え方が含まれています。
ディレクトリ構成では、client.py にModbus通信処理を集約し、devices/ 配下にセンサやコントローラごとのクラスを分離しています。これにより、機器追加や仕様変更があっても影響範囲を限定できます。utils.py には共通処理をまとめ、main.py は全体の制御フローのみを記述する役割になります。
Sample config.py
# config.py
MODBUS_CONFIG = {
'host': '192.168.1.100',
'port': 502,
'timeout': 3,
'retry_count': 3,
'slave_id': 1
}
REGISTER_MAP = {
'temperature': 100,
'humidity': 101,
'pressure': 102,
'status': 200
}
config.py では、IPアドレスやポート、タイムアウト、Slave IDなどの通信設定を一元管理しています。さらに REGISTER_MAP に物理量名とレジスタアドレスの対応を定義することで、「100番レジスタ」ではなく「temperature」として扱えるようになり、コードの可読性と安全性が大きく向上します。Modbusを“業務コード”として扱うための基本設計例です。
Sample clinet.py
# client.py
from pymodbus.client import ModbusTcpClient
from config import MODBUS_CONFIG
class ModbusClient:
def __init__(self):
self.client = ModbusTcpClient(
MODBUS_CONFIG['host'],
port=MODBUS_CONFIG['port']
)
self.slave_id = MODBUS_CONFIG['slave_id']
def connect(self):
return self.client.connect()
def disconnect(self):
self.client.close()
def read_register(self, address, count=1):
result = self.client.read_holding_registers(
address=address,
count=count,
slave=self.slave_id
)
if not result.isError():
return result.registers
return None
このコードは、pymodbusの生APIを直接使わず、Modbus通信をラップする専用クライアントクラスを定義しています。アプリケーション側から見たときに、通信設定や例外処理を意識せずにModbusを扱えるようにするのが目的です。
__init__ では config.py に定義された MODBUS_CONFIG を読み込み、IPアドレス・ポート・Slave ID を一元設定しています。これにより、通信条件の変更は設定ファイルだけで済み、コード修正が不要になります。connect() と disconnect() は接続管理をシンプルなメソッドとして提供しています。
read_register() は保持レジスタ読み取りを共通化したメソッドで、アドレスと個数を指定するだけで値を取得できます。内部で isError() を確認し、正常時のみレジスタ値のリストを返し、エラー時は None を返します。業務ロジックと通信処理を分離するための、実践的な設計パターンです。