概要
MELSEC_MCプロトコルを使用してPLC(Programmable Logic Controller)と通信するためのPythonコードを説明してます。
MCプロトコルとは??
MCプロトコル(Melsec Communication Protocol) は、三菱電機が開発したPLC(Programmable Logic Controller)と周辺機器を接続するための通信プロトコルです。三菱電機のPLC(キーエンス製やオムロン製も?)が搭載されている多くの機器で使用されており、データの送受信やプログラムの転送などに利用されます。
こちらで詳しく解説されています。
なぜラズパイとつなげるか?
ラズベリーパイを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: メイン関数
やっていることは以下の通りです。
- melsec.jsonファイルから必要な情報を読み込む
- PLCとの通信に必要なオブジェクトを作成する
- 読み込み/書き込みするデバイスのアドレスを指定する
- PLCと通信してデータを読み込む/書き込む
- 通信が完了したらオブジェクトをクローズする
設定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"フィールドには、書き込むデータが指定されています。