LoginSignup
1
2

More than 1 year has passed since last update.

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

Posted at

サーバやPC上のプログラムでModbus機器からデータを取得、その第二回です。
第一回では、Android上のModbusシミュレータからデータを取得するための、Perlスクリプトのサンプルを紹介しました。Perlでは問題があるという方は、他の言語で記述しても原理は変わらないはずなので、他の言語で実装してみてください。

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

今回は、取得したデータをデータベースに保存します。

データベース

以前の記事で使用した、MySQLとテーブル"from_plc"
を使います。

PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)

C:\Users\hoge\Downloads>mysql -u hoge -p
Enter password: *******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 60
Server version: 8.0.29 MySQL Community Server - GPL

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use hoge
Database changed
mysql> desc from_plc;
+-------------+-----------+------+-----+-------------------+-------------------+
| Field       | Type      | Null | Key | Default           | Extra             |
+-------------+-----------+------+-----+-------------------+-------------------+
| body        | json      | YES  |     | NULL              |                   |
| time_insert | timestamp | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+-------------+-----------+------+-----+-------------------+-------------------+
2 rows in set (0.02 sec)

mysql>

Perlスクリプト

前回は、Perlスクリプトを僕のセルフォンTermux上で動作させましたが、今回はPCのWindows上で開発し動作させます。何故?

僕のセルフォンのTurmuxのcpanになんらかの問題があるらしく、DBIのビルドができないんです。いろいろトライしたけど僕には解決は無理と判断し、Turmax上のPerlスクリプトを放棄しました。
DBIは必須ではないですが、データベースをアクセスするPerlなら、DBIを使うのが一般的ですよね。
DBIのない環境で、これ以上Perlを書く気にはなれませんよね。まあ、僕のせいなんですけどね。

「サーバやPC上のプログラムでModbus機器からデータを取得 (1)」で作成したスクリプトに手を加えました。これで、Modbus機器からModbus/TCPにより取得したデータをデータベースに保存します。

#!/usr/bin/perl

use IO::Socket;
use DBI;
use Time::HiRes "gettimeofday";
use utf8;
use Encode;
use JSON;

{
	my $Server = 'localhost';
	my $Port = 502;

	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 $old = -1;

	while (1) {
		my $socket = new IO::Socket::INET( 
			PeerAddr=>$Server,
			PeerPort=>$Port,
			Proto=>'tcp');
		die "IO::Socket : $!" unless $socket;

		my $size = $socket->send($req);
		shutdown($socket, 1);

		my $response = "";
		$socket->recv($response, 1024);

		my @data = unpack("n3C3n", $response);
		my $Register = $data[6];
		
		if ($old != $Register) {
			print "sent data:\n";
			BinaryDump($req);
			print "received response:\n";
			BinaryDump($response);

			my $now = getCurrentTimeStr();
			my $output_data = {
				'ts' => $now,
				'value' => $Register
			};
			my $json_text = decode('utf-8', encode_json( $output_data )); 
			print $json_text . "\n";
			my @values = ($json_text);
			
			$user = 'hoge';
			$passwd = 'hoge001';
			$db = DBI->connect('DBI:mysql:hoge:localhost', $user, $passwd);
			$sth = $db->prepare("INSERT INTO from_plc (body) values (?)");
			$sth->execute(@values);
			$sth->finish;
			$db->disconnect;
		}
		
		$old = $Register;

		$socket->close();
		sleep 1;
	}

}

# 利用させていただきました
# 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";
	}
}

# 利用させていただきました
# https://akrad.hatenablog.com/entry/2018/10/20/234528
sub getCurrentTimeStr {
	my ($epochSec, $microSec) = gettimeofday();
	my ($sec, $min, $hour, $day, $mon, $year) = localtime($epochSec);
	$year += 1900;
	$mon++;
	return "$year" 
		. '-' . sprintf("%02d", $mon) 
		. '-' . sprintf("%02d", $day) 
		. 'T' . sprintf("%02d", $hour) 
		. ':' . sprintf("%02d", $min)
		. ':' . sprintf("%02d", $sec)
		. '.' . "$microSec";
}

Perlの自由さと気楽さが良いですね。

テスト

Windows上ということで、再び MOD-RSsim に登場いただきます。MOD-RSsimを起動しておきます。
59603780d55c186e1a70e1b112539c5562d52790afb85.png
Perlスクリプトを実行します。

C:\Users\hoge\Downloads>perl sample.pl
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 00
{"ts":"2022-07-30T15:05:59.916895","value":0}

Holding Register 40001 の0を取得しデータベースに保存されます。
MOD-RSsim で 40001 に任意の数値を入力します。
117891e3a7d9cfb3ad2aee7bc5db0e2662d5287c3564a.png
40001 のデータが変わる都度、データベースに値が保存されます。

C:\Users\hoge\Downloads>perl sample.pl
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 00
{"ts":"2022-07-30T15:05:59.916895","value":0}
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 01
{"ts":"2022-07-30T15:06:04.10446","value":1}
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 02
{"ts":"2022-07-30T15:06:07.41537","value":2}
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 03
{"value":3,"ts":"2022-07-30T15:06:10.72655"}
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 04
{"value":4,"ts":"2022-07-30T15:06:12.150828"}
sent data:
length = 12
02 00 00 00 00 06 01 03 00 00 00 01
received response:
length = 11
02 00 00 00 00 05 01 03 02 00 05
{"ts":"2022-07-30T15:06:14.213053","value":5}
Terminating on signal SIGINT(2)

C:\Users\hoge\Downloads>

データベースのテーブル"from_plc"を確認してみます。

C:\Users\hoge\Downloads>mysql -u hoge -p
Enter password: *******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 75
Server version: 8.0.29 MySQL Community Server - GPL

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use hoge
Database changed
mysql> select * from from_plc;
+----------------------------------------------------+---------------------+
| body                                               | time_insert         |
+----------------------------------------------------+---------------------+
| {"ts": "2022-07-30T15:05:59.916895", "value": 0}   | 2022-07-30 15:05:59 |
| {"ts": "2022-07-30T15:06:04.10446", "value": 1}    | 2022-07-30 15:06:04 |
| {"ts": "2022-07-30T15:06:07.41537", "value": 2}    | 2022-07-30 15:06:07 |
| {"ts": "2022-07-30T15:06:10.72655", "value": 3}    | 2022-07-30 15:06:10 |
| {"ts": "2022-07-30T15:06:12.150828", "value": 4}   | 2022-07-30 15:06:12 |
| {"ts": "2022-07-30T15:06:14.213053", "value": 5}   | 2022-07-30 15:06:14 |
+----------------------------------------------------+---------------------+
6 rows in set (0.00 sec)

mysql>

まとめ

このように、Modbus機器からのデータ取得は、専用の機器や商用のソフトウェアがなくても可能です。今回はModbus/TCPでしたが、RTUでも原理は同じです。
データ取得だけではなく、Modbus機器にデータを書き込むことも難しくはありません。Modbusの仕様を理解すれば、データの取得も書き込みも簡単です。
Modbusに限らず、各メーカのPLCの仕様もほとんどがオープンになっているので、それぞれの仕様を理解しさえすれば、Modbusと同じように自分で書いたプログラムでの読み書きが可能です。
様々な種類のPLCやリモートIOなどをアクセスする場合には、ゲートウェイなどを利用したほうが良いかもしれません。一方で、限られた種類のPLCやリモートIOをアクセスする場合は、自分で書いたプログラムを使用するという選択もあると思います。

「作らない」のではなく「作れない」んです。なぜ「作れない」のか?「作らない」からです。

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2