Perlで三菱シーケンサーとSocket通信

何するの?[2006-02-06]

ここでは一体何をしようとしているのか?
工業用の制御システムとして一般に使用されているシーケンサー(PLC:プログラミングコントローラーの略らしいが,「L」はどこにあるんだ? Programable Logic Controler が正解。 まぁROMベースの組み込みコンピュータだな)には通信機能がオプションで用意されていてネットワークでデータ・パラメータの読み書きその他の制御が可能だ。

最近のITブーム(?)で,データを集めるのが流行っているのか,この機能を利用したデータ収集・解析システムが結構市販されているし,実際に見積もりを取ったこともある...が,どうも市販品だと「データ収集点を増やしたい」とか「こんな解析機能を付けたい」といった要望に対し融通が利かないしベースとなる金額も高すぎる! 「じゃぁ試しに自分で作ってみるか」と始めたら,趣味のプログラマーでもできちゃったりするんだなこれが。

ようするに「道楽」じゃなくて「業務館」に近い(あくまでも「近い」だけでそのものじゃないのが微妙)のかもしれないが,業界ではNo.1のシェアを誇る(?)三菱製シーケンサーQシリーズでの設定・プログラミング方法を解説していこう(オムロン製シーケンサーでも同様のことができる...が,どうもそちらは設定方法のクセが強くてめんどくさいのでヤダ。まぁ機会があればオムロンバージョンも出すかも)

シーケンサー側初期設定[2006-02-06]

え~と,シーケンサー本体のセットアップ云々はこの際割愛し本題からスタートする(っていうか,実際のところよく知らない)。

まずはネットワークユニットQJ71E71の初期設定をおこなおう。シーケンサー設計・保守ツール「GX Developer」の「パラメータ」-「ネットワークパラメータ」-「MELSECNET/Ethernet」を開いたら,「動作設定」と「オープン設定」を編集しよう。

PLC設定画面1
PLC設定画面1

「動作設定」の編集

ここでは「交信データコード設定」と「IPアドレス」を設定する。
「交信データコード設定」は「バイナリコード交信」がデータ通信量が少なくなるためおすすめだ。以降は,この「バイナリコード交信」で話を進めていく。「IPアドレス」はそのまんま。シーケンサーに割り当てられたIPアドレスを入力しよう。

PLC設定画面2
PLC設定画面2

「オープン設定」の編集

ここで設定するのは,外部との交信手順だ。画像は,TCP/IPプロトコルでポート番号500(16進)を開放する場合だ。交信相手を制限したい場合はIPアドレスで設定しよう。

PLC設定画面3
PLC設定画面3

通信コマンド

シーケンサー側の通信方法には機種により「QnA互換3E/3C/4C/2Cフレーム」とか「A互換1Eフレーム」があるが,Ethernet経由なので「3Eフレーム」で話を進めていく。一通りのシーケンサー制御が通信で可能だが,ここではデータレジスタからのデータ読み込みに絞り込んでみよう(他の内容は以下を応用すればできる...はず)。そのコマンドは以下の一文だ。

500000FFFF03000C001000010400002C0100A80300

なんじゃこりゃ(^^; さっぱりわからん...では内容毎に分解して説明するか。おっと,教科書として「Q対応MELSECコミュニケーションプロトコルリファレンスマニュアル」を手元に用意するように。これがないとさっぱりわからないぞ。上のコマンドはマニュアルP.3-7に書かれている手順そのものだ。

5000 00 FF FF03 00 0C00 1000 0104 0000 2C0100 A8 0300
コマンド内容備考
5000 サブヘッダ 固定値だ(マニュアルP.3-4参照)
00 ネットワーク番号 EthernetユニットQJ71E71が装着されているPLC本体と通信するぞ(マニュアルP.3-9参照)
FF PC番号 上のネットワーク番号により設定(マニュアルP.3-9参照)
FF03 >要求先ユニットI/O番号 単一CPU構成のPLCならこの値(マニュアルP.3-9参照 マルチCPUシステムならP.3-35参照)
00 要求先ユニット局番号 同上
0C00 要求データ長 ここから後のコマンドの長さをバイト数で指定だ(0000~FFFF)。え~と,次の「1000」以降は全部で24文字あるはずだ。最初に書いたようにバイナリコード交信なので2文字で1バイトになるぞ。よって全部で12バイト。これを16進数で書くと000C。下位から上位に向かって2文字ずつ記述するので0C00だ。
1000 CPU監視タイマ レスポンスまでの待ち時間設定(マニュアルP.3-10参照)>
0104 コマンド データレジスタをワード単位で一括読み出しするコマンド:0401だ(マニュアルP.3-48参照)。これも下位から上位に向かって2文字ずつ記述するぞ
0000 サブコマンド 同上(マニュアルP.3-57もチェックだ)
2C0100 先頭デバイス (マニュアルP.3-57参照)これはD300の例だ。300は16進表記だと00012Cなので,2文字ずつ下位から書くと2C0100だ。
A8 デバイスコード ここではデータレジスタを指定(マニュアルP.3-60参照)
0300 デバイス点数 16進数で指定するぞ。ここでは3点(=0003)だ。2文字ずつ下位から書こう。

要するに,データレジスタD300~D302までの内容を読み出すコマンドだったわけだ。

PerlのSocket通信プログラム[2006-02-06]

通信コマンドも決まったことなのでいよいよプログラムを作って通信してみよう。特別なモジュールは必要なくPerlの標準機能でできるはずだ。以下のソースは「Perl socket通信」みたいなキーワードをGoogleで検索して試行錯誤の上作ったものなので,とりあえず動いているとはいえ本当に問題ないかどうかは怪しいところだと断っておこう(^^;

use Socket					#Socketモジュールを使用

$ipadrs="192.168.1.1";		#EthernetユニットのIPアドレス
$port=0x500;				#上で設定したポート番号

$cmd="500000FFFF03000C001000010400002C0100A80300";	#コマンド
socket(SOCKET, PF_INET, SOCK_STREAM, 0) || die;		#socket通信をTCP/IPプロトコルでするよ
$inet=inet_aton($ipadrs);							#IPアドレス変換
$socket_add = pack_sockaddr_in($port,$inet);
connect(SOCKET, $socket_add) || die;				#接続

$res="";							#レスポンスを入れる変数の初期化
autoflush SOCKET (1);				#通信初期化
$cmd=pack("H*", $cmd);				#バイナリに変換
send(SOCKET,$cmd,0);				#送信
recv(SOCKET,$res,500,0) || die;		#受信	500(バイト数)は実際に返ってくるサイズより大きければ適当で可
close(SOCKET);
$res=unpack("H*",$res);				#asciiに変換
print "RES=$res";

えっこれだけなの?感じだ(^^; 実際はエラー処理とかデータ処理とかのルーチンが必要だが,データを取ってくるだけならホントこれだけだ。 さぁ,このプログラム(仮にファイル名を「melsec_socket.pl」としよう)を実行だ。

C:¥>perl melsec_socket.pl
RES=D00000FFFF03000800000001000A006400
C:¥>

できたできた...ところで,この例で帰ってきた値は一体?

応答データ[2006-02-06]

という訳で帰ってきた値も同様に内容毎に分解して説明しよう。

D000 00 FF& FF03 00 0800 0000 01000A006400
応答データ内容備考
D000 サブヘッダ 固定値だ(マニュアルP.3-4参照)
00 ネットワーク番号 「通信コマンド」で設定した値だ
FF PC番号 「通信コマンド」で設定した値だ
FF03 要求先ユニットI/O番号 「通信コマンド」で設定した値だ
00 要求先ユニット局番号 「通信コマンド」で設定した値だ
0800 応答データ長 ここから後の応答データの長さをバイト数で指定だ(0000~FFFF)。え~と,次の「0000」以降は全部で16文字あるはずだ。最初に書いたようにバイナリコード交信なので2文字で1バイトになるぞ。よって全部で8バイト。これを16進数で書くと0008。下位から上位に向かって2文字ずつ記述するので0800だ。
0000 終了コード 「0000」が正常終了時。その他の場合は異常終了で,詳細は「Ethernetインターフェースユニットユーザーマニュアル(基本編)」を参照だ。
01000A006400 データ この部分が必要なデータだ。D300-302の3点を読み出すコマンドだったのを思い出そう。前から4文字ずつがそれぞれに該当するぞ。この部分もそれぞれ下位2文字+上位2文字になっていて,16進数である点に注意が必要だ。
D300の値=0x0001=1(帰ってきた値は0100)
D301の値=0x000A=10(帰ってきた値は0A00)
D302の値=0x0064=100(帰ってきた値は6400)

あとは,必要なデータ部分を抜き出して,解析するなり,データベースに入れて保存するなり,使い道はなんでもありだ。