2026 年 8 月 19 日更新: 最もアクセスの多い三菱 MC プロトコル(SLMP)の章を大幅に加筆しました。pymcprotocol と pymelsec のどちらを選ぶべきかを、筆者の自宅検証環境(Windows 11 / Python 3.12.10)で実際にインストールして確かめた結果とあわせて掲載しています。また「IP を間違えているだけなのに TimeoutError が出て原因を見誤る」という pymcprotocol 固有のつまずきについて、実測値つきの切り分け手順を追加しました。
1. はじめに — なぜ PLC × Python の情報はまとまっていないのか
製造業の現場には、必ずと言っていいほど PLC(プログラマブルロジックコントローラ)が導入されています。三菱、キーエンス、オムロン、シーメンス、富士電機、横河——メーカーは多岐にわたりますが、いずれも生産設備の中核を担う「リアルタイム制御の頭脳」です。
これらの PLC から Python でデータを取り出して可視化したい、別のシステムと連携させたい、遠隔操作したい——そうしたニーズは年々高まっています。しかし、Web 検索で得られる情報は驚くほど断片的です。
理由はシンプルです。PLC の通信プロトコルがメーカーごとに異なり、対応する Python ライブラリも分散しています。「pymodbus」と「pymcprotocol」と「python-snap7」を、それぞれ別の記事で読んで、自分の現場では何を選べばいいか分からない——これが現状です。
本記事では、主要メーカーを横断的に比較しながら、「自分の現場で使う PLC を Python で読み書きする」ための判断材料を整理します。
2. PLC × Python の 5 つのメリット
そもそも、なぜ PLC のデータを Python で扱うのか。SCADA ソフトや専用ツールではなく、Python を選ぶ理由を整理します。
- 圧倒的な低コスト: 本記事で扱うライブラリはいずれも OSS(無償で公開されているソフトウェア)で、ライセンス費用がかからない。専用 SCADA ソフトの導入と比べ、かかるのは主に開発工数のみ
- データの二次活用が容易: 取得後の処理(pandas での集計、Matplotlib での可視化、機械学習、Web 連携、データベース保存)が同じ言語で完結
- 現代的な開発手法が使える: Git によるバージョン管理、ユニットテスト、CI(テストの自動実行)、コードレビュー——専用ツールでは難しいことが普通にできる
- 横展開しやすい: 一度書いたコードを、別の現場・別の PLC に応用しやすい。設定をパラメータ化しておけば設備が変わっても再利用可能
- 学習コミュニティが活発: 困った時に Stack Overflow、GitHub の Issue、Qiita / Zenn の記事で情報が見つかりやすい
具体的な活用シーンとしては、次のようなものが代表例です。
- 設備の稼働率自動集計
- 異常検知 → メール / Slack 通知
- 品質データの自動収集と管理図の生成
- 複数設備のデータを統合した Web ダッシュボード
- レシピデータの一括書き込み(生産品種切替の高速化)
3. メーカー別対応プロトコル一覧
主要メーカーがサポートしている通信プロトコルを整理します。1 つの PLC で複数のプロトコルに対応している場合も多いため、現場で使える選択肢を確認してください。
- 三菱電機(MELSEC): MC プロトコル(3E / 4E フレーム)、SLMP、Modbus TCP(オプションユニット必要)
- キーエンス(KV): KV ソケット通信(上位リンク)、MC プロトコル(3E フレーム)互換通信(KV-7500 / KV-8000 など、対応可否は要機種確認)、Modbus TCP / RTU
- オムロン(CJ / CP / NJ / NX): FINS、EtherNet/IP、Modbus TCP
- シーメンス(S7-1200 / 1500): S7Comm、PROFINET、OPC UA、Modbus TCP
- 富士電機(MICREX): T-LINK、Modbus RTU
- 横河電機(FA-M3): Personal Computer Link、Modbus
- 共通基盤: Modbus TCP / RTU、OPC UA
選定の指針: 自社の PLC が Modbus TCP か OPC UA に対応しているなら、まずそれを使うのが移植性・ライブラリ成熟度の観点でもっとも有利です。メーカー固有プロトコルは、機能性能やレスポンス速度で優位な場面で選択します。
4. 主要 Python ライブラリ一覧 — メーカー別カタログ
2026 年時点で実用的に使えるライブラリを整理します。採用前には GitHub の Star 数・最終更新日で開発が続いているかを自分でも確認しておくと安心です。
三菱 MC プロトコル系
- pymcprotocol: 日本語の解説記事で最もよく紹介されている軽量ライブラリ。ビット・ワード・ダブルワード単位の読み書き、ランダム読み書きに対応。日本語ドキュメントあり。ただし PyPI の最終リリースは 2021 年の v0.3.0 で、以後の新規リリースはない点を把握したうえで採用を
- pymelsec:
pymcprotocolに触発されて書き直された改良版(最終リリースは 2022 年の v0.2.5)。いずれも 3E フレームで実績があり、4E フレームは作者自身が「実装済みだが未テスト」と README で明言している扱い - 更新状況・4E フレームの扱い・どちらを選ぶかは、5-2 の比較表に実測値つきでまとめている
キーエンス KV 系
- pymcprotocol(MC プロトコル互換で流用): KV-7500 / KV-8000 など Ethernet 内蔵の KV シリーズは、三菱 MC プロトコル(3E フレーム)互換の通信に対応しており、PLC 側で MC プロトコル交信を有効化すれば
pymcprotocolがそのまま使える(対応可否・設定手順は機種のユーザーズマニュアルで要確認) - pymodbus(Modbus TCP 対応機): Modbus TCP に対応する構成なら、メーカー横断の
pymodbusも選択肢になる - KV ソケット通信(上位リンク)を直接扱う定番 Python ライブラリは現状見当たらないため、上記 2 つのいずれかを使うのが現実的
シーメンス S7 系
- python-snap7: 事実上のデファクト。低レベル C ライブラリ snap7 の Python ラッパー。S7-300/400/1200/1500 すべてに対応
オムロン FINS 系
- fins: PyPI で公開されているコミュニティ製ライブラリ(
pip install fins)。FINS 経由でメモリエリアの読み書きが可能で、2025 年もリリースが続く現役メンテ。ライセンスが GPLv2 なので、社内ツールへの組み込み方針は事前に確認を - aphyt: 同作者による EtherNet/IP 用パッケージ。NX / NJ シリーズならこちらも選択肢
Modbus 系(メーカー横断)
- pymodbus: Modbus 系のデファクトライブラリ。同期・非同期両対応、TCP / RTU / ASCII 全てサポート、開発活発
- minimalmodbus: シリアル(RTU / ASCII)に特化、軽量
EtherNet/IP(CIP)系
OPC UA 系
- asyncua(旧 opcua-asyncio): OPC UA クライアント / サーバー両対応、非同期
- opcua: 同期版、メンテナンスモード
初学者の選び方: 自社の PLC が Modbus TCP に対応しているか確認 → 対応していれば pymodbus から始める。ベンダー固有プロトコルが必要な場合のみ専用ライブラリへ進む、という順序が学習コストを最小化します。
5. 最短で動かす手順 — 4 つの実装例
各ライブラリで「とにかく動く」最小コードを示します。エラー処理は省略していますが、後続の章で必ず加える前提です。
5-0. 事前準備 — ライブラリのインストールと動作前提
本記事のコードは Windows + Python 3.10 以降を前提にしています(筆者の自宅検証環境の pymodbus 3.15.0 / Python 3.12.10 で確認)。各ライブラリは pip でインストールします。
pip install pymodbus # 5-1, 7 章の実装パターンで使用
pip install pymcprotocol # 5-2 三菱 MC プロトコル(既存の社内資産や参考記事に合わせる場合)
pip install pymelsec # 5-2 三菱 MC プロトコル(これから新規に書く場合)
pip install python-snap7 # 5-3 シーメンス S7
pip install asyncua # 5-4 OPC UA
社内プロキシで pip install が通らない場合は、環境変数 HTTP_PROXY / HTTPS_PROXY を設定してください(アドレスは情シスに確認)。インターネットに出られない閉域網の PC には、接続できる別 PC で pip download パッケージ名 -d ./pkgs と依存ごとダウンロードし、USB 等で持ち込んで pip install --no-index --find-links ./pkgs パッケージ名 でインストールします。このとき、ダウンロードする PC と持ち込み先の Python はバージョン・OS を揃えてください(揃えられない場合は pip download --python-version 310 --only-binary=:all: のように持ち込み先のバージョンを指定してダウンロードします)。詳細は 学習ロードマップ の STEP 2 も参照してください。
5-1. pymodbus で Modbus TCP の機器から読む
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient(host='192.168.1.10', port=502)
client.connect()
# 保持レジスタ(Holding Register)を 0 番地から 10 個読む
result = client.read_holding_registers(address=0, count=10, device_id=1)
print(result.registers)
client.close()
pymodbus はバージョンによって「相手局を指定する引数」の名前が変わっています。2.x では unit、3.x 系(3.3.0 以降)では slave、そして 3.10.0 で device_id に改名されました。本記事は 2026 年 8 月時点の最新版である 3.15.0(2026-08-13 リリース)に合わせて device_id を使っています。古い記事のコードをコピペして TypeError が出たら、まず pip show pymodbus でバージョンを確認してください。Modbus 側の詳細(レジスタ種別の使い分け、RTU での運用、例外応答の扱い)は Python で Modbus 通信を実装する — pymodbus 完全ガイドにまとめています。
5-2. 三菱 MC プロトコル(SLMP)を Python から読み書きする
MC プロトコルと SLMP は何が違うのか
先に用語を整理しておきましょう。ここを混同すると、ライブラリ選定でつまずきます。
- MC プロトコル(MELSEC Communication Protocol) — 三菱電機の PLC がイーサネット / シリアル経由でデバイス(D、M、X、Y など)の読み書きを受け付けるための通信仕様。
- SLMP(Seamless Message Protocol) — MC プロトコルを上位互換の形で汎用化し、CC-Link 協会が公開している仕様。MC プロトコルの 3E フレームは SLMP のサブセットにあたるため、実装としてはほぼ同じものを指していると考えて差し支えありません。
- 3E フレーム / 4E フレーム — 電文の形式。3E が広く使われ、4E はシリアル番号による応答の対応付けが加わったもの。Python ライブラリは
Type3E/Type4Eというクラス名でこれを表現します。
つまり「MC プロトコルで通信したい」「SLMP で通信したい」は、Python 側から見ればほぼ同じ作業になります。以下は 3E フレームを前提とした説明です。
pymcprotocol と pymelsec、2026 年にどちらを選ぶか
日本語の記事で最もよく紹介されているのは pymcprotocol ですが、PyPI へのリリースは 2021 年で止まったままです。ただし対抗馬の pymelsec も最終リリースは 2022 年で、2026 年 8 月時点ではどちらも新規リリースが出ていません。つまり選定基準は「開発の活発さ」ではなく、API の使いやすさと、自分たちで保守できるかになります。
| 項目 | pymcprotocol | pymelsec |
|---|---|---|
| 最新版 / 公開日 | 0.3.0 / 2021-04-26 | 0.2.5 / 2022-09-12 |
| GitHub の最終 push(2026-08-19 に API で取得) | 2025-02-14 | 2022-09-12 |
| GitHub Star / Fork(同上) | 103 / 26 | 31 / 11 |
requires_python(インストール可否を決める宣言) | >=3.0,<4.0 | >=3.7.0 |
| PyPI の classifiers(対応を表明する分類ラベル) | 3.4〜3.9 | Python バージョンの記載なし |
| フレーム | 3E / 4E(Type3E / Type4E クラスを提供。ただし 4E は作者自身が「未テスト」と明記) | 3E / 4E(同上) |
| データ型の扱い | ワード単位・ビット単位の一括読み書き | 型指定(SWORD / FLOAT / DOUBLE 等)に対応 |
with 構文 | 非対応(close() を明示) | 対応 |
| 日本語の解説記事 | 多い | 少ない |
表のとおり、リポジトリへの最後の push はむしろ pymcprotocol のほうが新しく、利用者数の目安になる Star 数も 3 倍以上あります。「pymelsec のほうが保守されているから安心」とは言えません。
4E フレームについて補足します。両ライブラリとも README に「4E は実装済みだが未テスト」と書かれており、これはライブラリ作者自身の警告です。加えて PLC 側にも制約があり、三菱電機の FA サイトの FAQ(No.17177「LCPU の内蔵 Ethernet ポートで 4E フレームの使用について」)では L シリーズの CPU 内蔵 Ethernet ポートは 4E フレーム非対応と案内されています(2026 年 8 月確認)。新規に組むなら、まず 3E フレームを選ぶのが無難です。
動作確認(2026 年 8 月・筆者の自宅検証環境 / Windows 11 / Python 3.12.10): PyPI 上の pymcprotocol は、classifiers(対応を表明する分類ラベル)が Python 3.9 までのままです。この表示だけを見て「新しい Python では使えない」と判断されがちです。しかしインストール可否を決めるのは requires_python のほうで、こちらは >=3.0,<4.0。3.12 も対象に含まれています。
実際に仮想環境で pip install pymcprotocol pymelsec を実行したところ、両方とも Python 3.12.10 でインストールでき、import も成功しました(pymcprotocol 0.3.0 / pymelsec 0.2.5)。Type3E() のインスタンス生成でも DeprecationWarning は出ていません。つまり「classifiers が古い=最新の Python で動かない」ではなく、3.12 系で動くのは仕様どおりです。ここが分かるだけで、ライブラリ選定でムダに悩む時間を削れます。
そのうえで選定の目安は次のとおりです。既存の社内資産や参考記事に合わせるなら pymcprotocol、これから新規に書くなら pymelsec。pymelsec は型指定つきの読み書きと with 構文が使えるぶん、長期運用するコードでは扱いやすくなります。どちらも新規リリースは止まっているため、採用するなら「自社で読める規模のコードか」も見ておいてください。配布物の Python コードは pymcprotocol が約 1,500 行、pymelsec が約 2,250 行です。いざというとき社内で読み切れる大きさで、これは更新が止まったライブラリを選ぶうえで重要な保険になります。
pymcprotocol の最小コード
import pymcprotocol
plc = pymcprotocol.Type3E(plctype='Q') # 機種を指定。4E フレームなら Type4E()
plc.soc_timeout = 5 # 既定は 2 秒(後述のエラー切り分けで重要)
plc.connect('192.168.1.20', 5007)
# データレジスタ D0 から 10 ワード分を読む
values = plc.batchread_wordunits(headdevice='D0', readsize=10)
print(values)
# D100 に値を書き込む(※実機では停止中の設備・検証機で試すこと)
plc.batchwrite_wordunits(headdevice='D100', values=[100, 200, 300])
plc.close()
機種の指定を忘れずに: Type3E() は引数を省略すると Q シリーズ扱いになります。指定できるのは "Q" / "L" / "QnA" / "iQ-L" / "iQ-R" の 5 種類で、これは pymcprotocol と pymelsec のどちらも同じです(両ライブラリのソースで確認)。iQ-R だけは電文のサブコマンドとデバイス指定部が他機種と違います(ワード単位の命令ではサブコマンドが 0x0000 から 0x0002 に、ビット単位の命令では 0x0001 から 0x0003 に変わり、バイナリ交信ではデバイス番号が 3 バイトから 4 バイト、デバイスコードが 1 バイトから 2 バイトに変わる)。そのため iQ-R を "Q" のまま叩くと、接続はできるのに読み書きだけ失敗します——6 章の切り分け手順 3 とまったく同じ症状です。逆に電文のヘッダそのものが変わるのは 3E フレームと 4E フレームを切り替えたときで、機種指定では変わりません。
FX5U(iQ-F)を使っている場合: 上の 5 種類に FX5U 向けの指定値は用意されていないため、この 2 つのライブラリでそのまま扱えるかは実機で確認が必要です。進め方は 2 段階で考えてください。まず、自機種のユーザーズマニュアルで CPU 内蔵 Ethernet が対応する交信フレーム(SLMP / MC プロトコル)を確認します。三菱電機 FA の製品ページでは FX5U の CPU 内蔵 Ethernet は SLMP 通信に対応と案内されているため、PLC 側が SLMP を話せるなら、詰まるとすればライブラリ側のデバイスコード対応のほうです。次に、それでも通らない場合や確実性を優先したい場合は、Modbus TCP に対応する構成であれば 5-1 の pymodbus のコードがそのまま使えます。なお筆者は FX5U の実機を持っていないため、いずれの経路も動作は保証できません。
pymelsec の最小コード
from pymelsec import Type3E
from pymelsec.constants import DT
# with を抜けると自動で切断される
with Type3E(host='192.168.1.20', port=5007, plc_type='Q') as plc:
# 型を指定して D0 から 2 点読む(SWORD = 符号付き 16bit)
values = plc.batch_read(ref_device='D0', read_size=2, data_type=DT.SWORD)
for v in values:
print(v.device, v.value)
型を指定して読むときのメソッドは read() ではなく batch_read() です。実際のシグネチャは batch_read(ref_device, read_size, data_type) で、read() のほうは data_type を受け取りません。read() に渡すのも、文字列ではなく Tag オブジェクトのリストです。戻り値はどちらも Tag のリストなので、v.device / v.value で取り出せます。plc_type の指定は pymcprotocol と同じ 5 種類です。
pymelsec で最初につまずく点: コンストラクタは Type3E(host=..., port=...) の形で接続先を受け取ります。ところが connect() を自分で呼ぶ場合は、connect(ip, port) と同じ接続先をもう一度渡す必要があります(引数名も host ではなく ip)。with 構文なら内部で処理されるため、こちらを使うほうが安全です。
PLC 側の設定を忘れずに
三菱 PLC の通信ユニット側で「MC プロトコル交信を有効化」「ポート番号設定」「クライアント IP の登録」が必要です。設定はエンジニアリングツールから行いますが、ツールは機種の世代で分かれます。Q / L シリーズは GX Works2 系、iQ-R / iQ-F(FX5U)は GX Works3 です。コード側の機種指定(plctype / plc_type)と、設定に使うツールは必ずセットで確認してください。これらの設定変更は、パラメータ書き込みと PLC のリセット(再起動)を伴う場合があります。稼働中の設備では、段取り替えや非稼働日などの計画停止のタイミングで実施してください。
5-3. python-snap7 でシーメンス S7
import snap7
from snap7.util import get_int
plc = snap7.client.Client()
plc.connect('192.168.1.30', rack=0, slot=1)
# DB1 の 0 バイト目から 32 バイト読む
data = plc.db_read(db_number=1, start=0, size=32)
# バイト列から INT 型で取り出す
value = get_int(data, 0)
print(value)
plc.disconnect()
シーメンス S7-1200/1500 では、PLC 側で「PUT/GET 通信を許可」「最適化なしの DB ブロック」の設定が必要です。既定では許可されていないことが多いので注意してください。こちらもハードウェア設定のコンパイル・ダウンロードが必要になるため、稼働中設備では計画停止時に実施します。なお、接続できない場合は slot=0 も試してください(機種・構成により異なります)。
5-4. asyncua で OPC UA サーバーから読む
import asyncio
from asyncua import Client
async def main():
async with Client(url='opc.tcp://192.168.1.40:4840') as client:
node = client.get_node('ns=2;s=Channel1.Device1.Tag1')
value = await node.read_value()
print(value)
asyncio.run(main())
async def / await / asyncio.run() は Python の非同期処理の書き方です。asyncua はこの書き方が前提のライブラリなので、初めての方はこのコードの形を「おまじない」としてそのままコピペして使って構いません(仕組みの理解は後回しで OK)。また、ns=2;s=Channel1.Device1.Tag1 のようなノード ID は環境ごとに異なります。無料の OPC UA クライアントツール UaExpert でサーバーに接続し、ツリーから対象タグを探して確認するのが確実です。
OPC UA は仕様が大きいぶんセキュリティ・認証の選択肢も多いため、本格運用時は証明書による認証を検討してください。
6. よくあるエラーと対策
PLC 通信で頻発するエラーと、その対処法を整理します。エラーメッセージから原因を逆引きできるよう、症状ベースで並べます。
ConnectionRefusedError / 接続できない
- PLC 側の通信ユニット設定(IP アドレス、サブネットマスク、ポート番号)を再確認
- PC と PLC が同一サブネットにあるか確認(
pingで疎通テスト) - Windows ファイアウォール、社内ファイアウォールがブロックしていないか
- クライアント IP の事前登録が必要な PLC(三菱、シーメンスなど)では、自分の IP が登録されているか
要注意: pymcprotocol では「接続拒否」が TimeoutError として出ることがある
IP アドレスやポート番号を間違えている場合、本来は「接続を拒否された」ことを示す ConnectionRefusedError が出てほしいところです。ところが pymcprotocol では、同じ原因なのに TimeoutError: timed out が返ることがあります。これを「ネットワークが遅い」「PLC の応答が遅い」と誤診すると、原因から遠ざかります。
原因はライブラリ既定のソケットタイムアウトの短さです。pymcprotocol.Type3E の soc_timeout は既定で 2 秒です。ところが Windows では、閉じたポートへの接続拒否が返るまでに 2 秒前後かかることがあります。その結果、拒否が返るより先にタイムアウトが成立してしまいます。
実測(2026 年 8 月・筆者の自宅検証環境 / Windows 11 / Python 3.12.10 / pymcprotocol 0.3.0): 待ち受けのないローカルポート(127.0.0.1 の閉じたポート)へ接続したときの結果は次のとおりでした。
soc_timeout | 発生した例外 | 戻るまでの時間 |
|---|---|---|
| 0.5 秒 | TimeoutError | 0.51 秒 |
| 2 秒(既定) | TimeoutError | 2.00〜2.01 秒(3 回試行) |
| 5 秒 | ConnectionRefusedError | 2.05 秒 |
(ライブラリを使わない素の socket) | ConnectionRefusedError | 2.03 秒 |
この表の測定条件は同じ PC の中にある閉じたポート、つまり拒否(RST)が確実に返ってくる状況です。現場のネットワークは条件が違うので、そのうえで次の順に切り分けてください。
plc.soc_timeout = 5のようにタイムアウトを長めにして再実行します。ここでConnectionRefusedErrorに変われば、原因はネットワーク遅延ではなく宛先が間違っている(または PLC 側でポートが開いていない)と確定できます。注意したいのは逆のケースです。5 秒にしても
TimeoutErrorのままだからといって、「宛先は合っている」ことにはなりません。指定した IP にホストが存在しない場合や、ファイアウォールがパケットを黙って破棄している場合は、拒否そのものが返ってこないためです。この場合はまずpingで相手の存在を確認してください。ping が通らなければ、ここから先は PLC ではなくネットワークの問題です——IP・サブネットマスク・ファイアウォール・arp -aの登録といったネットワーク側から先に潰し、ping が通ってからこの手順に戻ってきてください。ping が通るのにTimeoutErrorのままなら、次の手順 2 へ進みます。- それでも
TimeoutErrorのままなら、Python の標準ライブラリだけで直接叩いて、ライブラリの都合を切り離します。import socket s = socket.socket() s.settimeout(5) s.connect(('192.168.1.20', 5007)) # ここで出る例外が本当の症状ここで出た例外がそのまま本当の症状です。
socketをもう一段深く使う方法は Python と TCP/IP で産業機器とリアルタイム通信する完全ガイドにまとめています。 - 素の
socketで接続できるのに読み書きだけ失敗する場合は、TCP は通っていて MC プロトコルの交信設定(ポートのオープン設定・交信手順・局番)が合っていない状態です。実際、ダミーサーバー(Python のsocketでlisten()だけして応答を返さないもの)を立てて試すと、connect()は成功します。TimeoutErrorになるのは、その後のbatchread_wordunits()です。この場合に直すのは PC 側ではなく PLC 側です。エンジニアリングツール(Q / L シリーズなら GX Works2 系、iQ-R / iQ-F なら GX Works3)で次の 3 点を順に確認してください。
- 対象機器との接続構成設定で、そのポートが「MC プロトコル」として開いているか(オープン設定の有無)
- プロトコル(TCP / UDP)と自局ポート番号が、コードで指定した値と一致しているか
- 交信データコード(バイナリ / ASCII)が、ライブラリの前提であるバイナリになっているか
あわせて、コード側の機種指定(
plctype/plc_type)が実機と合っているかも確認してください。設定画面の名称は機種とツールのバージョンで異なります。設定変更にはパラメータ書き込みと PLC のリセットを伴うため、5-2「PLC 側の設定を忘れずに」のとおり計画停止のタイミングで実施してください。
TimeoutError / 応答が返らない
- PLC が別クライアントで占有されている可能性。同時に接続できるクライアント数は、Ethernet ユニット(または CPU 内蔵 Ethernet ポート)のオープン設定で確保したコネクション数で決まる。上限は機種によって異なるので、使用機種のユーザーズマニュアルで必ず確認を(たとえば FX5U の CPU 内蔵 Ethernet は最大 8 コネクションと案内されている)
- ネットワーク負荷(特に共有スイッチで他通信が多いケース)
- PLC 側のスキャンタイム遅延(複雑なラダーが組まれている)
- タイムアウト値が短すぎる。実機のレスポンス時間を測って 3〜5 倍を設定
- Modbus RTU などシリアル接続(RS-485)の場合は、A/B 線の極性逆接続・終端抵抗の欠落・ボーレート不一致といったハード起因をまず疑う(ソフトをいくら直しても解決しない定番パターン)。配線・パラメータの詰め方は Python と pyserial で RS-232C / RS-485 シリアル通信を実装する完全ガイドで詳しく扱っている
補足: 三菱 MC プロトコルの 3E / 4E フレームの違いは、同時接続数ではなくシリアル番号フィールドの有無です。4E フレームは電文にシリアル番号を付加することで、どの要求に対する応答なのかを突合できる方式です(複数系統から要求を出すときの取り違え防止に効きます)。なお本記事で紹介した Type4E クラスの実装は、3E と同じく 1 要求 1 応答で送受信します。
値が想定外
- バイト順(エンディアン)の罠: PLC によってビッグエンディアン・リトルエンディアン・ワードスワップが異なる。仕様書を必ず確認
- データ型の取り違え: INT(16bit)と DINT(32bit)の混同、符号付きと符号なしの違い
- レジスタアドレスのオフセット: Modbus では仕様上のアドレス(40001 など)と実際のアドレス(0 など)が異なる
文字列が文字化け
- PLC 内文字列は多くの場合 SJIS(CP932)。Python 側で UTF-8 を仮定すると
UnicodeDecodeError - 解決:
bytes.decode('cp932', errors='replace')。ログ・CSV まで含めた体系的な対策は 製造現場で発生する文字コード問題(CP932 vs UTF-8)の完全対策にまとめている
同時アクセスでエラー
- PLC によっては並行接続数が 1〜数本に制限されている
- 解決: 1 プロセスからシーケンシャルにアクセス、または接続プール(接続を使い回して同時接続数を一定以下に抑える仕組み)で並列度を制御
長時間運用で接続が切れる
- PLC 側の TCP セッションタイムアウト(数分〜数十分)
- 経路上のルータやファイアウォールが、無通信のセッションを一定時間で破棄している(NAT・セッションテーブルのタイムアウト)
- 解決: 定期的なハートビート送信、または切断時の自動再接続フローを実装。再接続の記録を残しておくと原因が追いやすくなる(24 時間動くアプリのためのロギング設計)
7. 現場で使える実装パターン 3 選
実装の出発点として使える 3 つの典型パターンを紹介します。コードは pymodbus を例にしていますが、考え方は他ライブラリでも同じです。
パターン A: 定期データ収集(ロガー型)
import csv
import os
import time
from datetime import datetime
from pymodbus.client import ModbusTcpClient
INTERVAL_SEC = 5
HOST = '192.168.1.10'
client = ModbusTcpClient(HOST, port=502)
write_header = not os.path.exists('log.csv')
with open('log.csv', 'a', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
if write_header: # 初回のみヘッダー行を書く(後で Excel で開いても列の意味が分かる)
writer.writerow(['timestamp', 'temp', 'pressure', 'flow', 'status'])
while True:
try:
if not client.connected:
client.connect()
res = client.read_holding_registers(address=0, count=4, device_id=1)
if res.isError():
raise IOError(f'PLC error: {res}')
row = [datetime.now().isoformat()] + list(res.registers)
writer.writerow(row)
f.flush()
except Exception as e:
print(f'[ERROR] {e}')
client.close()
time.sleep(INTERVAL_SEC)
用途: 稼働ログ、品質データ収集、温度・流量などのトレンド記録。注意点は、CSV ファイルが肥大化しないよう日次ローテートすること、エラー時にループを止めないこと。CSV の限界(検索の遅さ・同時アクセス・集計のしづらさ)を感じ始めたら、製造データを SQLite で管理する — Excel からデータベース運用へへの移行を検討してください。
パターン B: 異常検知 → 通知
import time
import smtplib
from email.message import EmailMessage
from pymodbus.client import ModbusTcpClient
THRESHOLD = 100
client = ModbusTcpClient('192.168.1.10', port=502)
last_alert = 0
ALERT_INTERVAL = 300 # 5 分間は同じ警告を繰り返さない
def send_alert(value):
msg = EmailMessage()
msg['Subject'] = f'[警告] 設備値超過: {value}'
msg['From'] = 'monitor@example.com'
msg['To'] = 'ops@example.com'
msg.set_content(f'監視値が {value} に到達しました。')
with smtplib.SMTP('smtp.example.com') as s:
s.send_message(msg)
while True:
try:
if not client.connected:
client.connect()
res = client.read_holding_registers(address=0, count=1, device_id=1)
if res.isError():
raise IOError(f'PLC error: {res}')
value = res.registers[0]
now = time.time()
if value > THRESHOLD and (now - last_alert) > ALERT_INTERVAL:
send_alert(value)
last_alert = now
except Exception as e:
# 監視スクリプトは「黙って死ぬ」のが最悪。エラーでもループを止めない
print(f'[ERROR] {e}')
client.close()
time.sleep(1)
用途: 異常監視、設備保全。注意点は、チャタリング(しきい値付近の振動)対策、通知の重複防止、夜間・休日の対応ポリシー。監視スクリプトは通信エラーで停止すると「監視が止まったことに誰も気づかない」状態になるため、上記のように try / except でループを守るのが最低条件です。加えて、スクリプト自体の死活監視まで組み込んでおくと、より確実になります。具体的には、Windows タスクスケジューラの「タスクを再起動する」設定や、定期的なハートビートの記録です。常駐のさせ方そのものは Python アプリを Windows サービスとして常駐させる方法で扱っています。また、コード例の SMTP 送信は最小構成です。社内メールサーバーは認証・TLS が必須のことが多いため、サーバー仕様に合わせて starttls() / login() を追加してください。
パターン C: 双方向制御(読み書き両方)
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.10', port=502)
client.connect()
# 状態取得(書き込みを伴う制御こそ、エラー応答の確認を省略しない)
res = client.read_holding_registers(address=100, count=1, device_id=1)
if res.isError():
raise IOError(f'PLC error: {res}')
status = res.registers[0]
# 状態に応じて制御信号を書き込み
if status == 0: # 待機中なら開始信号を送る
wres = client.write_register(address=200, value=1, device_id=1)
if wres.isError():
raise IOError(f'PLC write error: {wres}')
client.close()
用途: レシピ切替、リモート起動・停止、複数設備の協調制御。書き込みは現場の安全に直結するため、十分な検証と権限管理が必要です。書き込みアドレスはホワイトリストで限定し、誤操作で意図しないアドレスを書かない設計を心がけてください。あわせて、PC 側の対策だけに頼らないでください。PLC 側のラダーでも、書き込み値の範囲チェックや「運転条件が成立したときのみ受け付ける」インターロックを組みます。二重に守るのが現場の鉄則です。
8. おわりに
本記事では、主要メーカーの PLC と Python を通信させる方法を、横断的に整理しました。最初の選択は迷うかもしれませんが、自社の PLC が Modbus TCP に対応していれば pymodbus から始めるのが最も学習コストが低い選択です。
本サイトでは、各プロトコルの詳細・実機検証ノウハウ・トラブルシューティングを今後の記事で深掘りしていきます。
本記事の内容に誤りや古い情報をお気づきの場合、またお使いの機種で挙動が違ったという情報がありましたら、お問い合わせフォームからお知らせください。いただいた実機の情報は、機種名を伏せたうえで記事の改善に反映させていただきます(なお、個別のトラブルシューティング代行はお受けしていません)。その際、貴社の機密・社外秘にあたる情報(ライン名・取引先名・設備のパラメータ・ログ原本など)は送信しないでください。ライブラリ名・PLC の型式・エラーメッセージなど、公開マニュアルの範囲でご記入いただければ十分です。
⚠️ 実機接続時の注意: 本記事のサンプルは検証用(ローカル環境・ダミーサーバー・シミュレータ想定)で、実機の PLC に接続した検証ではありません。実際の PLC に接続する場合は、必ず読み出し専用の処理だけで先に疎通確認してください。とくに batchwrite_wordunits() や write_register() のような書き込みは、設備の予期しない動作・物損・人身事故に直結します。書き込みの検証は停止中の設備または検証機(実ラインから切り離した実験用の構成)で行い、本番適用時はラダー側のインターロック(運転条件が成立したときのみ受け付ける・書き込み値の範囲チェック)を必ず前提としてください。24 時間稼働している PLC への接続・設定変更・書き込みは、自社の安全基準と保全側の承認を経ることが前提です。本記事のコード・設定例による損害・事故(機器故障・ライン停止・不良流出・人身事故を含む)について筆者・GenbaPy は責任を負いません。