1. はじめに — なぜ PLC × Python の情報はまとまっていないのか
製造業の現場には、必ずと言っていいほど PLC(プログラマブルロジックコントローラ)が導入されています。三菱、キーエンス、オムロン、シーメンス、富士電機、横河——メーカーは多岐にわたりますが、いずれも生産設備の中核を担う「リアルタイム制御の頭脳」です。
これらの PLC から Python でデータを取り出して可視化したい、別のシステムと連携させたい、遠隔操作したい——そうしたニーズは年々高まっています。しかし、Web 検索で得られる情報は驚くほど断片的です。
理由はシンプルで、PLC の通信プロトコルがメーカーごとに異なり、対応する Python ライブラリも分散しているからです。「pymodbus」と「pymcprotocol」と「python-snap7」を、それぞれ別の記事で読んで、自分の現場では何を選べばいいか分からない——これが現状です。
本記事では、主要メーカーを横断的に比較しながら、「自分の現場で使う PLC を Python で読み書きする」ための判断材料を整理します。
2. PLC × Python の 5 つのメリット
そもそも、なぜ PLC のデータを Python で扱うのか。SCADA ソフトや専用ツールではなく、Python を選ぶ理由を整理します。
- 圧倒的な低コスト: 専用 SCADA ソフトの数分の一〜数十分の一の費用で同等機能を実装できる。ライブラリの多くが OSS
- データの二次活用が容易: 取得後の処理(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 ソケット通信(上位リンク)、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: 国内で最も使われている軽量ライブラリ。ビット・ワード・ダブルワード単位の読み書き、ランダム読み書きに対応。日本語ドキュメントあり
- pymelsoft: pymcprotocol より多機能。複数 PLC への接続、各種ヘルパー関数あり
シーメンス S7 系
- python-snap7: 事実上のデファクト。低レベル C ライブラリ snap7 の Python ラッパー。S7-300/400/1200/1500 すべてに対応
オムロン FINS 系
- fins: コミュニティ製、シンプル
- finsControl: より多機能だが更新頻度はそれほど高くない
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-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, slave=1)
print(result.registers)
client.close()
pymodbus 3.x 以降では引数名が address, count, slave に統一されています。古いバージョンの記事では unit となっていることがあるので注意してください。
5-2. pymcprotocol で三菱 MC プロトコル
import pymcprotocol
plc = pymcprotocol.Type3E()
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()
三菱 PLC の通信ユニット側で「MC プロトコル交信を有効化」「ポート番号設定」「クライアント IP の登録」が必要です。GX Works3 などのエンジニアリングツールから設定します。
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 ブロック」の設定が必要です。デフォルトでは許可されていないことが多いので注意。
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())
OPC UA は仕様が大きいぶんセキュリティ・認証の選択肢も多いため、本格運用時は証明書による認証を検討してください。
6. よくあるエラーと対策
PLC 通信で頻発するエラーと、その対処法を整理します。エラーメッセージから原因を逆引きできるよう、症状ベースで並べます。
ConnectionRefusedError / 接続できない
- PLC 側の通信ユニット設定(IP アドレス、サブネットマスク、ポート番号)を再確認
- PC と PLC が同一サブネットにあるか確認(
pingで疎通テスト) - Windows ファイアウォール、社内ファイアウォールがブロックしていないか
- クライアント IP の事前登録が必要な PLC(三菱、シーメンスなど)では、自分の IP が登録されているか
TimeoutError / 応答が返らない
- PLC が別クライアントで占有されている可能性。三菱の MC プロトコルは 3E フレームなら多重接続可だが、4E フレームでは制限あり
- ネットワーク負荷(特に共有スイッチで他通信が多いケース)
- PLC 側のスキャンタイム遅延(複雑なラダーが組まれている)
- タイムアウト値が短すぎる。実機のレスポンス時間を測って 3〜5 倍を設定
値が想定外
- バイト順(エンディアン)の罠: PLC によってビッグエンディアン・リトルエンディアン・ワードスワップが異なる。仕様書を必ず確認
- データ型の取り違え: INT(16bit)と DINT(32bit)の混同、符号付きと符号なしの違い
- レジスタアドレスのオフセット: Modbus では仕様上のアドレス(40001 など)と実際のアドレス(0 など)が異なる
文字列が文字化け
- PLC 内文字列は多くの場合 SJIS(CP932)。Python 側で UTF-8 を仮定すると
UnicodeDecodeError - 解決:
bytes.decode('cp932', errors='replace')
同時アクセスでエラー
- PLC によっては並行接続数が 1〜数本に制限されている
- 解決:1 プロセスからシーケンシャルにアクセス、または接続プールで並列度を制御
長時間運用で接続が切れる
- PLC 側の TCP セッションタイムアウト(数分〜数十分)
- ネットワーク機器(スイッチ、ルータ)の TIME_WAIT
- 解決:定期的なハートビート送信、または切断時の自動再接続フローを実装
7. 現場で使える実装パターン 3 選
実装の出発点として使える 3 つの典型パターンを紹介します。コードは pymodbus を例にしていますが、考え方は他ライブラリでも同じです。
パターン A: 定期データ収集(ロガー型)
import time
import csv
from datetime import datetime
from pymodbus.client import ModbusTcpClient
INTERVAL_SEC = 5
HOST = '192.168.1.10'
client = ModbusTcpClient(HOST, port=502)
with open('log.csv', 'a', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
while True:
try:
if not client.connected:
client.connect()
res = client.read_holding_registers(address=0, count=4, slave=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 ファイルが肥大化しないよう日次ローテートすること、エラー時にループを止めないこと。
パターン 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)
client.connect()
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:
res = client.read_holding_registers(address=0, count=1, slave=1)
value = res.registers[0]
now = time.time()
if value > THRESHOLD and (now - last_alert) > ALERT_INTERVAL:
send_alert(value)
last_alert = now
time.sleep(1)
用途: 異常監視、設備保全。注意点は、チャタリング(しきい値付近の振動)対策、通知の重複防止、夜間・休日の対応ポリシー。
パターン C: 双方向制御(読み書き両方)
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.10', port=502)
client.connect()
# 状態取得
status = client.read_holding_registers(address=100, count=1, slave=1).registers[0]
# 状態に応じて制御信号を書き込み
if status == 0: # 待機中なら開始信号を送る
client.write_register(address=200, value=1, slave=1)
client.close()
用途: レシピ切替、リモート起動・停止、複数設備の協調制御。書き込みは現場の安全に直結するため、十分な検証と権限管理が必要です。書き込みアドレスはホワイトリストで限定し、誤操作で意図しないアドレスを書かない設計を心がけてください。
8. おわりに
本記事では、主要メーカーの PLC と Python を通信させる方法を、横断的に整理しました。最初の選択は迷うかもしれませんが、自社の PLC が Modbus TCP に対応していれば pymodbus から始めるのが最も学習コストが低い選択です。
本サイトでは、各プロトコルの詳細・実機検証ノウハウ・トラブルシューティングを今後の記事で深掘りしていきます。