5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ラズパイ・MCプロトコルで工場のPLCをIoT化させる

Last updated at Posted at 2023-04-29

概要

MELSEC_MCプロトコルを使用してPLC(Programmable Logic Controller)と通信するためのPythonコードを説明してます。

MCプロトコルとは??

MCプロトコル(Melsec Communication Protocol) は、三菱電機が開発したPLC(Programmable Logic Controller)と周辺機器を接続するための通信プロトコルです。三菱電機のPLC(キーエンス製やオムロン製も?)が搭載されている多くの機器で使用されており、データの送受信やプログラムの転送などに利用されます。

こちらで詳しく解説されています。

MCプロトコルのチートシート - Qiita

なぜラズパイとつなげるか?

ラズベリーパイをMCプロトコルに対応したインタフェースボードを介してPLCに接続することで、ラズベリーパイからPLCに信号を送信し、PLCに接続された周辺機器を制御することができます。
また、AWSなどのクラウド上に設置されたアプリケーションからラズベリーパイを経由してPLCにデータを送信することもできます。

つまり、

  • 遠隔でPLCのデータを収集する
  • 遠隔でPLCにデータ転送を行う
  • 遠隔でPLCのラダー回路をON/OFFして、システム制御を行う

といったことができます。

動作環境

Pythonのバージョン3.6以降

事前準備

PLCを操作するのでPLC、ラズパイ、2つをつなげる有線LANケーブル。2つをつなげます。

また、PLCの方にIPアドレス等の設定が必要です。詳しくはこちらで解説されています。

PLCとRaspberry Piはとっても相性がいい。(通信の行い方) - Qiita

実行手順

melsec.jsonファイルを作成し、必要な情報を定義します。
Pythonファイルを実行します。引数に"r"または"w"を指定してください。

ソースコード

こちらにもおいています。
https://github.com/HirotoSakaki/plc-communication-python

# MELSEC_MCプロトコルを使用してPLC(Programmable Logic Controller)と通信するためのPythonコード

import json
import socket
import sys

# PLCとのソケット通信を行うためのクラス
class MySocket:
    def __init__(self, sock=None):
        print("init")
        if sock is None:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        else:
            self.sock = sock

    def connect(self, host, port):
        print("connect")
        try:
            self.sock.connect((host, port))
            return 0
        except socket.error:
            print("コネクトエラーです")
            return -1

    def close(self):
        print("CLOSE")

        try:
            self.sock.close()
            return 0
        except socket.error:
            print("クロ-ズエラーです")
            return -1

    def mysend(self, msg):
        print("mysend")
        try:
            self.sock.send(msg.encode("utf-8"))
        except Exception:
            return -1

    def myreceive(self):
        print("myreceive")
        data = []
        try:
            data = self.sock.recv(1024)
            return data
        except Exception:
            return -1

# 送信するコマンドを作成するためのクラス
class MakeCommand:
    def __init__(self) -> None:
        json_open = open("melsec.json", "r")
        self.loading = json.load(json_open)

    def socket(self):
        # PLCのIPアドレスとポート番号を取得
        return [val for val in self.loading["socket_settings"].values()]

    def common(self):
        # 読みと書きで共通コマンド部分
        return [val for val in self.loading["common"].values()]

    def read(self):
        # 共通コマンドstrと読み込みコマンドstrを結合させ、一個のコマンドを作成
        cmd = self.common() + [val for val in self.loading["read"].values()]
        print(cmd)
        return "".join(cmd)

    def write(self):
        # 共通コマンドstrと書き込みコマンドstrを結合させ、一個のコマンドを作成
        cmd = self.common() + [val for val in self.loading["write"].values()]
        print(cmd)
        return "".join(cmd)

def main(args):
    mysocket = MySocket()
    mk_cmd = MakeCommand()
    socket = mk_cmd.socket()

    if mysocket.connect(socket[0], int(socket[1])) == 0:  # PLCと通信成功したら実行
        if args == "r":  # 一括読み込みの場合
            print("read")
            read_cmd = mk_cmd.read()
            mysocket.mysend(read_cmd)  # 読み込みコマンド送信

        elif args == "w":  # 一括書き込みの場合
            print("write")
            write_cmd = mk_cmd.write()
            mysocket.mysend(write_cmd)  # 書き込みコマンド送信

        ret = mysocket.myreceive().decode("utf-8")  # 応答伝文
        new_ret = [
            ret[0:4],  # サブヘッダ
            ret[4:6],  # ネットワーク番号
            ret[6:8],  # PC番号
            ret[8:12],  # 要求先ユニットI/O番号
            ret[12:14],  # 要求先ユニット局番号
            ret[14:18],  # 応答データ長
            ret[18:22],  # 終了コード
            ret[22:],  # 読み込みのときは読み出しデータ(のはずだが・・・)
        ]
        print(new_ret)

    else:
        print("Conection Error")
        sys.exit()

    mysocket.close()

if __name__ == "__main__":
    if len(sys.argv) > 1:
        main(sys.argv[1])
    else:
        print("Please specify mode (r or w)")

やっていることはだいたいコードにコメントしています。が、大まかのまとめると以下のとおりです。

MySocket: PLCとのソケット通信を行うためのクラス
MakeCommand: 送信するコマンドを作成するためのクラス
main: メイン関数

やっていることは以下の通りです。

  1. melsec.jsonファイルから必要な情報を読み込む
  2. PLCとの通信に必要なオブジェクトを作成する
  3. 読み込み/書き込みするデバイスのアドレスを指定する
  4. PLCと通信してデータを読み込む/書き込む
  5. 通信が完了したらオブジェクトをクローズする

設定JSONファイル

{
    "socket_settings":{
    "IP":"192.168.2.39",
    "port":"5015"
    },
    "common":{
        "subheader":"5000",
        "net":"00",
        "pc":"FF",
        "unitio":"03FF",
        "unitno":"00"
    },
    "read":{
        "nlen":"0018",
        "cputimer":"0010",
        "command":"0401",
        "subcommand":"0000",
        "device":"D*000000",
        "number":"0001"
    },
    "write":{
        "nlen":"001C",
        "cputimer":"0010",
        "command":"1401",
        "subcommand":"0000",
        "device":"D*000000",
        "number":"0001",
        "devicedata":"00FF"
    }
}

このJSONファイルは、Mitsubishi Electric社のプログラマブルロジックコントローラー (PLC) で使用されるMELSEC通信プロトコルに関連する情報を定義しています。

"socket_settings"オブジェクトには、PLCに接続するためのIPアドレスとポート番号が含まれています。

"common"オブジェクトには、通信に必要ないくつかの情報が含まれています。たとえば、"net"と"pc"は、PLCの種類とステーション番号を識別するためのものであり、"unitio"と"unitno"は、通信に使用されるユニットとそのユニットの番号を指定します。

"read"オブジェクトには、PLCからデータを読み取るために必要な情報が含まれています。たとえば、"device"フィールドには、データを読み取るために使用されるアドレスが指定されています。

"write"オブジェクトには、PLCにデータを書き込むために必要な情報が含まれています。"devicedata"フィールドには、書き込むデータが指定されています。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?