ラズパイPythonと三菱PLCをネットワーク経由で連携させる方法

2021/05/04

IoT Python ガジェット ラズパイ 開発

ラズパイと三菱PLCを接続した状態

この記事ではラズパイと三菱PLCをネットワーク経由で連携させる方法を紹介します。FAでもIoTニーズが高まってきていますよね。といってもPLCと連動してくれないと出来ることは限られる。でもIO配線はめんどくさい。というわけでラズパイをネットワーク経由でPLCと連携させる方法を紹介します。トリガーをPLCからラズパイへ送り、画像処理させて、結果をPLCへ返すような動きに使えます。記事ではLAN直結でテストしていますが、工場に生産工程用のWiFiがあれば電源配線だけで済むのでかなり使えそうですね。

イーサネットユニットでの接続はこちら。

この記事でわかること

ラズパイと三菱PLC(シーケンサ)をネットワーク接続し、ラズパイ上のPythonからMCプロトコルでPLCのIOを読み書きする方法がわかります。動作確認のサンプルとしてオルタネート回路をラズパイのPythonで走らせます。三菱PLC側のラダーを書く必要がありますが数行程度です。ラズパイ側はサンプルコードコピペでOKです。

  • 三菱PLC設定方法
  • ラズパイIP設定方法
  • Pythonライブラリ pymcprotocolインストール方法
  • pymcprotocolを使った通信方法

前提条件

まず前提条件となる私の環境です。

Raspberry Pi 4 Model B 8GB
Raspberry Pi OS Raspbian GNU/Linux 10 (buster)
VSCode 1.55.2
Python 3.7.3
pymcprotocol 0.3.0
三菱PLC Q03UDECPU
CPU-LANポート使用
GX Works2 1.586L

ラズパイのPython開発環境はVisual Studio Codeを使用します。インストールはこちらを参照してください。

通信設定

三菱PLCパラメータ設定

まずはPLCを設定します。PLCのIPアドレスはローカル192.168.1.2を使用します。MCプロトコルはTCPポートを使用します。ラズパイとPLCをLANケーブル直結する場合はIPアドレスの設定だけでOKです。IPとMCプロトコルの設定方法はこちらを参照してください。

テストに使用するIPとポート

PLCのIPアドレス 192.168.1.2
MCプロトコルのポート TCP 1026

設定が完了したらPLCを再起動してください。再起動しないとパラメータ設定が反映されません。

ラズパイのIP設定

ラズパイのIPを192.168.1.3に設定します。 こちらもLANでPLC直結の場合はIPアドレスだけセットすればOKです。右上のネットワーク設定からeth0を選択してIPを入力するだけでOKです。

ラズパイのIPアドレス 192.168.1.3
LANのIPアドレス設定

pymcprotocolのインストール

PythonでMCプロトコルを扱うためのライブラリをインストールします。LXTerminalをひらいて次のコマンドを実行してください。

pip install pymcprotocol

インストールは以上です。ドキュメントはこちら。

LXTerminalでPythonを立ち上げてimportすることで動作確認できます。

python3
import pymcprotocol

import後にエラーがなければインストールに問題はありません。

pymcprotocolをインポート

pymcprotocolを使った通信テスト

テスト準備

準備できましたので実際にテストしていきます。ラズパイとPLCをLANケーブルで直接接続しましょう。

テストの動作パターンですが、PLC側でY0出力ON/OFFを繰り返し、それをラズパイ側で読み取ってオルタネート出力をPLCのX0に書き込むようにします。オルタネートについてはこちらを参照してください。

ラダー記述

テスト用のラダーを記述します。出力Y0をON/OFF繰り返す回路です。とりあえず1秒ON、1秒OFFにします。

テスト用のラダー回路

M1、M2はただのモニタ用です。編集が終わったらPLCに書き込んでRUNになっているか確認してください。MCプロトコルでの通信確認が目的ですので、ラダーは正直なんでも良いです。面倒であればY0を強制ON/OFFでもかまいません。

Pythonコード実行

VSCodeで新しくpyファイルを作成し、そこに以下のコードをコピペしてください。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from logging import getLogger, StreamHandler, DEBUG, INFO, FileHandler, Formatter
from logging.handlers import TimedRotatingFileHandler
import datetime
import time
import os
import pymcprotocol
# 接続設定
TARGET_PLC_IP = '192.168.1.2'
PORT = 1026
# 先頭デバイスアドレス
HEAD_X = 'X0'
HEAD_Y = 'Y0'
def main():
# Mitsubishi Qシリーズに接続
pymc = pymcprotocol.Type3E()
connect_plc(pymc)
# フラグ初期化
flag_work_done = False
try:
while True:
try:
# 配列初期化
bitunits_Xs = []
bitunits_Ys = []
# PLCのXとYをそれぞれ0から16点読み出し
bitunits_Xs = pymc.batchread_bitunits(headdevice= HEAD_X, readsize=16)
bitunits_Ys = pymc.batchread_bitunits(headdevice= HEAD_Y, readsize=16)
# オルタネート回路
alternate_circuit(pymc, bitunits_Xs, bitunits_Ys)
except Exception as e:
logger.error(f'def main: {e}')
pymc.close()
time.sleep(5)
logger.info('再接続開始')
connect_plc(pymc)
except Exception as e:
logger.error(f'main: {e}')
finally:
pymc.close()
logger.info('終了')
def connect_plc(pymc):
try:
pymc.connect(TARGET_PLC_IP, PORT)
logger.info('PLC接続完了')
except Exception as e:
logger.error(f'def connect_plc: {e}')
def alternate_circuit(pymc, bitunits_Xs, bitunits_Ys):
global flag_work_done
if bitunits_Ys[0] == 0:
flag_work_done = False # ワンショットメモリ
# XがOFFなら1回だけX0をONにしてX0から16点書き込み
if bitunits_Ys[0] == 1 and bitunits_Xs[0] == 0 and not flag_work_done:
flag_work_done = True
logger.debug(f'Y0 ON検出: {bitunits_Ys}')
bitunits_Xs[0] = 1
pymc.batchwrite_bitunits(headdevice=HEAD_X, values=bitunits_Xs)
logger.debug(f'X0 ON書き込み: {bitunits_Xs}')
# X0がONなら1回だけX0をOFFにしてX0から16点書き込み
if bitunits_Ys[0] == 1 and bitunits_Xs[0] == 1 and not flag_work_done:
flag_work_done = True
logger.debug(f'Y0 ON検出: {bitunits_Ys}')
bitunits_Xs[0] = 0
pymc.batchwrite_bitunits(headdevice=HEAD_X, values=bitunits_Xs)
logger.debug(f'X0 OFF書き込み: {bitunits_Xs}')
def set_log_config(logger):
# Log出力先フォルダ
LOG_PATH = 'MCProtocol/Log/'
os.makedirs(LOG_PATH, exist_ok=True) #フォルダが無い場合は作成
# handler1 ターミナル用
handler1 = StreamHandler()
handler1.setLevel(DEBUG)
handler1.setFormatter(Formatter("%(asctime)s %(levelname)8s %(message)s"))
# handler2 ファイル出力用
filename = LOG_PATH + "MCProtocol.log"
handler2 = TimedRotatingFileHandler(filename = filename, encoding='utf-8', when='midnight', backupCount=30)
handler2.setLevel(INFO)
handler2.setFormatter(Formatter("%(asctime)s %(levelname)8s %(message)s"))
logger.info(f'set_log_config: Path = {filename}')
# loggerにハンドラ設定
logger.setLevel(DEBUG)
logger.addHandler(handler1)
logger.addHandler(handler2)
logger.propagate = False
return logger
#### Log Config ####
logger = getLogger(__name__)
logger = set_log_config(logger)
## End of Log config
#### Start ####
if __name__ == '__main__':
main()

LAN接続状態で実行するとPLCのY0がONするたびにX0の状態が切り替わります。

ポイントは、PLCとの通信に失敗した場合5秒後に自動的に再接続を試みるところです。このようなコードにしておかないと通信が切れた場合ソフトを再起動しないといけません。再起動が必要ではFA用途としては全然ダメですよね。

停止したいときはターミナルにカーソルを合わせCtrl+Cです。

Pythonコードの簡単な解説

PLCへの接続

ドキュメントにあるとおりまずはPLCのシリーズを指定してインスタンスを作ります。AとFXは非対応ですのでご注意を。

connectメソッドを実行するとPLCに接続します。引数に、上で設定したPLCのIPアドレスとMCプロトコルのTCPポート番号を渡します。

PLCのIO読み書き

読み出しは先頭アドレスと読み出し点数を指定します。

# PLCのXとYをそれぞれ0から16点読み出し
bitunits_Xs = pymc.batchread_bitunits(headdevice= HEAD_X, readsize=16)
bitunits_Ys = pymc.batchread_bitunits(headdevice= HEAD_Y, readsize=16)

書き込みは先頭アドレスを指定し、値にはリストを渡します。先頭アドレスから連続した領域にリストのサイズ分が書き込まれます。

pymc.batchwrite_bitunits(headdevice=HEAD_X, values=bitunits_Xs)

ログ出力

常時稼働することを想定してログ出力を組んでいます。0時に自動的に新しいログファイルに切り替わります。30ファイルたまると古いものから自動削除。logger.debug()はログ記録されずターミナルに表示するだけ。logger.info()以上はログに記録されます。

#ログ出力
from logging import getLogger, StreamHandler, DEBUG, INFO, FileHandler, Formatter
from logging.handlers import TimedRotatingFileHandler
def set_log_config(logger):
# Log出力先フォルダ
LOG_PATH = 'MCProtocol/Log/'
os.makedirs(LOG_PATH, exist_ok=True) #フォルダが無い場合は作成
# handler1 ターミナル用
handler1 = StreamHandler()
handler1.setLevel(DEBUG)
handler1.setFormatter(Formatter("%(asctime)s %(levelname)8s %(message)s"))
# handler2 ファイル出力用
filename = LOG_PATH + "MCProtocol.log"
handler2 = TimedRotatingFileHandler(filename = filename, encoding='utf-8', when='midnight', backupCount=30)
handler2.setLevel(INFO)
handler2.setFormatter(Formatter("%(asctime)s %(levelname)8s %(message)s"))
logger.info(f'set_log_config: Path = {filename}')
# loggerにハンドラ設定
logger.setLevel(DEBUG)
logger.addHandler(handler1)
logger.addHandler(handler2)
logger.propagate = False
return logger
#### Log Config ####
logger = getLogger(__name__)
logger = set_log_config(logger)
logger.debug("Test_debug")
logger.info("Test_info")
logger.error("Test_error")

最後に

いかがでしたか。これでPLCからトリガーをネットワーク経由でラズパイに送ることができますね。LiDAR×IoT×AIは響き的にはゲームチェンジャーの予感しかしない!RealSenseをラズパイOSのPythonから起動するための記事はこちら。

イーサネットユニットでの接続はこちら。

スポンサーリンク

フォロワー

Labels

Amazon (3) Apache (3) Apple (9) AppleSilicon (7) Bloggerカスタマイズ (12) EchoShow15 (1) IoT (25) Jetson (1) MySQL (1) PHP (3) Python (20) Web (3) アウトドア (11) アメリカ生活 (19) ガジェット (35) キャンプ (9) ディープラーニング (1) らずキャン△ (11) ラズパイ (24) 暗号資産 (5) 開発 (31) 旅行 (8)

QooQ