この記事ではラズパイと三菱PLCをネットワーク経由で連携させる方法を紹介します。FAでもIoTニーズが高まってきていますよね。といってもPLCと連動してくれないと出来ることは限られる。でもIO配線はめんどくさい。というわけでラズパイをネットワーク経由でPLCと連携させる方法を紹介します。トリガーをPLCからラズパイへ送り、画像処理させて、結果をPLCへ返すような動きに使えます。記事ではLAN直結でテストしていますが、工場に生産工程用のWiFiがあれば電源配線だけで済むのでかなり使えそうですね。
イーサネットユニットでの接続はこちら。
CONTENTS [非表示]
この記事でわかること
ラズパイと三菱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 |
---|
pymcprotocolのインストール
PythonでMCプロトコルを扱うためのライブラリをインストールします。LXTerminalをひらいて次のコマンドを実行してください。
pip install pymcprotocol
インストールは以上です。ドキュメントはこちら。
LXTerminalでPythonを立ち上げてimportすることで動作確認できます。
python3 import pymcprotocol
import後にエラーがなければインストールに問題はありません。
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から起動するための記事はこちら。
イーサネットユニットでの接続はこちら。
0 件のコメント:
コメントを投稿