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 で複数のプロトコルに対応している場合も多いため、現場で使える選択肢を確認してください。

選定の指針: 自社の 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)系

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

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 から始めるのが最も学習コストが低い選択です。

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

関連記事