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: 国内で最も使われている軽量ライブラリ。ビット・ワード・ダブルワード単位の読み書き、ランダム読み書きに対応。日本語ドキュメントあり。ただし最終リリースは 2021 年の v0.3.0 で更新が止まっている点は把握したうえで採用を
  • pymelsec: pymcprotocol に触発されて書き直された改良版(最終リリースは 2022 年の v0.2.5)。いずれも 3E フレームに対応し、4E フレームは未検証の実装扱い

キーエンス 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)系

  • cpppo: EtherNet/IP 用、複雑だが多機能
  • pylogix: Allen-Bradley の ControlLogix / CompactLogix 用、シンプル

OPC UA 系

  • asyncua(旧 opcua-asyncio): OPC UA クライアント / サーバー両対応、非同期
  • opcua: 同期版、メンテナンスモード

初学者の選び方: 自社の PLC が Modbus TCP に対応しているか確認 → 対応していれば pymodbus から始める。ベンダー固有プロトコルが必要な場合のみ専用ライブラリへ進む、という順序が学習コストを最小化します。

5. 最短で動かす手順 — 4 つの実装例

各ライブラリで「とにかく動く」最小コードを示します。エラー処理は省略していますが、後続の章で必ず加える前提です。

5-0. 事前準備 — ライブラリのインストールと動作前提

本記事のコードは Windows + Python 3.10 以降を前提にしています(pymodbus 3.13 系で確認)。各ライブラリは pip でインストールします。

pip install pymodbus       # 5-1, 7 章の実装パターンで使用
pip install pymcprotocol   # 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 年時点の最新系列(3.13.x)に合わせて device_id を使っています。古い記事のコードをコピペして TypeError が出たら、まず pip show pymodbus でバージョンを確認してください。

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 などのエンジニアリングツールから設定します。これらの設定変更はパラメータ書込と 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 が登録されているか

TimeoutError / 応答が返らない

  • PLC が別クライアントで占有されている可能性。同時に接続できるクライアント数は、Ethernet ユニット(または CPU 内蔵 Ethernet ポート)のオープン設定で確保したコネクション数で決まる(内蔵ポートのオープン設定では最大 16)
  • ネットワーク負荷(特に共有スイッチで他通信が多いケース)
  • PLC 側のスキャンタイム遅延(複雑なラダーが組まれている)
  • タイムアウト値が短すぎる。実機のレスポンス時間を測って 3〜5 倍を設定
  • Modbus RTU などシリアル接続(RS-485)の場合は、A/B 線の極性逆接続・終端抵抗の欠落・ボーレート不一致といったハード起因をまず疑う(ソフトをいくら直しても解決しない定番パターン)

補足: 三菱 MC プロトコルの 3E / 4E フレームの違いは、同時接続数ではなくシリアル番号フィールドの有無です。3E フレームは要求 1 件ごとに応答を待つ方式、4E フレームは伝文にシリアル番号を付加して応答を待たずに複数の要求を送り、戻ってきた応答を突合できる方式です。

値が想定外

  • バイト順(エンディアン)の罠: 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 セッションタイムアウト(数分〜数十分)
  • 経路上のルータやファイアウォールが、無通信のセッションを一定時間で破棄している(NAT・セッションテーブルのタイムアウト)
  • 解決:定期的なハートビート送信、または切断時の自動再接続フローを実装

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 ファイルが肥大化しないよう日次ローテートすること、エラー時にループを止めないこと。

パターン 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 タスクスケジューラの「タスクを再起動する」設定や、定期的なハートビートの記録)まで組み込むと安心です。また、コード例の 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 から始めるのが最も学習コストが低い選択です。

本サイトでは、各プロトコルの詳細・実機検証ノウハウ・トラブルシューティングを今後の記事で深掘りしていきます。

関連記事