2022-07-30に更新

サーバやPC上のプログラムでModbus機器からデータを取得 (1)

サーバや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"に活躍いただきました。
image
ModTCPSimX のポート番号のデフォルトは5502です。僕のプログラム側も5502にしてあります。
※一般的なModbus/TCPは502です。
Holding Register 40001 に任意の数値を設定しておきます。
※ModTCPSimX では値の背景が黄色の場合は更新がコミットされていません。鉛筆アイコンでコミットするのを忘れずに
image
Termuxでプログラムを動かすと、最後のバイトに値が置かれたTXが返ります。
image

次回

せっかくなので、次回は取得したデータをデータベースに保存します。どうってことないんですけどね。

サーバやPC上のプログラムでModbus機器からデータを取得 (2)

ツイッターでシェア
みんなに共有、忘れないようにメモ

COOL MAGIC PRODUCTS

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント