![見出し画像](https://assets.st-note.com/production/uploads/images/166703576/rectangle_large_type_2_485af58f193d2e1c6e56be0750301267.jpeg?width=1200)
Pythonライブラリ(Modbus通信):Pymodbus
1.概要
PythonでModbus通信ができるライブラリPyModbusを紹介します。
1-1.Modbusとは
Modbusとは通信プロトコルの一種であり、米Modicon社により開発されたFA機器(PLC) 用ネットワークです。
下記の通り、記載方法により通信規格が異なります。
Modbus(Modbus/RTU):RS-232CやRS-485などシリアル通信をベース
バイナリデータでデータ伝送
RS-485またはRS-232Cポートを使用
データパケットには、アドレスフィールド、機能コード、データ、エラーチェックフィールド(CRC)が含まれる
Modbus/TCP:イーサネットネットワークを介して通信
内蔵Ethernetポートを使用
TCP/IPプロトコルスタックを使用し、より高いデータ転送速度を実現
通信プロトコルとして、マスタ・スレーブ方式で通信しており、マスタのみが通信開始を実行することができます。よって、複数のスレーブに対して一括で指令送信が可能です。
<参考資料>
1-2.Modbusのユースケース
どのような場面で使われるかの事例は下記の通り。
配線の簡略化
デイジーチェーン(数珠つなぎ)で接続できるため配線がシンプル
その他事例は追って
![](https://assets.st-note.com/img/1734834386-IKqmS7DQvMG54egVbf2uUhz0.png?width=1200)
![](https://assets.st-note.com/img/1734842604-tPVxG3TaWIziXyCscH1JhMLm.png?width=1200)
1-3.Pymodbusの概要
PymodbusはPythonで同期/非同期のModbus通信を扱うライブラリであり完全なFOSS(Free and Open Source Software)です。Pymodbusは大きく分けて5つのパーツで成り立ちます。
client,:自分の好きなデバイスに接続
server:自分の好きなデバイスを操作
repl:CLI(Command Line Interface)ベースのclient/server操作
REPL(Read-Eval-Print Loop)とは、ユーザーとインタプリタが対話的にコードを実行可能な形式
simulator:HLMLベースのserver simulator
examples:簡易・応用事例の紹介
共通の特徴としては下記の通り。
フルModbus標準プロトコルの実装
カスタム機能コードのサポート
シリアル(rs-485)、tcp、tls、udp通信をサポート
全ての標準フレームをサポート: socket, rtu, rtu-over-tcp, tcp, ascii
(pyserial(オプション)を除き)サードパーティに依存しない
強く型付けされたAPI (py.typedあり)
2.製品の購入
2-1.購入品リスト
今回は下記記事を参考に、Raspberry Piから湿温度センサをModbusで接続してみました。
購入・準備した製品は下記の通りです。
Raspberry Pi:マスタ用
購入や環境構築は記事参照
必要な配線は別途購入
温湿度センサ:スレーブ用
シンプルなテストなら購入は1個で問題なし
私は他のテスト実施+安い方でうまくいかないと嫌なため2種購入
※安い方は仕様の記載がないため非推奨
Amazonで”温湿度センサー modbus”と検索してやつを選定
シリアルコンバーター:USBからModbusへ配線するための変換器
直流電源:温湿度センサ用
詳細は記事参照
2-2.センサ仕様の確認
センサの仕様は以下の通りです。安物の方はサイトに仕様情報が何もなく、製品にラベルすらなかったため画像のラベルから推測しました。
![](https://assets.st-note.com/img/1735177264-u4ykiO7MSqWalwRjIF5dvem0.png?width=1200)
![](https://assets.st-note.com/img/1735178825-9MkPajtH1gf5RI2pNBebxCSQ.png?width=1200)
![](https://assets.st-note.com/img/1735178254-6BLZYjfXHM83PnvDpdC92zKo.png?width=1200)
3.事前準備
3-1.環境構築
Raspberry Piは環境構築済みとします。
Pymodbusのインストールはpipで実施可能です。PythonのVersionは3.9以上が推奨※されています。※Python3.8で実行したらImportErrorが発生。
Raspberry Piでは不要ですが別機器だとデフォルトでインストールされていないためpyserialもインストールしておきます。
[Terminal]
pip install pymodbus
pip install pyserial
![](https://assets.st-note.com/img/1735175452-hD4xX0qBbMpimKJIQTtfnec9.png?width=1200)
【参考:PymodbusのVer.を3.x系にアップデート】
私のRaspberry Pi5環境はデフォルトでPymodbus 2.5.3がインストールされていました。
Pymodbusは2022年3月に2.5系から3.0系にVersionアップ(リリース履歴)されており、2.x系と3.x系ではAPIが異なっているため注意が必要です。
[Terminal]
pip show pymodbus
[OUT]
Name: pymodbus
Version: 2.5.3
Summary: A fully featured modbus protocol stack in python
Home-page: https://github.com/riptideio/pymodbus/
Author: Galen Collins
Author-email: bashwork@gmail.com
License: BSD-3-Clause
Location: /home/kiyo/miniforge3/lib/python3.10/site-packages
Requires: pyserial, six
Required-by:
基本的に公式Docsから参照していきたい+将来でも参考にできるよう3.x系を使用したいため、下記でアップデートしました。
現在(2025年1月)でのVersionは3.8.3となります。
[Terminal]
pip uninstall pymodbus
pip install pymodbus
![](https://assets.st-note.com/img/1737289111-5vOIzGKVBQ89TYJctufLSNse.png?width=1200)
【参考:pymodbus.console※2.x系のみ】
pymodbusの2.x系ではインタラクティブにコマンドを打てるpymodbus.consoleがあります。これを利用するには他のライブラリもインストールが必要です。別の記事では”pip install click pygments prompt_toolkit”ですが、私はclickだけで対応できました。
[Terminal]
pip install click
![](https://assets.st-note.com/img/1737286848-Eb09hFDCI3iaL1qyxz2ks5dA.png?width=1200)
なお3.x系の場合は下記の通りエラーが出るため対応は不要です。
![](https://assets.st-note.com/img/1737290563-47e6SD2dFthqIZ1Yzi9rE08W.png?width=1200)
3-2.製品の配線
3-2-1.Case1:単体で接続
製品配線は下図の通りです。
電源はパワーサプライを使用してDC24V供給
シリアルコンバーターはRaspberry PiのUSBポートに接続
ModbusはRS485であり、ジャンピングワイヤーで結線
+/-があるため、機器の表記に合わせて結線
![](https://assets.st-note.com/img/1735188488-2JlsThoNKy5RCZu9iOV6nQzt.png?width=1200)
電源を入れてセンサ(下図右)が赤く点灯すれば準備完了です。
![](https://assets.st-note.com/img/1735188105-rtW1SDh3RgJABvpGqi5lwKxN.png?width=1200)
【参考:Modbus通信の配線ケーブル】
Modbusは通信プロトコルの規定のみのため、通信ケーブルまでは規定されていません。特にCVVSケーブル(遮へい付制御用ケーブル)とかまでは必要なさそうなのでジャンピングワイヤで接続しました。
![](https://assets.st-note.com/img/1737261560-z4Q3rMJhk71PvaxB5ZOWE8yU.png?width=1200)
3-2-2.Case2:複数接続
追って
3-3.接続の確認
差し込んだシリアルコンバーター(USB)を認識しているか等をLinuxコマンドで確認します。学習もかねて2パターン紹介します。
3-3-1.dmesgコマンド
dmesgはlinuxがブートからファイルシステムがマウントされるまでのログが保存されています。
Raspberry Piに差し込んだUSB(シリアルコンバーター)が認識しているかの確認は”dmesg | tail -n 5”で実施できます。コマンドの詳細は下記の通り。
※注意点としてdmesgは”イベントログをリアルタイムに見る(接続ログ)”ため、センサの電源を入れUSBを差してから実行しないと認識しません。
dmesgコマンドはLinuxカーネルが起動時に出力したメッセージを表示
"dmesg -T | grep usb"だとUSB関係を全表示(Qiita:dmesg について)
表示数を制限
Linuxコマンドのtailコマンドを使用して最終行から数行を表示(Qiita:tailコマンドが便利だった件)
”tail -n 5”で最新ログを5つ表示
【Terminal】
dmesg | tail -n 5
[OUT:USB差し込み前]
[14562.504513] w1_master_driver w1_bus_master1: Family 0 for 00.948000000000.5f is not registered.
[14596.861163] usb 1-2: USB disconnect, device number 3
[14596.861321] cp210x ttyUSB0: cp210x converter now disconnected from ttyUSB0
[14596.866807] cp210x 1-2:1.0: device disconnected
[14598.208892] w1_master_driver w1_bus_master1: Family 0 for 00.548000000000.95 is not registered.
[OUT:USB差し込み後]
[14640.818529] usb 3-1: Product: CP2104 USB to UART Bridge Controller
[14640.818532] usb 3-1: Manufacturer: Silicon Labs
[14640.818534] usb 3-1: SerialNumber: 00FD88AE
[14640.827600] cp210x 3-1:1.0: cp210x converter detected
[14640.833702] usb 3-1: cp210x converter now attached to ttyUSB0
![](https://assets.st-note.com/img/1735188970-xPsyrdvD1HGj0kbaJqiZzt3w.png?width=1200)
3-3-2.udevadmコマンド
udev (userspace device manager)とは、Linuxカーネルが認識したデバイスに対してユーザ空間で管理・命名・設定を行うシステムです。
udevadmは、udevデバイスマネージャを管理するためのコマンドラインツールであり、デバイスの現在の属性情報(Vendor IDやSerial番号などのデバイスプロパティ)を確認できます。
” udevadm info -q property -n /dev/ttyUSB*”でシリアルコンバーター(USB)の情報取得が可能です。
udevadm info:udevが認識しているデバイスの詳細情報を取得
-q property:クエリタイプの指定
-n /dev/ttyUSB* :ターゲットデバイスの指定
-n:udevが管理している対象デバイス(node)を指定
/dev/ttyUSB*:ワイルドカード"*"を使用してdev内のUSBという文字を持つものを検索
【Terminal】
udevadm info -q property -n /dev/ttyUSB*
[OUT]
DEVPATH=/devices/platform/axi/ーーーーーーーーー
DEVNAME=/dev/ttyUSB0
MAJOR=188
MINOR=0
SUBSYSTEM=tty
USEC_INITIALIZED=7659309
ID_BUS=usb
ID_MODEL=CP2104_USB_to_UART_Bridge_Controller
ID_MODEL_ENC=CP2104\x20USB\x20to\x20UART\x20Bridge\x20Controller
ID_MODEL_ID=ea60
ID_SERIAL=ーーーーーーーーー
ID_SERIAL_SHORT=ーーーーーーーーー
ID_VENDOR=Silicon_Labs
ID_VENDOR_ENC=Silicon\x20Labs
ID_VENDOR_ID=10c4
ID_REVISION=0100
ID_TYPE=generic
ID_USB_MODEL=CP2104_USB_to_UART_Bridge_Controller
ID_USB_MODEL_ENC=CP2104\x20USB\x20to\x20UART\x20Bridge\x20Controller
ID_USB_MODEL_ID=ea60
ID_USB_SERIAL=Silicon_Labs_CP2104_USB_to_UART_Bridge_Controller_00FD88AE
ID_USB_SERIAL_SHORT=00FD88AE
ID_USB_VENDOR=Silicon_Labs
ID_USB_VENDOR_ENC=Silicon\x20Labs
ID_USB_VENDOR_ID=10c4
ID_USB_REVISION=0100
ID_USB_TYPE=generic
ID_USB_INTERFACES=:ff0000:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=cp210x
ID_VENDOR_FROM_DATABASE=Silicon Labs
ID_MODEL_FROM_DATABASE=CP210x UART Bridge
ID_PATH=platform-xhci-hcd.1-usb-0:1:1.0
ID_PATH_TAG=platform-xhci-hcd_1-usb-0_1_1_0
ID_MM_CANDIDATE=1
DEVLINKS=/dev/serial/by-path/platform-xhci-hcd.1-usb-0:1:1.0-port0 /dev/serial/by-id/usb-Si>
TAGS=:systemd:
CURRENT_TAGS=:systemd:
lines 1-38/38 (END)
![](https://assets.st-note.com/img/1737285882-HSM4npdD7IKXqY9igrQAhv6o.png?width=1200)
”DEVNAME=/dev/ttyUSB0”より、シリアルコンバーター(USB)には”/dev/ttyUSB0”でアクセスできます。
参考までに”ID_USB_DRIVER=cp210x”より、LinuxカーネルがSilicon Labs社のUSBシリアル変換ICを扱うためのモジュールであるcp210を適用していることが分かります。
※ID_USB_VENDOR=Silicon_Labsからも確認可
4.QuickStart1:簡易の実装
まずは下記Qiita記事を参照し、簡単な操作を実行しました。
4-1.パラメータ確認
PyModbusで機器(センサ)と通信するには、機器情報が必要です。
(安物は何も情報ないのでWeytoll社のみ)Modbus通信を実施するために必要な情報を確認しました。
通信プロトコル(通信方法):Mobus-RTU
今回の機器はModbus TCP (Ethernet)出ないため、TCPは不使用
lsusbでcp210xが確認できれば今回購入したRS485変換器が付いているため通信はRTUとなります
物理インターフェース:今回は"/dev/ttyUSB0"
3章のdmesgやudevadmでも確認できるが、"ls /dev/ttyUSB*"が楽
Modbus TCPを使う場合は"ip a"や"ifconfig"を用いてIPアドレスを確認
シリアルボーレート:2400、4800、9600(デフォルト)、19200
バイトフォーマット:8 データビット、1 ストップビット、チェック不要
通信アドレス(レジスタマップ):1(デフォルト)~253
スケーリング(データの桁数)の情報は無し
応答時間:2秒
[IN]
ls /dev/ttyUSB*
[OUT]
/dev/ttyUSB0
![](https://assets.st-note.com/img/1737779477-4T0xJW1HVDLEiRs6evAdYghU.png?width=1200)
4-2.完成コード
参考記事のコードはPymodbus2.x系で作成されているため3.x系で実行するとエラーが発生します。よって3.x系で実装しました。
[IN]
from pymodbus.client import ModbusSerialClient
from pymodbus.framer import FramerType
client = ModbusSerialClient(
port="/dev/ttyUSB0", #ポート名(物理ポートを指定)
framer=FramerType.RTU, #Modbusのフレーミング方式
baudrate=9600, #通信速度(bit/s)※機器側と合わせる必要がある
parity='N', #パリティ(偶数: 'E', 奇数: 'O', なし: 'N')
stopbits=1, #ストップビット数
timeout=3 #応答待ち時間[sec]
)
if client.connect(): #通信開始
res = client.read_holding_registers(address=0, #レジスタのアドレス
count=2, #読み込むレジスタの数
slave=1) #スレーブのアドレス
if res.isError():
print(f"Error reading registers: {res}")
else:
temperature = res.registers[0] / 10
print(f"Temperature: {temperature} °C")
humidity = res.registers[1] / 10
print(f"Humidity: {humidity} %")
client.close() #通信終了
else:
print("Failed to connect")
[OUT]
Temperature: 16.7℃
Humidity: 28.5 %
![](https://assets.st-note.com/img/1737780426-Q2fdDCRc8mYJ9sgKW4BExMub.png?width=1200)
結果として温度と湿度の情報を簡単に取得することが出来ました。
4-3.コードの詳細確認
動作確認はできたためコードの詳細部分を確認していきます。
【1.クライアントの作成】
Raspberry Piはセンサー(Server)に値を要求するためクライアントになります。よってクライアントのクラス"pymodbus.client.ModbusSerialClient"を使用しました。使用した引数は下記の通りです。
![](https://assets.st-note.com/img/1737781935-kUH6QzBoiVFjpgyPhCm3AJcl.png?width=1200)
port:通信用のシリアルポート名
物理ポートを指定:"/dev/ttyUSB0", "COM3" など
framer:Modbusのフレーミング方式
デフォルトはFramerType.RTU
baudrate:通信速度(bits/sec)
センサ仕様から9600
機器側と合わせる必要あり
parity:パリティビットを設定->'N'(None), 'E'(Even), 'O' (Odd)
機器側と合わせる必要あり
stopbits:1, 1.5, 2
timeout:応答待ち時間(秒)
機器の応答時間が2secのため少し長めに設定
[API]
class pymodbus.client.ModbusSerialClient(port: str, *,
framer: FramerType = FramerType.RTU,
baudrate: int = 19200, bytesize: int = 8,
parity: str = 'N', stopbits: int = 1,
handle_local_echo: bool = False,
name: str = 'comm', reconnect_delay: float = 0.1,
reconnect_delay_max: float = 300,
timeout: float = 3, retries: int = 3,
trace_packet: Callable[[bool, bytes], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None)
【2.デバイスからデータ読み込み】
次に"client.connect()"でデバイス(またはCommポート)へ接続します。if文にすることで接続不良時はFalseとなり通信エラーを出します。
次にclient.read_holding_registers()を用いてデバイス(センサやPLCなど)にあるレジスタ(メモリ領域)からデータを読み取ります。
address:レジスタ開始アドレス
一般的にはレジスタは0番から割り当てる??
count:開始アドレスから何個のレジスタを読み取るか
温度と湿度のレジスタが順番にあるから2個??
slave:スレーブアドレス(機器のID)
おそらく仕様書の通信アドレス(Communication adress)。デフォルトが1のため、1に設定
エラーが無ければ、res変数内にデータが記録されます。
![](https://assets.st-note.com/img/1737781955-stHPAeg7TKmVFDoEQxp8X3Yb.png?width=1200)
[API]
read_holding_registers(address: int, *,
count: int = 1,
slave: int = 1,
no_response_expected: bool = False)→ T
【3.取得したデバイスデータの抽出・スケーリング】
最後に”res.registers”で取得したデータを表示します。本デバイスでは取得したデータは1桁大きい数値で出るため下記の通りスケーリングします。
$${T}$$:温度, $${H}$$:湿度, $${R_0}$$:レジスタ0, $${R_1}$$:レジスタ1
$$
\begin{aligned}
&温度T[℃]=\frac{R_0}{10} \\
&湿度H[%]=\frac{R_1}{10} \\
&R_0:レジスタ0, R_1:レジスタ1
\end{aligned}
$$
<参考:保持レジスタ(Holding Register)>
保持レジスタは、フィールドからの AO(Analog Output)やスレーブデバイス内の設定情報として 用いられます。16 ビット長のデータで、参照・変更ができます。可能アドレス範囲は 40001 から 49999 です。複数の連続したアドレスを割当てることによって、単精度実数、倍精度実数などのデー タを扱うこともできます。
(出典:Modbus プロトコル概説書)
5.Quickstart2:Windowsで実施
前章においてRaspberry Piで実施したことをWindowsのPCでもできるか確認しました。コードに関して変更点はほぼありません。
5-1.VCPドライバのインストール
結論として、「そのままだとシリアル変換器が認識されないためドライバを追加します」。
Silicon Labsが提供するCP210xファミリはUSB-UARTブリッジICとして広く普及しており、今回使用した変換器もこちらが使用されております。Windowsはデフォルトでは本USBを認識しないためCP210xのドライバが必要です。
私のPCはWindow10/64bitのため、”ダウンロードページ”からCP210x VCP Windowsを選択し、”CP210xVCPInstaller_x64.exe”を実行してVCPドライバをインストールしました。
![](https://assets.st-note.com/img/1737857256-WSCbtv9XonhkQPFmf6sVwlaM.png?width=1200)
5-2.ポートの確認
PCのUSBポートにシリアルコンバーターを差し込みます。それ以外は前章と全く同じです。
![](https://assets.st-note.com/img/1737857839-cwHNYJ5KvRVnTQPy37UiSadE.png?width=1200)
”Windows(右クリック)/デバイスマネージャー/ポート”でCP210xが認識されたらポート名(今回はCOM4)を確認します。
※右図はドライバが無い状態です。ポートが何も認識しておらず”ほかのデバイス”側に認識されます。この状態では通信はできません。
![](https://assets.st-note.com/img/1737857992-T5mzHDvLhiKBFcUxV2urERXs.png?width=1200)
5-3.実装
前のコードから2か所修正しました。それ以外は全て同じです。問題なく動作確認できました。
portの修正
デバイスマネージャーのポートで確認したCOM4を記載
while文の修正+待ち時間追加
Jupyterで実行+連続的にデータとるためにwhile文に修正
[IN]
from pymodbus.client import ModbusSerialClient
from pymodbus.framer import FramerType
import time
client = ModbusSerialClient(
port="COM4", #ポート名(物理ポートを指定)
framer=FramerType.RTU, #Modbusのフレーミング方式
baudrate=9600, #通信速度(bit/s)※機器側と合わせる必要がある
parity='N', #パリティ(偶数: 'E', 奇数: 'O', なし: 'N')
stopbits=1, #ストップビット数
timeout=3 #応答待ち時間[sec]
)
while True:
time.sleep(2) #2秒待機
if client.connect(): #通信開始
res = client.read_holding_registers(address=0, #レジスタのアドレス
count=2, #読み込むレジスタの数
slave=1) #スレーブのアドレス
if res.isError():
print(f"Error reading registers: {res}")
else:
temperature = res.registers[0] / 10
print(f"Temperature: {temperature} °C")
humidity = res.registers[1] / 10
print(f"Humidity: {humidity} %")
client.close() #通信終了
else:
print("Failed to connect")
[OUT]
![](https://assets.st-note.com/production/uploads/images/171670142/picture_pc_fb10292782b25108415ee0a298eb7ee1.gif?width=1200)
6.Quickstart3:2台を繋ぐ
前にRaspberry PiおよびWindows PCでの動作を確認しました。次は2台を使ってClientとServerを実装してみたいと思います。
今回はセンサではなくRaspberry PiとPCを直接繋ぐためModbus/TCPを使用しました。
6-1.物理接続(イーサーネット)
PCとRaspberry Piをイーサーネットで接続しました。私のケースではスイッチングハブを用いてLANケーブルをRaspberry PiとPCに繋ぎました。
おそらくPCとRaspberry Piを直接LANケーブルで繋いでも問題ないです。
![](https://assets.st-note.com/img/1737865112-z056J9RCpugPHZiF7hxG4mI1.png?width=1200)
6-2.IPアドレスの確認
Modbus通信を行うため、サーバー側のLinux(Raspberry Pi)と、クライアント側のWindowsのIPアドレスを確認します。
Linuxは”ip a”, Windowsは"ipconfig"コマンドで確認できます。Raspberry Piの方は有線LAN(eth0)のinetの値を使用しました。
lo:ループバックインターフェイス(127.0.0.1)
eth0:有線LANのポート
wlan0:無線LANのポート
[Terminal( Raspberry Pi側 (LXTerminal)]
ip a
[OUT]
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 192.168.xx.xxx/24 brd 192.168.xx.xxx scope global dynamic noprefixroute eth0
valid_lft 85451sec preferred_lft 85451sec
inet6 xxxxxxxxxxxxxxxx/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 192.168.xx.xxx/24 brd 192.168.xx.xxx scope global dynamic noprefixroute wlan0
valid_lft 72272sec preferred_lft 72272sec
inet6 xxxxxxxxxxxxxxxx/64 scope link noprefixroute
valid_lft forever preferred_lft forever
Windowsは”イーサネット アダプター イーサネット/自動構成 IPv4 アドレス”を確認し、IPアドレスの頭がRaspberry PiのIPアドレスと同じ(192.168)であることを確認しました。
[Terminal(Windows側(コマンドプロンプト)]
ipconfig
[OUT]
イーサネット アダプター イーサネット:
接続固有の DNS サフィックス . . . . .: katch.ne.jp
リンクローカル IPv6 アドレス. . . . .: xxxx::xxxx:xxxx:xxxx:xxxx%xx
IPv4 アドレス . . . . . . . . . . . .: 192.168.xx.xx
サブネット マスク . . . . . . . . . .: 255.255.255.0
デフォルト ゲートウェイ . . . . . . .: 192.168.10.1
6-3.実装
動作を確認するために実装していきます。先にサーバーを立ててから、クライアント側でデータを要求します。
※アドレスのオフセット(ズレ)が生じているためどこかのタイミングで修正します。
6-3-1.Server:Raspberry Pi
サーバー側のコードは下記の通りです。
(※実行例のPyスクリプトの名前は少し変えてます)
[server.py]
from pymodbus.datastore import ModbusSlaveContext, ModbusSequentialDataBlock
from pymodbus.datastore import ModbusServerContext
from pymodbus.server import StartTcpServer
def run_server():
#Holding registersを定義
store = ModbusSlaveContext(
hr=ModbusSequentialDataBlock(address=0,
values=[0,1,2,3]),
)
#slave=1 (single=True)
context = ModbusServerContext(slaves=store,
single=True)
print("Starting Modbus TCP server on 0.0.0.0:5020 ...") # (C) 0.0.0.0:5020で待機 => Piのeth0やwlan0, いずれでもアクセス可
StartTcpServer(context=context, address=("0.0.0.0", 5020))
if __name__ == "__main__":
run_server()
[Terminal]
python server.py
[OUT]
Starting Modbus TCP server on 0.0.0.0:5020 ...
![](https://assets.st-note.com/img/1737869544-RdKUgvr3l6ACOXy0kTbxPNYV.png?width=1200)
6-3-2.Client:Windows
クライアント側のコードは下記の通りです。
[client.py]
from pymodbus.client import ModbusTcpClient
def run_client():
client = ModbusTcpClient("192.168.<残りのIPアドレス>", #サーバー(Raspberry Pi)のIPアドレス
port=5020)
client.connect() #Modbusサーバーに接続
#Holding registerの読み込み
res = client.read_holding_registers(address=0,
count=3,
slave=1)
#サーバーからのレスポンスを表示(エラーチェック付)
if res.isError():
print("Error:", res)
else:
print("Registers:", res.registers) #読み込んだレジスタの値を表示(リスト形式)
client.close() #Modbusサーバーから切断
if __name__ == "__main__":
run_client()
[Terminal]
python client.py
6-3-3.結果
Clientの”client.read_holding_registers”のcount引数を変化させてどのような値が得られるかを確認しました。
(現状オフセットが発生していますが)サーバー側から値を抽出できることが確認できました。
[Terminal(count=1)]
python client.py
[OUT]
Registers: [1]
[IN(count=2)]
python client.py
[OUT]
Registers: [1, 2]
[IN(count=3)]
python client.py
[OUT]
Registers: [1, 2, 3]
[IN(count=4)]
python client.py
[OUT]
Exception response 131 / 0
Error: ExceptionResponse(dev_id=1, transaction_id=1, address=0, count=0,
bits=[], registers=[], status=1)
7.API
7-1.API:Client
7-2.API:Server
7-3.API:REPL (Read Evaluate Print Loop)
7-4.API:Simulator
7-5.ユースケース
参考資料
別添1:Python関係
別添2:Pymodbus-公式Docs
別添3:PyModbus関連記事
https://www.cosel.co.jp/technical/app_guide/exuart/pdf/c4_python.pdf
別添4:Modbus用ソフト
あとがき
OptaとかMELSECでModbus通信できるはずなので、次はこいつらに繋いでみよう
OMRONとかチノーの温調でも遊んでみたいけど個人でやるにはさすがにたかかった・・
https://www.fa.omron.co.jp/products/family/3157/specification.html
https://www.fa.omron.co.jp/products/family/3101/specification.html
![](https://assets.st-note.com/img/1734830295-ThblrXCtzdyxsgqZwFS6ncKI.png?width=1200)
体型的に学べる本がほしいな・・