サーバやPC上のプログラムでModbus機器からデータを取得、なんて場面があります。例えば、接点やセンサーがつながったリモートIOからデータを取得したい場合などです。
この記事では、Modbus/TCPでリモートIOなどからデータを取得するサンプル・プログラムを紹介します。
この記事で紹介するサンプルはModbus/TCPを前提としていますが、Modbus/RTUでも原理は同じです
。Socketがシリアル・インタフェースに変わり、やりとりするデータのフォーマットがTCPからRTUに変わるだけです。
※RTUの場合、送受するデータにCRCを組み込む必要があり、少々やっかいですけどね。
リモートIOのメーカーのサイトでは、PLCがリモートIOからデータを取得するように書かれていたりします。リモートIOからのデータを使って機械を制御する場合はそんな構成なのかもしれませんが、そのデータをサーバやPCで処理したい場合には、リモートIOのデータをサーバやPCが直接取得するのが合理的です。リモートIOとPLCは必ずしもセットではないのです。
「そんなことは、ここであらためて書かなくてもあたりまえだろ」と思われる方も多いでしょうが、固定観念と先入観が、迷わず(本来は不要であるはずの)PLCを登場させてしまいます。
Modbus/TCP
Modbusについては、エム・システム技研さまのこのドキュメントがおすすめです。
Modbus プロトコル概説書
サンプル・プログラム
今回はPerlを使いました。Perlである必要性はありませんが、Perlではだめだという理由もありません。なぜPerlを選択したか?僕のセルフォンでPerlが使えたからです。
サンプルでは、ファンクション0x03: Read Holding Register を使っています。
データをコンソールに16進表記で表示するために、公開されていたコレを遠慮なく拝借いたしました。
(Perl) バイナリ変数の16進ダンプ表示
#!/usr/bin/perl
use IO::Socket;
{
my $Server = 'localhost';
my $Port = 5502;
my $socket = new IO::Socket::INET(
PeerAddr=>$Server,
PeerPort=>$Port,
Proto=>'tcp');
die "IO::Socket : $!" unless $socket;
print "connected to the server\n";
my $TransactionID = 0x0200;
my $ProtocolID = 0x0000;
my $Length = 0x0006;
my $UnitID = 0x01;
my $FunctionCode = 0x03;
my $StartAddress = 0x0000;
my $CountRegister = 0x0001;
my $req = pack("n3C2n2",
$TransactionID, $ProtocolID, $Length,
$UnitID, $FunctionCode,
$StartAddress, $CountRegister);
my $size = $socket->send($req);
print "sent data:\n";
BinaryDump($req);
shutdown($socket, 1);
my $response = "";
$socket->recv($response, 1024);
print "received response:\n";
BinaryDump($response);
$socket->close();
}
# 利用させていただきました
# https://netlog.jpn.org/r271-635/2018/11/perl-bynary-dumper.html
sub BinaryDump {
my ($buf) = @_;
my $len;
my $i;
$len = length($buf);
printf ("length = %d\n", $len);
for ($i = 0; $i < $len; $i++) {
printf("%02X ", ord(substr($buf, $i, 1)));
# 16文字目で画面上の改行
if (($i % 16) == 15) {
print "\n";
}
}
if (($i % 16) != 15) {
print "\n";
}
}
テスト
テストにも僕のセルフォンを使いました。
Modbusスレーブとして、Androidアプリケーション"ModTCPSimX"に活躍いただきました。
ModTCPSimX のポート番号のデフォルトは5502です。僕のプログラム側も5502にしてあります。
※一般的なModbus/TCPは502です。
Holding Register 40001 に任意の数値を設定しておきます。
※ModTCPSimX では値の背景が黄色の場合は更新がコミットされていません。鉛筆アイコンでコミットするのを忘れずに
Termuxでプログラムを動かすと、最後のバイトに値が置かれたTXが返ります。
次回
せっかくなので、次回は取得したデータをデータベースに保存します。次回は今回以上にどうってことない記事です。