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

2021/05/04

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

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

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

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

ラズパイPythonと三菱PLCの連携【イーサネットユニット編】

この記事でわかること

ラズパイと三菱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を使用します。インストールはこちらを参照してください。

【IoT×キャンプ|Python開発環境VSCode編】意地でもラズパイとキャンプへ!らずキャン△プロジェクト第6回 RealSense L515をラズパイ4 Raspberry Pi OSのPythonから起動する最速手順&サンプルコード

通信設定

三菱PLCパラメータ設定

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

GX Works2でSLMP通信を試すための内蔵Ethernet設定 - Qiita

テストに使用する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

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

Welcome to pymcprotocol's documentation! - pymcprotocol '0.3.0' documentation

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は非対応ですのでご注意を。

Welcome to pymcprotocol's documentation! - pymcprotocol '0.3.0' documentation

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から起動するための記事はこちら。

RealSense L515をラズパイ4 Raspberry Pi OSのPythonから起動する最速手順&サンプルコード ラズパイのCPUクロックをPythonでPID制御しCPU温度コントロール

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

ラズパイPythonと三菱PLCの連携【イーサネットユニット編】

スポンサーリンク

フォロワー

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