PythonでPLCのレジスタアクセス
from https://qiita.com/senrust/items/d026575f583e75f9ce30
三菱電機株式会社製のPLCからデータを読み出す, 書き込む際には,
MCプロトコルという通信プロトコルを通信フォーマットでデータのやり取りを行う必要があります.
すでにQiitaにはMCプロトコルの説明や実装を紹介する記事を先駆者様たちが投稿されていましたが,
この度PythonのMCプロトコルライブラリpymcprotocolを作成いたしましたので紹介いたします.
PyPI: https://pypi.org/project/pymcprotocol/
APIリファレンス: https://pymcprotocol.netlify.app/pymcprotocol.html
github: https://github.com/yohei250r/pymcprotocol
対応PLC
Qシリーズ, Lシリーズ, QnA, iQ-R, iQ-Lシリーズに対応.
iQ-RシリーズのみQシリーズと異なるので, 明確に指定する必要があります。
動作確認はQシリーズでのみ行っています。
(Aシリーズは3Eフレームには対応していないため, 現状は不可です.)
対応通信フォーマット
MCプロトコル3Eタイプ(バイナリ形式, ASCII形式)を実装しています.
4Eタイプは実装していますが未テストです。
対応コマンド
- 一括読み込み(ワード単位, ビット単位)
- 一括書き込み(ワード単位, ビット単位)
- ランダム読み込み
- ランダム書き込み(ワード単位, ビット単位)
- リモートRUN, リモートSTOP, リモートRESET, リモートPAUSE
に対応しています.
その他のコマンドですが, 実装が簡単で使用されそうなコマンドを優先して実装する予定です.
要望がありましたら教えて下さい。
インストール
pip install pymcprotocol
PLCの設定
PLCのネットワーク設定でIPアドレス設定, MCプロトコル用のポート開放と, イーサネット通信形式の選択をしてください.
PythonからPLCへ接続
import pymcprotocol
#Qシリーズがデフォルトです
pymc3e = pymcprotocol.Type3E()
#Lシリーズの場合はインスタンス化にplctypeを与えてください
pymc3e = pymcprotocol.Type3E(plctype="L")
#iQ-Rシリーズの場合はインスタンス化にplctypeを与えてください
pymc3e = pymcprotocol.Type3E(plctype="iQ-R")
#イーサネットの接続形式をASCIIにした場合はここで"ascii"を与えてください
#もしMCプロトコルのアクセス経路をデフォルトから変更する場合もこのメソッドから可能です.
pymc3e.setaccessopt(commtype="ascii")
#PLCに設定したIPアドレス, MCプロトコル用ポートに接続
pymc3e.connect("192.168.1.2", 1025)
コマンド発行
#D100からD110まで読み込み
wordunits_values = pymc3e.batchread_wordunits(headdevice="D100", readsize=10)
#X10からX20まで読み込み(ビットデバイスアクセス)
bitunits_values = pymc3e.batchread_bitunits(headdevice="X10", readsize=10)
#D10からD15まで与えた数値を書き込み
pymc3e.batchread_wordunits(headdevice="D10", values=[0, 10, 20, 30, 40])
#Y10からY15まで与えた数値を書き込み(ビットデバイスアクセス)
pymc3e.batchwrite_bitunits(headdevice="Y10", values=[0, 1, 0, 1, 0])
#"D1000", "D2000"をワード単位で読み込み
#"D3000"をダブルワードで読み込み. (D3001が上位16ビットD3000が下位16ビット)
word_values, dword_values = pymc3e.randomread(word_devices=["D1000", "D2000"], dword_devices=["D3000"])
#"D1000"に1000 "D2000"に2000を書き込み
#"D3000"に655362を書き込み. (D3001が上位16ビットD3000が下位16ビット)
pymc3e.randomwrite(word_devices=["D1000", "D1002"], word_values=[1000, 2000],
dword_devices=["D1004"], dword_values=[655362])
#X0とX10にそれぞれ1と0を書き込み
pymc3e.randomwrite_bitunits(bit_devices=["X0", "X10"], values=[1, 0])
from https://qiita.com/satosisotas/items/78814da5756de76f1a77
SLMPでPLCレジスタのリード/ライトを試します。
バイナリコードでのビットアクセスについてはこちらの記事に書きました。
接続
まずPLCとコネクションを張ります。
HOST = '192.168.1.1'
PORT = 1026
BUFSIZE = 4096
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
sock.settimeout(3)
基本パラメータ
SLMPの要求伝文に設定する基本パラメータです。
アクセス先設定
ホストPCと直接Ethernet接続されているPLCのCPUユニットレジスタを読みます。
この場合、SLMPの規定によりPLCアクセス先の設定は下記のようにします。
項目 | 値 |
---|---|
要求先ネットワーク番号 | 00h |
要求先局番 | FFh |
要求先ユニットI/O番号 | 3FFh |
要求先マルチドロップ局番 | 0 |
監視タイマ
監視タイマは250ms単位で設定します。今回は 4 (1秒) に設定します。
デバイスコード
今回はデータレジスタを使用します。ワードアクセスです。
ASCIIコードででは "D*"、バイナリコードでは A8h となります。
リード
要求伝文を組み立ててフレームを送信します。
Readコマンド(0401h)のワードアクセスとします。
バイナリコード時には、サブヘッダ以外はリトルエンディアンで値を格納する必要があります。
サブヘッダだけは何故かビッグエンディアンなので気を付けましょう。
D0001 ~ D0003 の3つのレジスタの値を読み込みます。
# '5000 00 FF 03FF 00 0018 0020 0401 0000 D* 000001 0003'
data = [
0x50,0x00, # サブヘッダ
0x00, # 要求先ネットワーク番号
0xFF, # 要求先局番
0xFF,0x03, # 要求先ユニットI/O番号
0x00, # 要求先マルチドロップ局番
0x0C,0x00, # 要求データ長 (後で設定)
0x20,0x00, # 監視タイマ
0x01,0x04, # コマンド
0x00,0x00, # サブコマンド
0x01,0x00,0x00, # 先頭デバイス番号
0xA8, # デバイスコード
0x03,0x00 # デバイス点数
]
# 要求データ長を設定
data[7] = len(data[9:]) & 0xFF
data[8] = (len(data[9:]) >> 16) & 0xFF
# 要求送信
sock.send(bytes(data))
# 応答受信
res = sock.recv(BUFSIZE)
print (*[format(i,'02X') for i in res])
受信データは下記のようになります。
現在のレジスタ値はすべて 0 でした。
D0 00 00 FF FF 03 00 08 00 00 00 00 00 00 00 00 00
ライト
要求伝文を組み立ててフレームを送信します。
Writeコマンド(1401h)のワードアクセスとします。
D0000 ~ D0004 の5つのレジスタに値を書き込みます。
# '5000 00 FF 03FF 00 0018 0020 0401 0000 D* 000000 0005 1122 3344 5566 7788 99AA'
data = [
0x50,0x00, # サブヘッダ
0x00, # 要求先ネットワーク番号
0xFF, # 要求先局番
0xFF,0x03, # 要求先ユニットI/O番号
0x00, #
0x00,0x00, # 要求データ長 (後で設定)
0x04,0x00, # 監視タイマ
0x01,0x14, # コマンド (1401H)
0x00,0x00, # サブコマンド
0x00,0x00,0x00, # 先頭デバイス番号
0xA8, # デバイスコード
0x05,0x00, # デバイス点数
0x11,0x22, # D0000
0x33,0x44, # D0001
0x55,0x66, # D0002
0x77,0x88, # D0003
0x99,0xAA, # D0004
]
# 要求データ長を設定
data[7] = len(data[9:]) & 0xFF
data[8] = (len(data[9:]) >> 16) & 0xFF
# 要求送信
sock.send(bytes(data))
# 応答受信
res = sock.recv(BUFSIZE)
ライト完了後、先ほどのReadコマンドを再度実行すると、実行結果は下記のようになります。
D0 00 00 FF FF 03 00 08 00 00 00 33 44 55 66 77 88
正しく書込みと読み出しができていることが分かります。
コネクション切断
sock.close()
留言
張貼留言