tag:crieit.net,2005:https://crieit.net/users/COOLMAGICPRODU1/feed COOL MAGIC PRODUCTSの投稿 - Crieit CrieitでユーザーCOOL MAGIC PRODUCTSによる最近の投稿 2022-08-15T06:32:55+09:00 https://crieit.net/users/COOLMAGICPRODU1/feed tag:crieit.net,2005:PublicArticle/18280 2022-08-14T17:08:59+09:00 2022-08-15T06:32:55+09:00 https://crieit.net/posts/OpenBlocks-IoT-WEB OpenBlocks IoTをWEBサーバにする <p>OpenBlocks IoT をWEBサーバにする方法です。<br /> OpenBlocks IoT をセンサー類のゲートウェイとして使用し、さらにWEBサーバとすることができれば、OpenBlocks IoT 1台でセンサーからのデータを入力しモニターする等のシステムを稼働させることも可能となります。</p> <p><a href="https://crieit.now.sh/upload_images/84240a486f4bcb757b594d8970038ed062f84fdae123d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/84240a486f4bcb757b594d8970038ed062f84fdae123d.png?mw=700" alt="image" /></a></p> <p>FW4搭載の OpenBlocks IoT を対象とした解説です。FW4非搭載の OpenBlocks IoT については、汎用OSが裸で搭載されているため、それぞれのOSに応じた手段でWEBページの実装と配信が可能であり、それは難しいことではないためわざわざ解説する必要はないでしょう。</p> <blockquote> <p>FW4とは?という方はこのサイトを参照してください。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.plathome.co.jp/product/fw/fw4-application/">IoTゲートウェイソフトウェア FW4</a></p> </blockquote> <p>一方、FW4搭載の OpenBlocks IoT の場合は、FW4 が HTTP/HTTPS を使用しており、ユーザがWEBページを実装しようとする場合に「さて、どうしたものか」と悩むかもしれません。</p> <blockquote> <p>FW4 にはNode-REDが付随しており、Node-REDを用いたWEBシステム実装が可能です。ただし、以下の解説では、Node-REDを使用せずにWEBシステムを実装する方法を述べています。</p> </blockquote> <h2 id="FW4のHTTPサーバを利用する"><a href="#FW4%E3%81%AEHTTP%E3%82%B5%E3%83%BC%E3%83%90%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B">FW4のHTTPサーバを利用する</a></h2> <p>前述のとおり、FW4 は HTTP/HTTPS を使用しています。FW4 のマンマシン・インターフェースはGUIですが、これはWEBで実装されています。このため、FW4 は自身のGUIを処理するためにHTTPサーバを使用しています</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.plathome.co.jp/docs/openblocks/fw4/webui/reference/index">Debian Linux FW4 WEB-UIガイド</a></p> <p>FW4 が使用するHTTPサーバをユーザも使用することができます。FW4 のHTTPサーバに相乗りするわけです。<br /> OpenBlocks のメーカーがこれを許しているわけではありません。以下に述べる方法でWEBアプリケーションを構築する場合は自己責任でお願いします。</p> <h3 id="FW4のGUIはどこにいるのか"><a href="#FW4%E3%81%AEGUI%E3%81%AF%E3%81%A9%E3%81%93%E3%81%AB%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B">FW4のGUIはどこにいるのか</a></h3> <p>FW4 の GUI に使用されているWEBページは、ファイル・システム中のどこにいるのか、これがわかれば FW4 のHTTPサーバに容易に相乗りすることができます。<br /> これを探すのは簡単です。OpenBlocks の Debian にログインしコマンドを使用すればわかります。</p> <blockquote> <p>OpenBlocks の Debian にログインするには、FW4の設定でSSHを有効にする必要があります。<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.plathome.co.jp/docs/openblocks/fw4/webui/initial/initial">Debian Linux FW4 スタートアップガイド 初期設定</a></p> </blockquote> <p>FW4 の GUI を処理しているのは PHP です。FW4 のログインのページを見ると、<code>login.php</code>となっている。<br /> <a href="https://crieit.now.sh/upload_images/5690c1081c78d2b94df4ab56361aa3ce62f8a63e5362a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5690c1081c78d2b94df4ab56361aa3ce62f8a63e5362a.png?mw=700" alt="image" /></a><br /> <code>login.php</code> を探すと、<code>/var/webui/docroot</code>と出てくる。わかりやすいですね。</p> <pre><code class="sh">root@obsiot:~# find / -name login.php -print /var/webui/docroot/system/login.php root@obsiot:~# </code></pre> <h3 id="/var/webui/docroot"><a href="#%2Fvar%2Fwebui%2Fdocroot">/var/webui/docroot</a></h3> <p><code>/var/webui/docroot</code>はというと。</p> <pre><code class="sh">root@obsiot:~# ls -l /var/webui/docroot total 112 -rw-r--r-- 1 www-data www-data 1524 Dec 21 2021 _ctrl_datacollect.php -rw-r--r-- 1 www-data www-data 1284 Dec 20 2021 _file_del.php -rw-r--r-- 1 www-data www-data 1412 Jan 26 2022 _nodered_ctl.php -rw-r--r-- 1 www-data www-data 1076 Dec 20 2021 _ppp_con.php drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:15 airmanage drwxr-xr-x 6 www-data www-data 4096 Apr 10 2020 apps drwxr-xr-x 4 www-data www-data 4096 Apr 29 16:16 css drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:15 extension drwxr-xr-x 4 www-data www-data 4096 Apr 29 16:15 images -rw-r--r-- 1 www-data www-data 18740 Dec 20 2021 index.php -rw-r--r-- 1 www-data www-data 4548 Dec 21 2021 index_datacontroller.php -rw-r--r-- 1 www-data www-data 3652 Jan 26 2022 index_nodered.php drwxr-xr-x 3 www-data www-data 4096 Apr 29 16:16 js drwxr-xr-x 3 www-data www-data 4096 Apr 29 16:16 lib drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:15 maintenance drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:15 network -rw-r--r-- 1 www-data www-data 21 Oct 12 2020 phpinfo.php drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:15 service drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:16 system drwxr-xr-x 2 www-data www-data 4096 Apr 29 16:15 technical drwxrwxrwt 2 www-data www-data 4096 Apr 29 17:54 tmp -rw-r--r-- 1 www-data www-data 884 Dec 20 2021 unsupport.php root@obsiot:~# </code></pre> <p><code>phpinfo.php</code>で見てみると。<br /> <a href="https://crieit.now.sh/upload_images/af285e9fb292814cf93441e83a89e8ff62f8a8ceb8b3d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/af285e9fb292814cf93441e83a89e8ff62f8a8ceb8b3d.png?mw=700" alt="image" /></a><br /> 万全の体制です。<br /> ユーザ用のディレクトリを作成します。その下にテスト用のページを作成します。</p> <pre><code class="sh">root@obsiot:/var/webui/docroot# mkdir hoge root@obsiot:/var/webui/docroot# cd hoge root@obsiot:/var/webui/docroot# vi test.php root@obsiot:/var/webui/docroot/hoge# ls -l total 4 -rw-r--r-- 1 root root 32 Aug 14 16:48 test.php root@obsiot:/var/webui/docroot/hoge# cat test.php <?php echo "Hello, World."; ?> root@obsiot:/var/webui/docroot/hoge# </code></pre> <p><a href="https://crieit.now.sh/upload_images/876c8fb28b0bdd37893d4f47bb07be2862f969f93a855.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/876c8fb28b0bdd37893d4f47bb07be2862f969f93a855.png?mw=700" alt="image" /></a><br /> 狙いどおりです。</p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18275 2022-08-11T09:30:57+09:00 2022-08-11T22:46:08+09:00 https://crieit.net/posts/ConMas-Gateway-2 ConMas Gateway スクリプトのデバッグ (2) <p>ConMas Gateway スクリプトのテスト、デバッグをどうする、というテーマの続編です。</p> <p>前編 <a href="https://crieit.net/posts/ConMas-Gateway-1">ConMas Gateway スクリプトのデバッグ (1)</a></p> <p>結論は「スクリプト単体でテストをしましょう」です。<br /> 端末(i-Reporter)や ConMas Gateway を使うと何かと不便なので、テスト、デバッグの9割をスクリプト単体でテストをし、最後に端末と ConMas Gateway を使ったテストをしましょう、ということです。</p> <p>このような、スクリプトを例として解説を進めます。<br /> <a href="https://crieit.now.sh/upload_images/8cb3d1ab854cf71d0668d52dd6b7a8cf62f32f9c851c3.PNG" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8cb3d1ab854cf71d0668d52dd6b7a8cf62f32f9c851c3.PNG?mw=700" alt="image" /></a></p> <h2 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h2> <p>テストに必要となるデータの雛形を取得します。</p> <p>Gateway が稼働するサーバ上のディレクトリ action 下のファイルを以下のようにしておきます。</p> <blockquote> <p>Gateway は端末からのリクエストを受けると最初にこのファイルを探し出し参照します。次に Gateway は、"script"に記述されたスクリプトを起動します。</p> </blockquote> <p>ConMas/gateway/action/hogehoge.json</p> <pre><code>{ "datasource": "script", "script": "scripts/requestlog.py" } </code></pre> <p>"script"に記述するスクリプトは以下のようなスクリプトにしておきます。</p> <p>ConMas/gateway/scripts/requestlog.py</p> <pre><code class="python">import sys import json import traceback jsonData = json.loads(sys.stdin.readline()) # querydata = jsonData['query'] # postdata = jsonData['post']['clusters'] f = open('./logs/request.log', 'w') dp = json.dumps(jsonData) f.write(dp + '\n') f.close() mappings_data = [] mappings = dict(error="", mappings=mappings_data) print(json.dumps(mappings)) </code></pre> <p>この状態で、端末からGateway連携を起動させます。当然ですが、このスクリプトが実行されます。<br /> このスクリプトは、端末からのリクエストをファイルに書き込む以外に何の仕事もしません。端末は期待するデータを受け取ることができませんが、準備作業としては問題はありません。</p> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>前述のスクリプトが出力したファイルは、サーバ上のディレクトリ ConMas/gateway/logs にあります。<br /> テスト対象となる(前述の、リクエストをファイルに書く以外に何もしないスクリプトを偽物とした場合の)本物のスクリプトの入力には、端末からのリクエストの代わりに、このファイルを使用します。<br /> これでスクリプト単体でのテストが可能となります。ここからのテストには、Gateway も端末も不要です。つまり、スクリプト・エンジンがPythonであれば、Pythonスクリプトの通常のテストとまったく同様のテストが可能です。当然ですが、便利なテスト・ツールを使うことが出来ます。</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://runebook.dev/ja/docs/python/library/trace">trace-Python文の実行をトレースまたは追跡する。</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kaitolucifer/items/dc58efebd72d72a8feb2">Pythonのデバッグを完全理解</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sky11fur/items/d4e6e2041d3bddd7b657">Pythonプログラムを追跡する</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/garaemon/items/ca72432b1890f2d793df">pythonの例外でstack traceを表示する</a></p> </blockquote> <p>忘れないうちにディレクトリ action 下のファイルを書き変えて本物にしておきましょう。<br /> ConMas/gateway/action/hogehoge.json</p> <pre><code>{ "datasource": "script", "script": "scripts/hogehoge.py" } </code></pre> <p>テスト用に取得した入力ファイルにはJSONが書かれています。ただし、見にくいのでツール等を使ってキレイにしてから使用することをおすすめします。</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://tools.m-bsys.com/development_tooles/json-beautifier.php">JSONきれい ~JSON整形ツール~</a></p> </blockquote> <p>例えば、こうやってスクリプトを実行させます。</p> <pre><code>c:\ConMas\gateway\scripts>type ..\logs\request.log | python hogehoge.py </code></pre> <p>参考までに、本物のスクリプトの姿はこんな感じです。<br /> ConMas/gateway/script/hogehoge.py</p> <pre><code class="python">import sys import json import traceback try: #----------------------------------------------------- # 帳票から送信されたデータ #----------------------------------------------------- jsonData = json.loads(sys.stdin.readline()) mappings_data = [] #----------------------------------------------------- # 帳票にレスポンスするデータを作成する処理がこの辺に書かれているはず #----------------------------------------------------- mappings = dict(error="", mappings=mappings_data) print(json.dumps(mappings)) except Exception as e: logging.debug(traceback.format_exc()) mappings = {"error": "Error: " + str(e)} print(json.dumps(mappings)) </code></pre> <p>テストにおいて入力データを変更したければ、入力ファイルの内容を書き換えれば良いのです。</p> <p>ここで紹介したテストでは、端末(帳票)とのコンビネーションをテストすることは出来ませんが、それは後ですれば良い、という考え方に基づいています。それじゃダメ、という考えの場合は始めからコンビネーションでテストしてください。</p> <p><a href="https://crieit.net/posts/ConMas-Gateway-1">ConMas Gateway スクリプトのデバッグ (1)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18272 2022-08-07T13:17:07+09:00 2022-08-07T14:17:54+09:00 https://crieit.net/posts/65978518bf6982554de02285b10e6a97 時系列データからデータの変化点を得る <p>仮想の課題と解決策を考えてみます。</p> <h2 id="課題"><a href="#%E8%AA%B2%E9%A1%8C">課題</a></h2> <p>PLCあるいはセンサーから取得されるデータの変化点(変化が発生した時刻)と、変化した際のデータの差(前のデータとの差)を取得する</p> <h3 id="取得するデータ"><a href="#%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF">取得するデータ</a></h3> <ul> <li>データに変化が生じた時刻</li> <li>前のデータとの差(対象のデータは数値で数は1つ)</li> </ul> <h3 id="条件"><a href="#%E6%9D%A1%E4%BB%B6">条件</a></h3> <ul> <li>データは1秒間隔でソースから取得され、テーブルAに記録される</li> <li>対象のデータは数値、1回に取得される数は1つ</li> <li>1回に取得されるデータは、テーブルAにレコードとして追加される</li> </ul> <h2 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h2> <p><a href="https://crieit.now.sh/upload_images/d6a43f666a7624cd589c903e4f32b2ec62ef2e3205011.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d6a43f666a7624cd589c903e4f32b2ec62ef2e3205011.png?mw=700" alt="image" /></a></p> <blockquote> <p>さて、データ・ソースから取得されたデータが無秩序にデータベース中のテーブルに書き込まれます。これはありがちなケースです。PLCやセンサーからデータを取得するゲートウェイの設計担当者が、出来る努力をしなかったケースですね。(もちろん、全てのデータがほしい、というオーダーがあった可能性は否定できませんが)<br /> このようなお粗末を許すプロジェクトですから、他の失敗も考えられます。<br /> 簡単に想像できる失敗は、目的とするデータを計算するバッチ・プログラムを作り出すことです。このプログラムはスケジューラで定期的に実行されます。良さそうな設計ですが、はたしてこのプログラムの処理はデータの発生に追いつくのでしょうか。もし追いつかない場合には、対症療法的な対策が取られるでしょう。それは、チューニングと称して行われる、データベースに対する様々な工夫です。それでも解決できない場合には、ハードウェアの設計担当者のミスが指摘されるかもしれません。こうしてプロジェクトは「計画になかった役に立たない不要な作業」のために大きく複雑になっていきます。<br /> 想像したくもない状況ですが、情報システム開発のプロジェクトでは、今でもこれに類する失敗が繰り返されています。</p> </blockquote> <p>経験からではなく歴史(学問)から学ぶ賢者はこう考えます「データへの処理は、そのデータが発生したときにしろ」。<br /> データ・ドリブン型システムとは、本来はこのようなことを指し、大昔からソフトウェアの工学分野で有効性が唱えられていました。ただし、今回のケースでは、データが発生した時には何も手出しができません。条件から読み取ると、発生したデータはもれなくテーブルAに書き込まれ、テーブルAに書き込まれる前になにかの工夫はできなさそうです。<br /> となると、可能で有効な細工は、データがテーブルAに追加される部分にすべきのようです。そして、もう一歩考えを進めると、データベースの基礎的な知識さえあれば、使うべきはトリガーだということに簡単に気づくことができます。<br /> 後述の解決策の一例でもトリガーを活用しています。</p> <h2 id="解決策の一例"><a href="#%E8%A7%A3%E6%B1%BA%E7%AD%96%E3%81%AE%E4%B8%80%E4%BE%8B">解決策の一例</a></h2> <h3 id="テーブルA"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%ABA">テーブルA</a></h3> <p>解決策を具体的に説明するために、テーブルAを以下のように仮定します。これは、かつての僕の記事で何度か登場した"from_plc"ですね。</p> <pre><code>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> </code></pre> <p>見てのとおり、モデルはMySQLで実装されています。</p> <h3 id="トリガー"><a href="#%E3%83%88%E3%83%AA%E3%82%AC%E3%83%BC">トリガー</a></h3> <p>計算と目的のデータをピックアップするために、トリガーを使います。トリガーは以下のとおりです。</p> <pre><code>DROP TRIGGER IF EXISTS from_plc_proc; DELIMITER // CREATE TRIGGER from_plc_proc AFTER INSERT ON from_plc FOR EACH ROW BEGIN DECLARE count_new INTEGER; DECLARE count_old INTEGER; DECLARE count_rec INTEGER; -- from_plc_last の参照 SELECT COUNT(*) INTO count_rec FROM from_plc_last; IF count_rec > 0 THEN SELECT JSON_EXTRACT(body, '$.value') INTO count_old FROM from_plc_last; ELSE INSERT INTO from_plc_last (time_insert) VALUES (NOW()); SET count_old = 0; END IF; -- from_plc_last の更新 UPDATE from_plc_last SET body = NEW.body, time_insert = NEW.time_insert; SET count_new = JSON_EXTRACT(NEW.body, '$.value'); -- count_status の更新 IF count_new <> count_old THEN INSERT INTO count_status (count_old, count_new, time_change) VALUES (count_old, count_new, NEW.time_insert); END IF; END; // DELIMITER ; </code></pre> <p>3つのテーブルが登場します。</p> <ul> <li>from_plc : 前述のテーブルAの実態</li> <li>from_plc_last : from_plc中の最新のレコードのコピーが置かれる</li> <li>count_status : 目的のデータの導出が可能なデータが記録される</li> </ul> <p>各テーブルの属性は以下のとおりです。from_plcは既出なので、あらためて示すことはしません。</p> <pre><code>mysql> desc from_plc_last; +-------------+-----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+-----------+------+-----+---------+-------+ | body | json | YES | | NULL | | | time_insert | timestamp | YES | | NULL | | +-------------+-----------+------+-----+---------+-------+ 2 rows in set (0.01 sec) mysql> desc count_status; +-------------+-----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+-----------+------+-----+---------+-------+ | count_old | int | YES | | NULL | | | count_new | int | YES | | NULL | | | time_change | timestamp | YES | | NULL | | +-------------+-----------+------+-----+---------+-------+ 3 rows in set (0.01 sec) mysql> </code></pre> <p>このシステムでは、from_plc_last はヒット率100%のキャッシュとして機能し、前後のデータの差を計算する処理の効率化に寄与します。</p> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>上記の解決策をテストしてみました。</p> <h3 id="データ"><a href="#%E3%83%87%E3%83%BC%E3%82%BF">データ</a></h3> <p>以前の僕の記事「サーバやPC上のプログラムでModbus機器からデータを取得 (2)」で登場したPerlスクリプトの劣化版とModbusシミュレーターでデータを発生させました。</p> <p><a href="https://crieit.net/posts/PC-Modbus-2">サーバやPC上のプログラムでModbus機器からデータを取得 (2)</a></p> <p>Perlスクリプトの46, 68行目をコメント・アウトしただけです。</p> <h3 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h3> <p>from_plc</p> <pre><code>mysql> select * from from_plc; +----------------------------------------------------+---------------------+ | body | time_insert | +----------------------------------------------------+---------------------+ | {"ts": "2022-08-07T12:31:52.445466", "value": 0} | 2022-08-07 12:31:52 | | {"ts": "2022-08-07T12:31:53.492522", "value": 0} | 2022-08-07 12:31:53 | | {"ts": "2022-08-07T12:31:54.547653", "value": 0} | 2022-08-07 12:31:54 | | {"ts": "2022-08-07T12:31:55.563391", "value": 0} | 2022-08-07 12:31:55 | | {"ts": "2022-08-07T12:31:56.610040", "value": 0} | 2022-08-07 12:31:56 | | {"ts": "2022-08-07T12:31:57.672496", "value": 1} | 2022-08-07 12:31:57 | | {"ts": "2022-08-07T12:31:58.703830", "value": 1} | 2022-08-07 12:31:58 | | {"ts": "2022-08-07T12:31:59.734885", "value": 1} | 2022-08-07 12:31:59 | | {"ts": "2022-08-07T12:32:00.766081", "value": 1} | 2022-08-07 12:32:00 | | {"ts": "2022-08-07T12:32:01.797569", "value": 1} | 2022-08-07 12:32:01 | | {"ts": "2022-08-07T12:32:02.859724", "value": 2} | 2022-08-07 12:32:02 | | {"ts": "2022-08-07T12:32:03.891062", "value": 2} | 2022-08-07 12:32:03 | | {"ts": "2022-08-07T12:32:04.958819", "value": 2} | 2022-08-07 12:32:04 | | {"ts": "2022-08-07T12:32:06.5521", "value": 2} | 2022-08-07 12:32:06 | +----------------------------------------------------+---------------------+ 14 rows in set (0.01 sec) mysql> </code></pre> <p>from_plc_last</p> <pre><code>mysql> select * from from_plc_last; +------------------------------------------------+---------------------+ | body | time_insert | +------------------------------------------------+---------------------+ | {"ts": "2022-08-07T12:32:06.5521", "value": 2} | 2022-08-07 12:32:06 | +------------------------------------------------+---------------------+ 1 row in set (0.00 sec) mysql> </code></pre> <p>count_status</p> <pre><code>mysql> select * from count_status; +-----------+-----------+---------------------+ | count_old | count_new | time_change | +-----------+-----------+---------------------+ | 0 | 1 | 2022-08-07 12:31:57 | | 1 | 2 | 2022-08-07 12:32:02 | +-----------+-----------+---------------------+ 2 rows in set (0.00 sec) mysql> </code></pre> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18271 2022-08-06T19:02:04+09:00 2022-08-11T09:31:57+09:00 https://crieit.net/posts/ConMas-Gateway-1 ConMas Gateway スクリプトのデバッグ (1) <p>製造現場の紙帳票のデジタル化を担うi-Reporter。現段階ではまだベストなソリューションとは言えませんが、今後の機能アップが期待出来るとすればそのポテンシャルは大きく、紙帳票のデジタル化に留まらず、旧態依然な現場のパネルまでも、i-ReporterをのせたWindowsのタッチパネルに入れ替わるかもしれません。日本の製造業をチャンピオンに返り咲かせるDXの誘因として重要なソリューションのひとつであることに間違いはありません。。<br /> このi-Reporterシステムに必須といってもよいのが"ConMas Gateway"です。まだ機能面での不足がある ConMas Gateway ですが、i-Reporter の jQuery.ajax とも言える ConMas Gateway を使わずして i-Reporter の存在意義は語れません。</p> <p>この記事では、ConMas Gateway スクリプトの効率的なデバッグ手法について複数回に分けて解説します。</p> <blockquote> <p>この記事の内容は、IT系開発に不慣れなFA系システム開発者向けです。WEBシステムのサーバ・サイド開発等に長けたITエンジニアにとっては、当たり前のことが書かれています。</p> <p>この記事の内容を理解するために、i-Reporterに関する細かな知識を事前に得ている必要はありません。ただし、この知識がない方は、ある程度の想像力を働かせながら読み進める必要があります。</p> </blockquote> <h2 id="システム・モデル"><a href="#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%83%BB%E3%83%A2%E3%83%87%E3%83%AB">システム・モデル</a></h2> <p><a href="https://crieit.now.sh/upload_images/62f914a7bc277c864df20629b59fa44862ee2e6f8df37.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62f914a7bc277c864df20629b59fa44862ee2e6f8df37.png?mw=700" alt="image" /></a></p> <ul> <li>ConMas Gateway から起動されたスクリプトは、i-Reporter からPOST(あるいはGET)されたデータを、標準入力を介して ConMas Gateway から受け取ります。</li> <li>スクリプトは i-Reporter へのレスポンスを標準出力を介して ConMas Gateway へ渡し、ConMas Gateway はこれを i-Reporter にレスポンスします。</li> </ul> <p>つまり、ConMas Gateway のスクリプトは、WEBシステムにおける CGIを使うスクリプトと何ら変わりがありません。</p> <blockquote> <p>面白いのは、ConMas Gateway と ConMas Server の間にインタフェースが全く存在せず、両者は何のやりとりもしないことです。<br /> もちろん、ConMas Server のデータベースは公開されているため、ConMas Gateway が直接あるいはスクリプトを介して、このデータベースをアクセスすることは可能です。</p> </blockquote> <p>なお、ConMas Gateway はスクリプトがなくても、自らの機能でデータベースをアクセスし結果を i-Reporter にレスポンスすることが出来ます。ただし、このデータベースのアクセスはシンプルなものに限定され、複雑な処理、あるいはデータベース・アクセス以外の処理を行うにはスクリプトが必要となります。<br /> この記事では、このスクリプトのデバッグについて述べており、ConMas Gateway 自身が持つデータベース・アクセスについては述べていません。</p> <h2 id="データ"><a href="#%E3%83%87%E3%83%BC%E3%82%BF">データ</a></h2> <p>i-Reporter とスクリプトの間でやりとりされるデータはJSONです。これは、データを処理する手続きをシンプルに記述できるという点で歓迎できます。</p> <ul> <li>POSTされるデータは、クラスタ(i-Reporterで処理される帳票上のデータの最小単位)の属性と(当該クラスタに入力された)データが key: value のタプルとなったJSONです。複数のタプルが配列で並びます。どのクラスタをPOSTするかは、事前に ConMas Designer 上で選択します。</li> <li>GETされるデータは任意です。ConMas Designer による設定でURLに続くデータを任意に記載することが出来ます。</li> <li>レスポンスは、帳票上のクラスタのIDと当該クラスタに表示されるデータをタプルにしたJSONです。もちろん、このタプルは配列で複数にすることができます。</li> </ul> <blockquote> <p>この記事では、データのフォーマットに関する細かな解説をしません。データのフォーマットに関する細かなルールや、ConMas Designer での設定については、シムトップス社から提供されるマニュアルを参照してください。これらの情報がなくても、この記事を読み進める上での支障にはなりません。<br /> マニュアルにアクセスするには、シムトップス社のサイトにアクセスするためのIDが必要になります。</p> </blockquote> <h2 id="標準と思われるデバッグ手法"><a href="#%E6%A8%99%E6%BA%96%E3%81%A8%E6%80%9D%E3%82%8F%E3%82%8C%E3%82%8B%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E6%89%8B%E6%B3%95">標準と思われるデバッグ手法</a></h2> <p>標準と思われるデバッグの手法は、i-Reporterで(必要であれば帳票にデータを入力し)帳票上のアクション・クラスタを発動させ、当該クラスタに設定されたスクリプトを動作させながらテストと不具合の解消を進めるという(何の工夫もない)いたって普通の方法です。<br /> 小さく単純でテストが簡単なスクリプトのデバッグでは、この方法でも良いかもしれません。ただし、大きく複雑で、複雑、高度なテストとデバッグを要するスクリプトの場合には問題が生じます。</p> <h4 id="問題"><a href="#%E5%95%8F%E9%A1%8C">問題</a></h4> <ul> <li>この方法ではスクリプトの動作を簡単にはトレースすることができない</li> <li>この方法でのスクリプトのトレースは、デバッグのための古典的なプリント、あるいはカバレージをファイル等に出力するなどが必要となる。一方で、Python で提供される Pdb や Trace 等のコマンドラインで容易に使えるツールが使用できない。</li> </ul> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> 初心者の方々のために、ゆっくりとマッタリと進めさせていただきます。<br /> 次回は「ではどうすれば良いか」です。</p> <p><a href="https://crieit.net/posts/ConMas-Gateway-2">ConMas Gateway スクリプトのデバッグ (2)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18267 2022-07-30T15:43:22+09:00 2022-07-30T15:57:47+09:00 https://crieit.net/posts/PC-Modbus-2 サーバやPC上のプログラムでModbus機器からデータを取得 (2) <p>サーバやPC上のプログラムでModbus機器からデータを取得、その第二回です。<br /> 第一回では、Android上のModbusシミュレータからデータを取得するための、Perlスクリプトのサンプルを紹介しました。Perlでは問題があるという方は、他の言語で記述しても原理は変わらないはずなので、他の言語で実装してみてください。</p> <p><a href="https://crieit.net/posts/PC-Modbus-1">サーバやPC上のプログラムでModbus機器からデータを取得 (1)</a></p> <p>今回は、取得したデータをデータベースに保存します。</p> <h2 id="データベース"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9">データベース</a></h2> <p>以前の記事で使用した、MySQLとテーブル"from_plc"<br /> を使います。</p> <p><a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a></p> <pre><code>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> </code></pre> <h2 id="Perlスクリプト"><a href="#Perl%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">Perlスクリプト</a></h2> <p>前回は、Perlスクリプトを僕のセルフォンTermux上で動作させましたが、今回はPCのWindows上で開発し動作させます。何故?</p> <blockquote> <p>僕のセルフォンのTurmuxのcpanになんらかの問題があるらしく、DBIのビルドができないんです。いろいろトライしたけど僕には解決は無理と判断し、Turmax上のPerlスクリプトを放棄しました。<br /> DBIは必須ではないですが、データベースをアクセスするPerlなら、DBIを使うのが一般的ですよね。<br /> DBIのない環境で、これ以上Perlを書く気にはなれませんよね。まあ、僕のせいなんですけどね。</p> </blockquote> <p>「サーバやPC上のプログラムでModbus機器からデータを取得 (1)」で作成したスクリプトに手を加えました。これで、Modbus機器からModbus/TCPにより取得したデータをデータベースに保存します。</p> <pre><code class="perl">#!/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"; } </code></pre> <p>Perlの自由さと気楽さが良いですね。</p> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>Windows上ということで、再び MOD-RSsim に登場いただきます。MOD-RSsimを起動しておきます。<br /> <a href="https://crieit.now.sh/upload_images/ce54d89db6542b8d42e049dac808c83862e4cf06c02a0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ce54d89db6542b8d42e049dac808c83862e4cf06c02a0.png?mw=700" alt="image" /></a><br /> Perlスクリプトを実行します。</p> <pre><code>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} </code></pre> <p>Holding Register 40001 の0を取得しデータベースに保存されます。</p> <p>MOD-RSsim で 40001 に任意の数値を入力します。<br /> <a href="https://crieit.now.sh/upload_images/365c981a39ba1de3af4b05aa11b1bcf162e4cfae8fe0a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/365c981a39ba1de3af4b05aa11b1bcf162e4cfae8fe0a.png?mw=700" alt="image" /></a></p> <p>40001 のデータが変わる都度、データベースに値が保存されます。</p> <pre><code>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> </code></pre> <p>データベースのテーブル"from_plc"を確認してみます。</p> <pre><code>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> </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>このように、Modbus機器からのデータ取得は、専用の機器や商用のソフトウェアがなくても可能です。今回はModbus/TCPでしたが、RTUでも原理は同じです。<br /> データ取得だけではなく、Modbus機器にデータを書き込むことも難しくはありません。Modbusの仕様を理解すれば、データの取得も書き込みも簡単です。<br /> Modbusに限らず、各メーカのPLCの仕様もほとんどがオープンになっているので、それぞれの仕様を理解しさえすれば、Modbusと同じように自分で書いたプログラムでの読み書きが可能です。<br /> 様々な種類のPLCやリモートIOなどをアクセスする場合には、ゲートウェイなどを利用したほうが良いかもしれません。一方で、限られた種類のPLCやリモートIOをアクセスする場合は、自分で書いたプログラムを使用するという選択もあると思います。</p> <p>「作らない」のではなく「作れない」んです。なぜ「作れない」のか?「作らない」からです。</p> <p><a href="https://crieit.net/posts/PC-Modbus-1">サーバやPC上のプログラムでModbus機器からデータを取得 (1)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18265 2022-07-29T07:33:22+09:00 2022-07-29T07:33:22+09:00 https://crieit.net/posts/15538b281af3d9bb53ada98193886b4e 旅先でサーバ上のデータベースをメンテナンスする <p>以前に投稿した記事「旅先でクラウド上のWindowsサーバにRDP接続」は、Androidセルフォンのアプリケーションで、SSHのポートフォワードを介して<br /> WindowsサーバにRDP接続するというものでした。</p> <p><a href="https://crieit.net/posts/Windows-RDP">旅先でクラウド上のWindowsサーバにRDP接続</a></p> <p>この記事で紹介した、僕を助けてくれるアプリケーションのうちの1つがこれなんです。</p> <p><a href="https://crieit.now.sh/upload_images/0f1620b003abae44c6e2dc0b9a1cb92a62e30b62d0a6f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0f1620b003abae44c6e2dc0b9a1cb92a62e30b62d0a6f.png?mw=700" alt="image" /></a></p> <p>これのポートフォワード設定を追加してあげると、セルフォンのアプリケーションから、サーバのデータベースのデータを参照したり更新したりすることができます。<br /> 皆さんもご存知のとおり、あたりまえのことなんですけどね。<br /> <a href="https://crieit.now.sh/upload_images/029983100bf745182d0640da3a29124262e304560a1a0.jpeg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/029983100bf745182d0640da3a29124262e304560a1a0.jpeg?mw=700" alt="image" /></a></p> <h2 id="ポートフォワード"><a href="#%E3%83%9D%E3%83%BC%E3%83%88%E3%83%95%E3%82%A9%E3%83%AF%E3%83%BC%E3%83%89">ポートフォワード</a></h2> <p>ConnectBotのSSHに下図にようなポートフォワードを追加してあげます。この例は、SSHサーバのローカルで稼働するMySQLに接続する設定です。<br /> <a href="https://crieit.now.sh/upload_images/47822d9b8e2a2f058338bdd5f7b2763562e30bf7b571b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/47822d9b8e2a2f058338bdd5f7b2763562e30bf7b571b.png?mw=700" alt="image" /></a><br /> RDPは以前に設定したものです。</p> <h2 id="データベースのデータを操作するアプリケーション"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3">データベースのデータを操作するアプリケーション</a></h2> <p>データベースのデータを操作するのに便利なアプリケーションがあります。僕の好みです。皆さんの評価など受け付けません。<br /> <a href="https://crieit.now.sh/upload_images/f6942cff736d92fe0736b22d959d417062e30d0b1153c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f6942cff736d92fe0736b22d959d417062e30d0b1153c.png?mw=700" alt="image" /></a><br /> MySQLへの接続はこんなふうにしてあげます。<br /> <a href="https://crieit.now.sh/upload_images/8822652800e925e419f3ed4441f1dff062e30d7d6ad50.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8822652800e925e419f3ed4441f1dff062e30d7d6ad50.png?mw=700" alt="image" /></a><br /> デフォルトでSQLiteへの接続が見えますが、ここにSQLiteがいれば、おそらくつながるのでしょう。<br /> <a href="https://crieit.now.sh/upload_images/b02515b7e752989101e54ece9e6f6e0462e30dc24b9c7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b02515b7e752989101e54ece9e6f6e0462e30dc24b9c7.png?mw=700" alt="image" /></a><br /> MySQLをクリックすると、こんなふうにオブジェクトが見えるようになります。<br /> <a href="https://crieit.now.sh/upload_images/740d71e54addfcf9377a6b27426a139a62e30e1268e41.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/740d71e54addfcf9377a6b27426a139a62e30e1268e41.png?mw=700" alt="image" /></a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18262 2022-07-26T17:14:42+09:00 2022-07-30T18:42:44+09:00 https://crieit.net/posts/PC-Modbus-1 サーバやPC上のプログラムでModbus機器からデータを取得 (1) <p>サーバやPC上のプログラムでModbus機器からデータを取得、なんて場面があります。例えば、接点やセンサーがつながったリモートIOからデータを取得したい場合などです。</p> <p>この記事では、Modbus/TCPでリモートIOなどからデータを取得するサンプル・プログラムを紹介します。</p> <p>この記事で紹介するサンプルはModbus/TCPを前提としていますが、Modbus/RTUでも原理は同じです<br /> 。Socketがシリアル・インタフェースに変わり、やりとりするデータのフォーマットがTCPからRTUに変わるだけです。<br /> ※RTUの場合、送受するデータにCRCを組み込む必要があり、少々やっかいですけどね。</p> <blockquote> <p>リモートIOのメーカーのサイトでは、PLCがリモートIOからデータを取得するように書かれていたりします。リモートIOからのデータを使って機械を制御する場合はそんな構成なのかもしれませんが、そのデータをサーバやPCで処理したい場合には、リモートIOのデータをサーバやPCが直接取得するのが合理的です。リモートIOとPLCは必ずしもセットではないのです。<br /> 「そんなことは、ここであらためて書かなくてもあたりまえだろ」と思われる方も多いでしょうが、固定観念と先入観が、迷わず(本来は不要であるはずの)PLCを登場させてしまいます。</p> </blockquote> <h2 id="Modbus/TCP"><a href="#Modbus%2FTCP">Modbus/TCP</a></h2> <p>Modbusについては、エム・システム技研さまのこのドキュメントがおすすめです。</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.m-system.co.jp/mssjapanese/kaisetsu/nmmodbus.pdf">Modbus プロトコル概説書</a></p> <h2 id="サンプル・プログラム"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%BB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0">サンプル・プログラム</a></h2> <p>今回はPerlを使いました。Perlである必要性はありませんが、Perlではだめだという理由もありません。なぜPerlを選択したか?僕のセルフォンでPerlが使えたからです。</p> <p>サンプルでは、ファンクション0x03: Read Holding Register を使っています。</p> <p>データをコンソールに16進表記で表示するために、公開されていたコレを遠慮なく拝借いたしました。<br /> <a target="_blank" rel="nofollow noopener" href="https://netlog.jpn.org/r271-635/2018/11/perl-bynary-dumper.html">(Perl) バイナリ変数の16進ダンプ表示</a></p> <pre><code class="perl">#!/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"; } } </code></pre> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>テストにも僕のセルフォンを使いました。<br /> Modbusスレーブとして、Androidアプリケーション"ModTCPSimX"に活躍いただきました。<br /> <a href="https://crieit.now.sh/upload_images/672850389bd205ccd2287edea4b6eea462df857ac0e06.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/672850389bd205ccd2287edea4b6eea462df857ac0e06.png?mw=700" alt="image" /></a><br /> ModTCPSimX のポート番号のデフォルトは5502です。僕のプログラム側も5502にしてあります。<br /> ※一般的なModbus/TCPは502です。<br /> Holding Register 40001 に任意の数値を設定しておきます。<br /> ※ModTCPSimX では値の背景が黄色の場合は更新がコミットされていません。鉛筆アイコンでコミットするのを忘れずに<br /> <a href="https://crieit.now.sh/upload_images/e0e934f699c14d7e14657cced9848d5c62df8d8718344.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e0e934f699c14d7e14657cced9848d5c62df8d8718344.png?mw=700" alt="image" /></a><br /> Termuxでプログラムを動かすと、最後のバイトに値が置かれたTXが返ります。<br /> <a href="https://crieit.now.sh/upload_images/a486c6b8599b14f88005f174460cdbd662df8dad794c7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a486c6b8599b14f88005f174460cdbd662df8dad794c7.png?mw=700" alt="image" /></a></p> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>せっかくなので、次回は取得したデータをデータベースに保存します。どうってことないんですけどね。</p> <p><a href="https://crieit.net/posts/PC-Modbus-2">サーバやPC上のプログラムでModbus機器からデータを取得 (2)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18260 2022-07-25T21:47:47+09:00 2022-07-25T23:01:21+09:00 https://crieit.net/posts/Windows-RDP 旅先でクラウド上のWindowsサーバにRDP接続 <p>この記事には、目新しい技術は書かれていません。既成のアプリケーションの使い方のひとつが紹介されているだけです。</p> <p>旅先でクラウド上のWindowsサーバにRDP接続したいな、という機会は、僕にはたくさんあります。PCを使える状況であればPCを使えば良いのですが、例えば電車の中などでPCを使えない、あるいはPCは使えるけどそれも面倒という場面も僕にはたくさんあります。<br /> こんなときのために、僕にはセルフォンがあるのです。</p> <h2 id="外部からの接続"><a href="#%E5%A4%96%E9%83%A8%E3%81%8B%E3%82%89%E3%81%AE%E6%8E%A5%E7%B6%9A">外部からの接続</a></h2> <p>外部から目的のWindowsサーバにRDPで接続するには、間にSSHが配置され、このSSHがポートフォワードしてくれる、というのが前提です。当然ながら、SSH接続には鍵が必要というのも前提です。<br /> つまり、何の変哲もない一般的な構成です。</p> <h2 id="僕を助けてくれるAndroidアプリ"><a href="#%E5%83%95%E3%82%92%E5%8A%A9%E3%81%91%E3%81%A6%E3%81%8F%E3%82%8C%E3%82%8BAndroid%E3%82%A2%E3%83%97%E3%83%AA">僕を助けてくれるAndroidアプリ</a></h2> <p>こういった場面で僕を助けてくれるAndroidアプリは以下の2つです。どちらか1つというわけではなく、2つがタックで助けてくれます。</p> <ul> <li>ConnectBot</li> <li>Remote Desktop</li> </ul> <p>これらはよく配慮された機能をもち、かつ余分な機能を持たないという、セルフォンのアプリケーションとして優れた特徴を持ったアプリケーションです。<br /> <a href="https://crieit.now.sh/upload_images/d56076624b7e1efe9cc376dfadad312b62de9ea0cb881.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d56076624b7e1efe9cc376dfadad312b62de9ea0cb881.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/a15fd3a22e58e5d358e77d9e1f0b9d9862de9ec12704a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a15fd3a22e58e5d358e77d9e1f0b9d9862de9ec12704a.png?mw=700" alt="image" /></a></p> <h2 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h2> <p>ConnectBotの設定を事前に行っておくべきです。</p> <h3 id="ConnectBot"><a href="#ConnectBot">ConnectBot</a></h3> <p>以下はConnectBotの設定です。</p> <p>"Manage Pubkeys"を使って、サーバへのSSH接続を行うための鍵を登録します。<br /> <a href="https://crieit.now.sh/upload_images/bd584093bd0999427a3c12f2f66be6e462de95445d595.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bd584093bd0999427a3c12f2f66be6e462de95445d595.png?mw=700" alt="image" /></a><br /> 鍵を登録した状態です。<br /> <a href="https://crieit.now.sh/upload_images/7f2e4dfbf1cef4fe59851276b2816fed62de9ee62bb97.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7f2e4dfbf1cef4fe59851276b2816fed62de9ee62bb97.png?mw=700" alt="image" /></a><br /> "Hosts"にSSHサーバを登録し接続します。鍵はここで使用します。SSHサーバへの登録と接続については、この記事では解説しません。難しくはないでしょう。</p> <p>SSHでSSHサーバに接続したら、メニューから[Port Forward]を選択します。<br /> <a href="https://crieit.now.sh/upload_images/372c4d79e6285840903eb025c3048e7562de94b11ae96.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/372c4d79e6285840903eb025c3048e7562de94b11ae96.png?mw=700" alt="image" /></a><br /> WindowsサーバのRDPへのポート・フォワードを設定します。<br /> <a href="https://crieit.now.sh/upload_images/be66b92cc798d4d94f8bf1b1f82c933262de9f17184cb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/be66b92cc798d4d94f8bf1b1f82c933262de9f17184cb.png?mw=700" alt="image" /></a></p> <h3 id="Remote Desktop"><a href="#Remote+Desktop">Remote Desktop</a></h3> <p>僕は、マイクロソフト社純正のアプリケーションを使用しています。<br /> ConnectBotによるSSH(= ポートフォワード)セッションを活かした状態で、RDPに接続します。<br /> <a href="https://crieit.now.sh/upload_images/698517cdfeff566370d6a2f2c9016cb562de9f2ebd751.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/698517cdfeff566370d6a2f2c9016cb562de9f2ebd751.png?mw=700" alt="image" /></a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18256 2022-07-23T23:14:58+09:00 2022-07-23T23:47:21+09:00 https://crieit.net/posts/PLC-JSON-6 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 前回は、PLCから取得したデータをデータベースに保存しました。</p> <p><a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a></p> <p>タイトルに書かれたテーマは前回で完了しています。完了していますが、これでは何か物足りないと感じ、今回はデータベースに次々と書かれるPLCからのデータをブラウザに表示してみます。</p> <p><a href="https://crieit.now.sh/upload_images/e6061bc3683ceff53f53e09554a0288e62dbf2a4188d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e6061bc3683ceff53f53e09554a0288e62dbf2a4188d4.png?mw=700" alt="image" /></a></p> <h2 id="WEBアプリケーション"><a href="#WEB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3">WEBアプリケーション</a></h2> <p>データベースのデータをWEBブラウザに表示するために、WEBアプリケーションをサーバ上に作成します。このサンプルでは、WEBアプリケーションはPHPスクリプトで実装します。</p> <p>いよいよ図が窮屈になってきました。これ以上に窮屈な図は、もはや理解容易性の面で逆効果です。正確さと理解容易性は、ある時点以後、反比例します。</p> <p><a href="https://crieit.now.sh/upload_images/a96212b429a0ca11f28e750801258aea62dbf49dd5ca8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a96212b429a0ca11f28e750801258aea62dbf49dd5ca8.png?mw=700" alt="image" /></a></p> <p>PHPである必要はありません。では、このサンプルを何故にPHPスクリプトで実装するのか?それは、そこ(私の開発環境)にPHPが稼働していたから。</p> <p>PHPの準備についてはここでは解説しません。PHPの準備についての解説は、他の記事にお任せします。PHPスクリプトでのMySQLアクセスについても、他の記事にお任せします。</p> <h2 id="HTMLとPHPスクリプト、そしてJavaScript"><a href="#HTML%E3%81%A8PHP%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%80%81%E3%81%9D%E3%81%97%E3%81%A6JavaScript">HTMLとPHPスクリプト、そしてJavaScript</a></h2> <p>結果的には、サーバ・サイドはPHPスクリプトのみではなく、以下の3つのファイルで構成してみました。</p> <ul> <li>HTML</li> <li>PHPスクリプト</li> <li>JavaScript</li> </ul> <p>3つめの JavaScript は jQuery です。jQuery は一般に配布されているとても便利なライブラリです。jQuery を利用することにより、とても高機能なWEBアプリケーションを短期間で実装することができます。jQuery の解説も他の記事にお任せします。</p> <p>以下がサンプルです。</p> <p>index.htm</p> <pre><code class="html"><html lang="ja"> <head> <meta charset="utf-8"> <meta http-equiv="Cache-Control" content="no-cache"> <title>Sample</title> <script type="text/javascript" src="js/jquery-3.6.0.min.js"></script> <script type="text/javascript"> function count_update() { $.ajax({ url:"index.php", method:"POST", success:function(data) { $('#count').html(data); } }); } $(function() { setInterval ( function() { count_update(); }, 1000 ); }); </script> </head> <body> count: <div id="count" style="display: inline-block;">count</div> </body> </html> </code></pre> <p>index.php</p> <pre><code class="php"><?php try { $dbh = new PDO('mysql:dbname=hoge;host=localhost;charset=utf8;' , 'hoge' , 'hoge001'); } catch (PDOException $e) { echo "Can't connect to database: " . $e->getMessage() . "\n"; exit(); } $sql = "SELECT JSON_EXTRACT(body, '$.value') AS count FROM from_plc ORDER BY time_insert DESC"; $res= $dbh->query($sql); foreach($res as $value) { echo $value['count']; break; } ?> </code></pre> <p>jQuery は、HTML中で参照されている、jquery-3.6.0.min.js です。<br /> HTML中には、jQueryを呼び出すオリジナルのJavaScriptスクリプトを書いています。<br /> いずれもコードの解説はいたしません。</p> <h2 id="WEBブラウザでアクセスしてみる"><a href="#WEB%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%A7%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">WEBブラウザでアクセスしてみる</a></h2> <p>WEBブラウザで上記の index.htm を参照すると以下ようになります。<br /> <a href="https://crieit.now.sh/upload_images/104c32d7402ec5f8908cc9021b18f35162dbfdbf6b841.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/104c32d7402ec5f8908cc9021b18f35162dbfdbf6b841.png?mw=700" alt="image" /></a><br /> 数字は、データベース上のテーブルが更新される都度変化してます。(正確には、JSONの要素"value"の値が変化してから1秒以内に、ブラウザの表示も変化します)</p> <h2 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h2> <p>ここまでサンプルとして実装したシステムを応用すると、工場の機器により製造される製品の生産数を、ほぼリアルタイムに画面表示する、といったシステムを構築することができます。<br /> 勘違いしていただきたくないのは、この記事で示したサンプルはあくまでもサンプルであって、基盤となるミドルウェアやプロトコル、プログラム言語は、この記事のサンプルで使用したものである必要はありません。もし、この記事で取り上げたような、PLCからデータを取得し、これを処理するようなシステムを設計・構築する場合は、この記事の内容にとらわれず、それぞれの環境、条件、実現したい機能などに応じて、最適なハードウェア、ミドルウェア、プロトコル、プログラム言語、そしてアーキテクチャを想像し選択してください。</p> <blockquote> <p>実はこのアーキテクチャを想像する、というのが設計者にとって最も楽しく重要な工程なのです。実装のテクニックなどは、アーキテクチャの上に成立する技術であって、アーキテクチャがイマイチなシステムは、おそらく実装もへんてこりんになってしまったり、どこかアンバランスなものになってしまうものです。</p> </blockquote> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18255 2022-07-22T18:43:07+09:00 2022-07-30T16:03:47+09:00 https://crieit.net/posts/PLC-JSON-5 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 前回は、データを保存するデータベースとテーブルを準備しました。</p> <p><a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a></p> <p>今回は、MQTTブローカーからデータを受信するサブスクライバーを実装します。少々駆け足になりますが、サブスクライバーには、データを受信しつつデータベースに書き込むまでを実装します。<br /> <a href="https://crieit.now.sh/upload_images/9a527b4c96ab1ffcf8096821036def3462d966e06c6c1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9a527b4c96ab1ffcf8096821036def3462d966e06c6c1.png?mw=700" alt="image" /></a></p> <h2 id="プログラミング"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">プログラミング</a></h2> <p>Pythonを使用して実装します。Pythonでなければならない合理的な理由はありませんが、私の環境では、Pythonではだめだという理由もありません。</p> <p>PythonでMQTTをハンドリングには、paho-mqttというライブラリを使うと、新規コーディング量を減らすことができます。paho-mqttの導入については、このサイトをおすすめします。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/emqx_japan/items/b63c918fe137a6db4b37">Python で MQTT (Paho)</a></p> <p>同様に、PythonからMySQLをアクセスするには、mysql-connector-python-rfというライブラリを使うと、新規コーディング量を減らすことができます。mysql-connector-python-rfの導入については、このサイトをおすすめします。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/hoto17296/items/0cfe7cdd3c47b69cc892">Python 3 から MySQL を触る</a></p> <p>最低限の機能を実装してみました。</p> <pre><code class="python">import paho.mqtt.client as mqtt import json import mysql.connector #----------------------------- # mqtt host = '127.0.0.1' port = 1883 topic = 'sample' def on_connect(client, userdata, flags, respons_code): print('status {0}'.format(respons_code)) client.subscribe(topic) def on_message(client, userdata, msg): json_text = json.dumps(json.loads(msg.payload)) print(json_text) try: #----------------------------- # database conn = mysql.connector.connect( host='127.0.0.1', port=3306, user='hoge', password='hoge001', database='hoge' ) cur = conn.cursor() sql = "insert into from_plc (body) values (%s);" cur.execute(sql, (json_text,)) conn.commit() cur.close() conn.close() except Exception as e: print(e) print("Debug: Error at Database access.") return if __name__ == '__main__': client = mqtt.Client(protocol=mqtt.MQTTv311) client.on_connect = on_connect client.on_message = on_message client.connect(host, port=port, keepalive=60) client.loop_forever() </code></pre> <p>サンプル・プログラムは、MQTTブローカー(=mosquitto)に届く、Topic = sample のメッセージを待ち受けます。メッセージが届くと、メッセージ中のデータをJSON文字列に変換し、これをMySQLのテーブル"from_plc"に追加します。この動作をひたすら繰り返します。</p> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>Pythonスクリプトを起動しておきます。</p> <pre><code>c:\Users\hoge>py sample.py status 0 </code></pre> <p>これまでと同様に、PLCには、本物の代わりにModbus/TCPシミュレーターを使用します。<br /> Modbus/TCPシミュレーター"mod_RSsim.exe"を起動します。<br /> <a href="https://crieit.now.sh/upload_images/7c92f619579f92befb1d50f450ce74b062d96b0700adb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7c92f619579f92befb1d50f450ce74b062d96b0700adb.png?mw=700" alt="image" /></a><br /> ゲートウェイのシミュレーターを起動するために、EasyBuilderのメニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/a6e571139cf659163b06d6721ff09fbf62d96b6c39201.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a6e571139cf659163b06d6721ff09fbf62d96b6c39201.png?mw=700" alt="image" /></a><br /> シミュレーターの起動を確認します。<br /> <a href="https://crieit.now.sh/upload_images/12e87729c176c0ab6bf620f55f4ff1a262d96ba8913fc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/12e87729c176c0ab6bf620f55f4ff1a262d96ba8913fc.png?mw=700" alt="image" /></a><br /> Modbus/TCPシミュレータの、アドレス40001(注意: +0です)をクリックし、Valueに任意の数値を入力し値を変更します。[OK]ボタンで閉じます。<br /> <a href="https://crieit.now.sh/upload_images/f609d225a2af3983fd89312d7fb7770862d96bd14929c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f609d225a2af3983fd89312d7fb7770862d96bd14929c.png?mw=700" alt="image" /></a><br /> Modbus/TCPシミュレータの、アドレス40001の値を変更する都度、Pythonスクリプトがこの情報をJSONで受け取り、コンソールに表示します。<br /> Pythonスクリプトを止めるには、キーボードから<code>Ctrl+C</code>を押してください。</p> <pre><code>c:\Users\hoge>py sample.py status 0 {"value": 0, "ts": "2022-07-21T15:27:43.676079"} {"value": 1, "ts": "2022-07-21T15:27:49.704662"} {"value": 2, "ts": "2022-07-21T15:27:57.641088"} {"value": 3, "ts": "2022-07-21T15:28:07.644020"} {"value": 4, "ts": "2022-07-21T15:28:11.627588"} {"value": 5, "ts": "2022-07-21T15:28:14.659965"} Traceback (most recent call last): File "c:\Users\hoge\sample.py", line 44, in <module> client.loop_forever() File "C:\Users\hoge\AppData\Local\Programs\Python\Python310\lib\site-packages\paho\mqtt\client.py", line 1756, in loop_forever rc = self._loop(timeout) File "C:\Users\hoge\AppData\Local\Programs\Python\Python310\lib\site-packages\paho\mqtt\client.py", line 1150, in _loop socklist = select.select(rlist, wlist, [], timeout) KeyboardInterrupt ^C c:\Users\hoge>py sample.py </code></pre> <p>MySQLのテーブル"from_plc"を確認すると、ゲートウェイからのJSONデータが保存されているのはわかります。</p> <pre><code>c:\Users\hoge>mysql -u hoge -p Enter password: ******* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 24 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-21T15:27:43.676079", "value": 0} | 2022-07-21 23:27:43 | | {"ts": "2022-07-21T15:27:49.704662", "value": 1} | 2022-07-21 23:27:49 | | {"ts": "2022-07-21T15:27:57.641088", "value": 2} | 2022-07-21 23:27:57 | | {"ts": "2022-07-21T15:28:07.644020", "value": 3} | 2022-07-21 23:28:07 | | {"ts": "2022-07-21T15:28:11.627588", "value": 4} | 2022-07-21 23:28:11 | | {"ts": "2022-07-21T15:28:14.659965", "value": 5} | 2022-07-21 23:28:14 | +--------------------------------------------------+---------------------+ 6 rows in set (0.00 sec) mysql> </code></pre> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>PLCのデータをデータベースにJSONで書きました。もう次回は不要かもしれませんが、せっかくデータベースに書いたので、次回はこれをブラウザに表示させます。くどいなあ。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18254 2022-07-21T09:17:30+09:00 2022-07-23T23:37:55+09:00 https://crieit.net/posts/PLC-JSON-4 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 初回から前回にかけては、ゲートウェイを使って、PLCから取得したデータをMQTTでブローカーに送信するまでを実装しました。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a></p> <p>今回はデータベースを準備します。<br /> <a href="https://crieit.now.sh/upload_images/348f9673721149a9206023ad2b3ca8e062d7eb7ab1449.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/348f9673721149a9206023ad2b3ca8e062d7eb7ab1449.png?mw=700" alt="image" /></a></p> <h2 id="データベース"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9">データベース</a></h2> <p>このサンプルではMySQLを使用します。MySQLでなければいけない理由はまったくありません。MySQLを選択した理由は、そこ(私の開発環境、作業場ですね)で動いていたからです。<br /> テーマを「JSONで保存」とした以上は、JSON型をサポートしているDBMSである必要はあるのですが、MySQL以外にもJSON型をサポートしているDBMSはあります。</p> <p>ユーザとデータベースは事前に作成されていたものを使います。これらの作成については解説はいたしません。</p> <p>MySQLにおける、データベースとユーザの作成については、こちらを参照ください。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.dbonline.jp/mysql/database/">データベースの作成</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.dbonline.jp/mysql/user/">ユーザーの作成</a></p> <h2 id="テーブルを作成する"><a href="#%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">テーブルを作成する</a></h2> <p>任意のユーザとデータベースでテーブルを作成します。</p> <pre><code>hoge@localhost C:\Users\hoge>mysql -u hoge -p Enter password: ******* Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 9 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> </code></pre> <p>データを保存するためのテーブルをひとつだけ作成します。</p> <pre><code>mysql> create table from_plc ( -> body json, -> time_insert timestamp default current_timestamp -> ); Query OK, 0 rows affected (0.17 sec) mysql> show full columns from from_plc; +-------------+-----------+-----------+------+-----+-------------------+-------------------+---------------------------------+---------+ | Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment | +-------------+-----------+-----------+------+-----+-------------------+-------------------+---------------------------------+---------+ | body | json | NULL | YES | | NULL | | select,insert,update,references | | | time_insert | timestamp | NULL | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED | select,insert,update,references | | +-------------+-----------+-----------+------+-----+-------------------+-------------------+---------------------------------+---------+ 2 rows in set (0.07 sec) mysql> </code></pre> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> MySQLのセットアップを行われた方がいるとすれば、その方にとっては、この記事の中で最も時間を要した回であったはずです。そういった方に配慮し、今回は短めとし続きは次回とさせていただきます。<br /> 私の記事は先を急ぎませんので。<br /> 次回は、いよいよデータをデータベースに書き込みます。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18252 2022-07-20T08:40:18+09:00 2022-07-23T23:36:59+09:00 https://crieit.net/posts/PLC-JSON-3 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 前回は、ゲートウェイを用いてPLCからデータを取得し、ゲートウェイのメモリに保存しました。<br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a></p> <p>今回はこのデータをサーバに送信します。<br /> <a href="https://crieit.now.sh/upload_images/99f8027812fe841fafbb82cb316db3e062d514f019ff6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/99f8027812fe841fafbb82cb316db3e062d514f019ff6.png?mw=700" alt="image" /></a></p> <p>サンプルで使用するゲートウェイは、引き続きWEINTEK製ゲートウェイの開発環境であるEasyBuilderのシミュレーターです。WEINTEK製ゲートウェイはサーバへのデータ送信に2種類のインタフェースを持っており、そのひとつがMQTTです。サンプルではMQTTを使用してサーバにデータを送信します。</p> <p>IT技術より生産現場の技術に強い方は、"MQTT"とか登場した時点で「もういやだな」となるかもしれません。そういった方はロガーの類が欲しくなりますよね。わかります。この記事ではロガーの類はまったく登場しません。この先を読み進めるかどうか、ご自由にご判断ください。</p> <p>サーバ側にはMQTTのブローカーが必要となります。サンプルでは、MQTTブローカーとしてMosquittoを使用します。<br /> <a href="https://crieit.now.sh/upload_images/ad4d70ef98a1d741deedb1e19d7eef3762d52f8173167.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ad4d70ef98a1d741deedb1e19d7eef3762d52f8173167.png?mw=700" alt="image" /></a></p> <p>Mosquittoはテストの場面で必要となります。サンプルの実装に加えて、テストをしてみたいという方は、自己責任でMosquitto、あるいは他のMQTTブローカーを準備してください。Mosquittoの準備については以下を参照してください。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/koichi_baseball/items/8fa9e0bdbe6d0aebe57d">【MQTT】MQTTの導入 mosquittoのインストール/動作確認まで</a></p> <h2 id="ゲートウェイのMQTT送信設定"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AEMQTT%E9%80%81%E4%BF%A1%E8%A8%AD%E5%AE%9A">ゲートウェイのMQTT送信設定</a></h2> <p>データをサーバに送信するために、前回まで作成したプロジェクトに設定を加えます。</p> <p>EasyBuilderのメニュー[IIoT/エネルギー]-[MQTT]をクリックすます。<br /> <a href="https://crieit.now.sh/upload_images/27403bdd1beb17ed91a9e1faad0c1ca462d5180119baf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/27403bdd1beb17ed91a9e1faad0c1ca462d5180119baf.png?mw=700" alt="image" /></a></p> <p>[有効にする]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/419a1bed20d2da61b4a260fcfaa1a92f62d518b58b47d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/419a1bed20d2da61b4a260fcfaa1a92f62d518b58b47d.png?mw=700" alt="image" /></a></p> <p>[一般的な属性]はデフォルトのままです。サンプルでは、ゲートウェイのシミュレーターと同じWindowsでMosquittoを動作させるので、IPアドレスは127.0.0.1のままです。[OK]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/3643faef7ae563403b18ecd92652d34562d5191b1eb6a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3643faef7ae563403b18ecd92652d34562d5191b1eb6a.png?mw=700" alt="image" /></a></p> <p>[トピック発行者]タブで[新規作成]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/0602c7b6837e71a182c385cd85680f2862d51a01c7f24.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0602c7b6837e71a182c385cd85680f2862d51a01c7f24.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/999d37a64349d3064b39bf08d0b2694f62d51a4c34273.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/999d37a64349d3064b39bf08d0b2694f62d51a4c34273.png?mw=700" alt="image" /></a></p> <p>[一般的な属性]タブで以下のように設定します。<br /> 中ほどの設定、[アドレス]のLBと0は、LB0がONになったらMQTTでサーバにデータを送信する、という意図です。<br /> <a href="https://crieit.now.sh/upload_images/01b4e17a1817470a004336900fa7ffbb62d51b4209fc2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/01b4e17a1817470a004336900fa7ffbb62d51b4209fc2.png?mw=700" alt="image" /></a></p> <p>[アドレス]タブに移り、[新規作成]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/59910b8f3292a98b49714a41816f8c3762d51c263f6ec.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59910b8f3292a98b49714a41816f8c3762d51c263f6ec.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/58b56e5ee811a948f574326534769a5762d51c2fa8f19.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/58b56e5ee811a948f574326534769a5762d51c2fa8f19.png?mw=700" alt="image" /></a></p> <p>ダイアログに以下のように設定し、[OK]ボタンをクリックします。<br /> [アドレス]の設定LWと0は、LW0にあるデータを送信する、という意図です。LW0には、PLCから取得したデータが置かれています。<br /> <a href="https://crieit.now.sh/upload_images/de2bc7ffeecf75b76dec7522f7a27fd162d51cb082328.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/de2bc7ffeecf75b76dec7522f7a27fd162d51cb082328.png?mw=700" alt="image" /></a></p> <p>[MQTTトピック発行者オブジェクト属性]ウィンドウの[OK]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/bc638bc4ee1e717109c9a3a6157e5d5f62d51d48b7489.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bc638bc4ee1e717109c9a3a6157e5d5f62d51d48b7489.png?mw=700" alt="image" /></a></p> <p>[MQTT]ウィンドウの[終了]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/9534a197b16903a10274457bdd2e71e662d51dbac95f8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9534a197b16903a10274457bdd2e71e662d51dbac95f8.png?mw=700" alt="image" /></a></p> <p>以上で、ゲートウェイのMQTT設定は完了です。</p> <h2 id="ゲートウェイのプログラミング"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">ゲートウェイのプログラミング</a></h2> <p>前回作成したゲートウェイのプログラムに、若干のステップを追加します。</p> <p>EasyBuilderのメニュー[プロジェクト]-[マクロ]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d5216eb34e4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d5216eb34e4.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d52185a7fbe.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d52185a7fbe.png?mw=700" alt="image" /></a></p> <p>[ID : 001]をダブル・クリックします。<br /> <a href="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d52233bc50d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d52233bc50d.png?mw=700" alt="image" /></a></p> <p>以下のように、ステップを追加します。<br /> <a href="https://crieit.now.sh/upload_images/48e72c305c89ae10a73ee74cb389156462d5295243ba1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/48e72c305c89ae10a73ee74cb389156462d5295243ba1.png?mw=700" alt="image" /></a><br /> 6〜10行目では、今回取得したデータと前回取得したデータを比較して、同じデータであったら何もしない、と書いています。業務上、連続する同じデータは不要という場合に、サーバに無駄なデータを送信しません。<br /> 13〜14行目は、MQTT送信をキックするために、ゲートウェイのLB0をオンにします。LB0はMQTT送信後に自動でオフになります(そのようにMQTTを設定しました)。</p> <p>コピーしたい方はこちらからどうぞ。</p> <pre><code>macro_command main()         unsigned short        x40001 = 0         GetData(x40001, "MODBUS TCP/IP (32-Bit)", 4x, 1, 1)                  unsigned short        LW0 = 0         GetData(LW0, "Local HMI", LW, 0, 1)         if x40001 == LW0 then                 return         end if         SetData(x40001, "Local HMI", LW, 0, 1)         bool        LB0 = 1                 SetData(LB0, "Local HMI", LB, 0, 1) end macro_command </code></pre> <p>エディタの[閉じる]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d56c6e2048c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d56c6e2048c.png?mw=700" alt="image" /></a></p> <p>[コンパイルが成功]に[ID : 000]があることを確認します。<br /> <a href="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d56ca4cea61.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d56ca4cea61.png?mw=700" alt="image" /></a></p> <blockquote> <h3 id="連続する同じデータは不要"><a href="#%E9%80%A3%E7%B6%9A%E3%81%99%E3%82%8B%E5%90%8C%E3%81%98%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E4%B8%8D%E8%A6%81">連続する同じデータは不要</a></h3> <p>連続する同じデータは不要という仕様は、よくある仕様です。例えば、工場の生産マシンで生産される製品の数をPLCで数えている場合、そのカウンタがこれに該当します。変化のないカウント値は不要なはずです。この不要なデータをネットワークやサーバに垂れ流すと、ネットワークやサーバにとって無駄な負荷となります。データベースのサイズも無駄に膨れ上がります。「IoTのデータは無駄なデータばかりだけど、そういうものだ」という誤った悟りを持った設計者を多く見かけますが、実は、設計次第でエレガントなシステムになります。想像が足りないだけなのです。<br /> 時系列データベースが提供する重複排除機能などは、あったほうが便利、という程度です。排除する前に発生させなければ良いのです。ただし、ロガーなどを使って無秩序に記録したデータをデータベースにロードする、というような場合は、不要なデータの排除に苦労します。<br /> 連続する同じデータをサーバ側で捨てるテクニックもあります。これについては機会があれば、別途解説をさせていただきます。</p> </blockquote> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>テストには、MQTTブローカーと、一時的にテストで使用するMQTTの汎用サブスクライバーが必要となります。<br /> これらは、Mosquittoが提供してくれます。Mosquittoの準備については以下を参照してください。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/koichi_baseball/items/8fa9e0bdbe6d0aebe57d">【MQTT】MQTTの導入 mosquittoのインストール/動作確認まで</a></p> <p>私の開発・テスト環境では、MosquittoブローカーはWindowsの設定で自動起動にしてあり、常に実行中です。<br /> <a href="https://crieit.now.sh/upload_images/d3c132e88775428a7334c810a8606fc362d5203d78fd4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d3c132e88775428a7334c810a8606fc362d5203d78fd4.png?mw=700" alt="image" /></a></p> <blockquote> <p>注意: 他のサーバ上のMosquittoへ送信する場合には注意が必要です。Mosquittoは、標準の設定では外部からのメッセージを受け付けません。この場合は、外部からのメッセージを受け付けるよう、Mosquittoの設定を変更する必要があります。</p> </blockquote> <p>受信側となるMQTTサブスクライバーを起動しておきます。<br /> Mosquittoにはコマンド起動可能な簡単なサブスクライバーが付属しています。Windowsでは、このコマンドはMosquittoのインストール・フォルダにあります。<br /> 以下のように起動しておきます。</p> <pre><code>c:\Program Files\mosquitto>mosquitto_sub -h localhost -t sample </code></pre> <p>オプション -h localhost は、localhost上のブローカーから受信することを示しています。-t sample はトピック名を示しており、ゲートウェイのMQTT設定で設定したトピック名と同じである必要があります。mosquitto_sub はトピックsampleのメッセージをひたすら待ち続けます。</p> <p>前回のテストと同様に、Modbus/TCPシミュレーター"mod_RSsim.exe"を起動します。</p> <blockquote> <p>注意: "mod_RSsim.exe"は連続起動に時間制限がある点に注意してください。制限を超えた場合は再起動させます。</p> </blockquote> <p><a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d52790afb85.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d52790afb85.png?mw=700" alt="image" /></a></p> <p>EasyBuilderのメニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/439fd0e9432caba5e9ed0546875106a462d528085da76.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/439fd0e9432caba5e9ed0546875106a462d528085da76.png?mw=700" alt="image" /></a></p> <p>シミュレーターが起動したことを確認します。<br /> <a href="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d5285c8e0d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d5285c8e0d4.png?mw=700" alt="image" /></a></p> <p>前回のテストと同様に、Mdobus/TCPシミュレーターのアドレス40001のデータを任意の数値に書き換えます。<br /> <a href="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d5287c3564a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d5287c3564a.png?mw=700" alt="image" /></a></p> <p>Mosquittoのサブスクライバーが、受信したデータをコンソールに出力します。<br /> <a href="https://crieit.now.sh/upload_images/e6004c9810a653ca72e7b22384c6d9b862d525e0e1ffc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e6004c9810a653ca72e7b22384c6d9b862d525e0e1ffc.png?mw=700" alt="image" /></a></p> <p>ゲートウェイのプログラムで、データに変化があった場合にのみ送信、としているため同じデータが次々と送られてくることはありません。ゲートウェイを単なるプロトコル変換器だと考えているなら、それは誤りです。</p> <p>ゲートウェイのMQTT設定にて、タイムスタンプを付与するよう設定したため、tsという名前でゲートウェイの時刻が送られてきます。</p> <blockquote> <p>このゲートウェイの時刻はサーバの時刻と必ずしも一致しない、という点を設計上留意してください。時刻の取扱いについては別途、解説をさせていただきます。</p> </blockquote> <p>以上でテストは終了です。</p> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> 次回は、データベースを準備します。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18251 2022-07-19T16:02:44+09:00 2022-07-23T23:36:00+09:00 https://crieit.net/posts/PLC-JSON-2 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> 今回はその2回目、ゲートウェイによるPLCからのデータ取得です。<br /> <a href="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4d91f05808.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4d91f05808.png?mw=700" alt="image" /></a><br /> サンプルの環境については第一回で解説しています。必ず第一回を参照し、前提となる環境を理解した上で以下の解説を参照してください。<br /> <a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a></p> <h2 id="想定: PLC上のデータ"><a href="#%E6%83%B3%E5%AE%9A%3A+PLC%E4%B8%8A%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF">想定: PLC上のデータ</a></h2> <p>サンプルでは、取得するPLC上のデータを以下のように想定しています。<br /> <a href="https://crieit.now.sh/upload_images/520f28de070aceae292d971ae449b7a762d4e11edce3b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/520f28de070aceae292d971ae449b7a762d4e11edce3b.png?mw=700" alt="image" /></a></p> <blockquote> <h3 id="Modbus/TCP"><a href="#Modbus%2FTCP">Modbus/TCP</a></h3> <p>Modbus/TCPについては、このドキュメントが理解しやすいドキュメントです。M・SYSTEM技研さまに感謝です。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.m-system.co.jp/mssjapanese/kaisetsu/nmmodbus.pdf">M・SYSTEM技研さま Modbusプロトコル概説書</a></p> <p>間違っていただきたくないのは、"Modbus/TCP"とは、PLCが自身のメモリを後悔するためのインタフェースのひとつにすぎない、ということです。"Modbus/TCP"が貴重なプロトコルである理由を述べます。PLCが自身のメモリを公開するためのプロトコルは他にもたくさんあります。そして、それらの多くは各PLCメーカー固有のものです。Modbus/TCPの特徴は、複数のPLCメーカー間で横断で採用されているプロトコルという点です。複数のメーカーのPLCは、標準ではModbus/TCPが使用できずオプションとして提供され、標準では当該メーカー固有のプロトコルがサポートされます。</p> <h3 id="Holding Registers"><a href="#Holding+Registers">Holding Registers</a></h3> <p>"Holding Registers"とは、Modbus/TCPが定める複数のメモリ領域のうちのひとつです。主に、数値や文字を記憶するために用いられます。アドレスは40001〜です。サンプルでは、40001上のデータをターゲットにします。<br /> 今回、PLCの代わりに活躍してくれる"mod_RSsim.exe"では、このような画面で眺めることが出来ます。<br /> <a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4ea62383a2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4ea62383a2.png?mw=700" alt="image" /></a></p> </blockquote> <h2 id="ゲートウェイの設定とプログラミング"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%A8%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">ゲートウェイの設定とプログラミング</a></h2> <p>前回は、ゲートウェイを設定し、PLCに見立てたModbus/TCPシミュレーターに接続しました。<br /> 今回は、ゲートウェイを使って、PLCに見立てたModbus/TCPシミュレーターからデータを取得します。サンプルでは前回と同様に、ゲートウェイの代わりにWEINTEK製ゲートウェイのソフトウェア開発ツールであるEasyBuilderのシミュレーターを使用します。</p> <p>前回作成したEasyBuilderのプロジェクトを開き、メニュー[プロジェクト]-[マクロ]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d4f019b0314.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b6df62bab4d0a16cee1095a55dcb836f62d4f019b0314.png?mw=700" alt="image" /></a><br /> <a href="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d4f0418d4a6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5f0828812556ea6a775135414a365cc362d4f0418d4a6.png?mw=700" alt="image" /></a></p> <p>[マクロ]ウィンドウから[新規作成]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/3dc7453b874086ec512519c0c059939862d4f063d178d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3dc7453b874086ec512519c0c059939862d4f063d178d.png?mw=700" alt="image" /></a></p> <p>[マクロエディター]に以下のようにコードを記述します。<br /> [周期的に実行する]オプションをオンにし、時間間隔を10にします。<br /> <a href="https://crieit.now.sh/upload_images/418d778add1fbee6a591cd7db72ef99462d4f0998d976.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/418d778add1fbee6a591cd7db72ef99462d4f0998d976.png?mw=700" alt="image" /></a><br /> コピーしたい方はこちらからどうぞ。</p> <pre><code>macro_command main()         unsigned short        x40001 = 0         GetData(x40001, "MODBUS TCP/IP (32-Bit)", 4x, 1, 1)         SetData(x40001, "Local HMI", LW, 0, 1) end macro_command </code></pre> <p>[マクロエディター]の下にある[閉じる]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d4f113d33c3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/901952ea06c21742a0a40d878071bca462d4f113d33c3.png?mw=700" alt="image" /></a></p> <p>[マクロ]ウィンドウの[コンパイルが成功]に新たなマクロが追加されたら、ひとまずプログラミングは終了です。<br /> <a href="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d4f137a479e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/62d62aa6b4b8125fc109c443ad5f47b262d4f137a479e.png?mw=700" alt="image" /></a></p> <blockquote> <h3 id="マクロ=プログラムの意図"><a href="#%E3%83%9E%E3%82%AF%E3%83%AD%3D%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E6%84%8F%E5%9B%B3">マクロ=プログラムの意図</a></h3> <p>Modbus/TCPが定める"Holding Register"(アドレス: 40001〜)は、EasyBuilderでは"4x"で解釈されます。<br /> EasyBuilderでは、対象装置(今回はModbus/TCPシミュレーター)のメモリ・マップを参照することができます。EasyBuilderの[アドレス]ペイン(ウィンドウ)にて、装置名、ステーション番号、アドレスタイプを以下のように設定すると、ひとつめのアドレスが赤になっているはずです。これは、作成したマクロがこのアドレスを参照している、という証です。<br /> <a href="https://crieit.now.sh/upload_images/26365e0800f525da7b98106b7e7bd71062d4f28eba07f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/26365e0800f525da7b98106b7e7bd71062d4f28eba07f.png?mw=700" alt="image" /></a><br /> 今回作成したコードでは、行4で、対象装置の"4x"のひとつめのアドレスから1ワードだけデータを取得しています。対象装置は”MODBUS TCP/IP (32-Bit)”です。これは、前回、システムパラメータで設定した対象装置の「装置名」です。<br /> 行4で取得したデータを、行5で自らのメモリ"LW"のひとつめのアドレスにコピーしています。EasyBuilder(=WEINTEK製ゲートウェイ)も複数のメモリ領域をもっており、LWはそのうちのひとつです。LWは16bitワードであり、Modbus/TCPのHolding Registerと同じワード・サイズです。<br /> <a href="https://crieit.now.sh/upload_images/5e787b399f3f70441d4ec0226b16d5de62d4f52debe24.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5e787b399f3f70441d4ec0226b16d5de62d4f52debe24.png?mw=700" alt="image" /></a><br /> [周期的に実行する]オプションのオンはこのプログラムを周期的に実行することを意味します。そしてその周期を今回は1000m秒(=1秒)としています。<br /> このプログラムで、PLCからデータを取得する、という今回の目的は達成されるはずです。</p> <h3 id="周期的に実行する"><a href="#%E5%91%A8%E6%9C%9F%E7%9A%84%E3%81%AB%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B">周期的に実行する</a></h3> <p>とても残念なことに、PLCからデータを取得するゲートウェイは、PLC側のデータ変更等のイベントをもって振る舞いを起こすということが出来ません。これは、完全にPLC側の都合によるものです。このため、ゲートウェイがPLC上のデータを参照するとき、ゲートウェイは空振りすることを承知でPLCのメモリを周期的に参照します。いまどきのシステムとしては実に残念です。"Factory Automation"の技術者は、ITの世界ではとっくに古典となった"Message Passing"や"Data Driven"といった考え方すら学んでいないのです。こういった問題は当面は放置され続けるでしょう。</p> </blockquote> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>前回と同様に、PLCには、本物の代わりにModbus/TCPシミュレーターを使用します。</p> <p>Modbus/TCPシミュレーター"mod_RSsim.exe"を起動しておきます。<br /> <a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4f9862eaeb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4f9862eaeb.png?mw=700" alt="image" /></a></p> <p>EasyBuilderのメニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4f99fb1415.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4f99fb1415.png?mw=700" alt="image" /></a></p> <p>シミュレーターが起動したら、メニュー[Object]を選択し、右側にある[+]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/f596a7ef9d3f9ee63dadb64cd89f981d62d4fa3b2cb36.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f596a7ef9d3f9ee63dadb64cd89f981d62d4fa3b2cb36.png?mw=700" alt="image" /></a></p> <p>ダイアログにて以下のように入力し[OK]ボタンをクリックします。<br /> <a href="https://crieit.now.sh/upload_images/8fc3bd495e156941cea03c12cf981d6062d4faaf89f7f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8fc3bd495e156941cea03c12cf981d6062d4faaf89f7f.png?mw=700" alt="image" /></a></p> <p>Modbus/TCPシミュレータの、アドレス40001(注意: +0です)をクリックし、Valueに任意の数値を入力、[OK]ボタンで閉じます。<br /> <a href="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d4fc4dbfc1b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/117891e3a7d9cfb3ad2aee7bc5db0e2662d4fc4dbfc1b.png?mw=700" alt="image" /></a><br /> アドレス40001の表示が変わります。<br /> <a href="https://crieit.now.sh/upload_images/4b7477028132e4aa88253b72874d6e0d62d50fdb774c8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4b7477028132e4aa88253b72874d6e0d62d50fdb774c8.png?mw=700" alt="image" /></a></p> <p>EasyBuilderのシミュレータ上で、LW-0も変更されることを確認します。<br /> <a href="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d4fb4d7cf76.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/75590b5b1988d3633f2403c0eda4c4f762d4fb4d7cf76.png?mw=700" alt="image" /></a></p> <p>これでテストは終わりです。数行のプログラムのテストとしては十分でしょう。</p> <h2 id="次回"><a href="#%E6%AC%A1%E5%9B%9E">次回</a></h2> <p>続きは次回とさせていただきます。<br /> 今回は、初心者には少しだけ難解であったかもしれません。慌てずじっくり、しっかり理解して進んでください。</p> <p><a href="https://crieit.net/posts/PLC-JSON-1">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18249 2022-07-18T11:42:52+09:00 2022-07-23T23:35:09+09:00 https://crieit.net/posts/PLC-JSON-1 PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (1) <p>PLCからゲートウェイでデータを取得し、データベースにJSONで保存します。複数回に分けて、サンプルを用いて解説します。<br /> <a href="https://crieit.now.sh/upload_images/4bef24e301bab8c385bc5659d66fab1262d4b8451782b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4bef24e301bab8c385bc5659d66fab1262d4b8451782b.png?mw=700" alt="image" /></a><br /> 初回は、ゲートウェイでPLCのデータを取得します。<br /> <a href="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4b8537ad2c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c2c94364c86325cb400ed616c683221562d4b8537ad2c.png?mw=700" alt="image" /></a><br /> サンプルは、Windowsがあれば環境の準備と動作が可能です。よろしければ、自己責任のもとで環境を準備し、サンプルを動作させてみてください。</p> <h2 id="PLC"><a href="#PLC">PLC</a></h2> <p>この解説でのサンプルでは、PLCの代わりにModbus/TCPシミュレーターを使用します。これにより、PLCの本物がなくても、実装の学習とテストが可能となります。<br /> 今回使用したModbus/TCPシミュレーターは、貴重なmod_RSsim.exeです。もちろん、他のシミュレータでも構いません。</p> <ul> <li>Modbus/TCPシミュレーター: MOD-RSsim Version 8.20</li> </ul> <p><a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4afc50041e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4afc50041e.png?mw=700" alt="image" /></a></p> <h2 id="ゲートウェイ"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4">ゲートウェイ</a></h2> <p>この解説のサンプルでは、WEINTEK製ゲートウェイを想定します。WEINTEK製ゲートウェイのソフトウェア開発ツールは、Windows上でソフトウェアを書くことができることに加え、ゲートウェイの動きをシミュレートすることが出来ます。これにより、ゲートウェイの本物がなくても、実装の学習とテストが可能となります。</p> <ul> <li>WEINTEK製ゲートウェイのソフトウェア開発環境: EasyBuilder Pro</li> </ul> <p>EasyBuilderはWEINTEKのサイトからダウンロードすることが出来ます。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.weintek.com/globalw/Download/Download.aspx">https://www.weintek.com/globalw/Download/Download.aspx</a></p> <h2 id="ゲートウェイの設定とプログラミング"><a href="#%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%A8%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0">ゲートウェイの設定とプログラミング</a></h2> <p>ゲートウェイは各社、各モデルで実装のスタイルが大きく異なります。しかし、この解説では、多くのゲートウェイでの機能実装が可能となるよう勘案し、よりシンプルな手順になるよう考慮しています。<br /> 前述のとおり、この解説では、ゲートウェイの本物を使用しません。代わりにEasyBuilderという、WEINTEK製ゲートウェイの開発環境を使用します。</p> <p>EasyBuilder Pro を起動します。<br /> <a href="https://crieit.now.sh/upload_images/70b6249e55a43645fa24782555dba08a62d4bfe2afa50.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/70b6249e55a43645fa24782555dba08a62d4bfe2afa50.png?mw=700" alt="image" /></a></p> <p>プロジェクトを新たに作成します。<br /> これから行う設定が、どのゲートウェイの設定であるかをゲートウェイのモデルの一覧から選択します。このサンプルでは、最もシンプルなモデル"cMT-G01"を選択します。<br /> <a href="https://crieit.now.sh/upload_images/73133d6f86ad19a49ce3d64ec95ce1a662d4c0da0fe55.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/73133d6f86ad19a49ce3d64ec95ce1a662d4c0da0fe55.png?mw=700" alt="image" /></a><br /> "cMT-G01"とはこのようなハードウェアです。<br /> <a target="_blank" rel="nofollow noopener" href="https://dl.weintek.com/public/cMT/eng/Datasheet/cMT-G01_Datasheet_ENG.pdf">cMT-G01</a></p> <p>このゲートウェイが対象にするPLCを設定します。メニュー[ホーム]-[システムパラメータ]を選択します。<br /> <a href="https://crieit.now.sh/upload_images/e1fd4a5385cd74a43bada117619cb52762d4c1ed7671d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e1fd4a5385cd74a43bada117619cb52762d4c1ed7671d.png?mw=700" alt="image" /></a></p> <p>[装置]タブを選択します。<br /> <a href="https://crieit.now.sh/upload_images/173c66d906d17f0d2905b9713ccd038c62d4c305cc7ca.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/173c66d906d17f0d2905b9713ccd038c62d4c305cc7ca.png?mw=700" alt="image" /></a></p> <p>[装置/サーバを新規追加]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/116c9134b0a53cfc0182069b0ee3f37a62d4e5e3e346e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/116c9134b0a53cfc0182069b0ee3f37a62d4e5e3e346e.png?mw=700" alt="image" /></a></p> <p>以下のように設定します。<br /> <a href="https://crieit.now.sh/upload_images/b4813a2fca2935a56983ddc1dfcefddc62d4c2a3aec24.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b4813a2fca2935a56983ddc1dfcefddc62d4c2a3aec24.png?mw=700" alt="image" /></a><br /> PLCに相当するModbus/TCPシミュレータは、EasyBuilderと同じWindowsマシン上で稼働させる予定です。このため装置(=PLC=Modbus/TCPシミュレータ)のIPアドレスを127.0.0.1にしています。<br /> 装置のポートはModbus/TCPの標準である502を選択しています。</p> <blockquote> <p>上記設定は、この解説におけるサンプルのための設定です。現実は、対象とするPLCやリモートIOなどに応じた設定が必要となります。<br /> 例えば、対象の装置が三菱電機のMELSECであれば、該当のモデルを選択します。下は、Mitsubishi QJ71E71の例です。<br /> <a href="https://crieit.now.sh/upload_images/cf6a77fc1f36d91ac1b06786216393f462d634aac0aa8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cf6a77fc1f36d91ac1b06786216393f462d634aac0aa8.png?mw=700" alt="Mitsubishi QJ71E71 の例" /></a></p> </blockquote> <h2 id="テスト"><a href="#%E3%83%86%E3%82%B9%E3%83%88">テスト</a></h2> <p>ここまでで、PLC(=Modbus/TCPシミュレータ)に接続する設定が終わりました。接続のテストをします。</p> <p>mod_RSsim.exeを起動しておきます。<br /> <a href="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4c596c61b2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59603780d55c186e1a70e1b112539c5562d4c596c61b2.png?mw=700" alt="image" /></a></p> <p>次はEasyBuilderです。<br /> もちろん、これまでの設定をゲートウェイ(cTM-G01)にロードしてゲートウェイを動かすことも可能ですが、今回はシミュレーターを使います。<br /> メニュー[プロジェクト]-[オンラインシミュレーション]をクリックします。<br /> <a href="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4c5d9df4b8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e57da9ac757974ac53d74202ab3f186962d4c5d9df4b8.png?mw=700" alt="image" /></a></p> <p>シミュレーターが起動します。"MODBUS TCP/IP (32-Bit)"に接続済みであるように見えますが、これはあてにはなりません。<br /> <a href="https://crieit.now.sh/upload_images/2b4ac9177034a63ae4627b253d2cade562d4c6d9f2b98.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2b4ac9177034a63ae4627b253d2cade562d4c6d9f2b98.png?mw=700" alt="image" /></a></p> <p>Modbus/TCPシミュレーターのウィンドウの左上にある"Connected"の表示を確認します。ゲートウェイからの接続が行われていれば、[1/10]となります。<br /> <a href="https://crieit.now.sh/upload_images/320155af5baceef2c37476485fd2741962d4e7455062b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/320155af5baceef2c37476485fd2741962d4e7455062b.png?mw=700" alt="image" /></a></p> <h2 id="次回へ"><a href="#%E6%AC%A1%E5%9B%9E%E3%81%B8">次回へ</a></h2> <p>続きは次回とさせていただきます。<br /> 初心者の方々のために、ゆっくりとマッタリと進めさせていただきます。<br /> 次回は、ゲートウェイによるPLCからのデータ取得です。</p> <p><a href="https://crieit.net/posts/PLC-JSON-2">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (2)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-3">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (3)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-4">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (4)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-5">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (5)</a><br /> <a href="https://crieit.net/posts/PLC-JSON-6">PLCからゲートウェイでデータを取得しデータベースにJSONで保存 (6)</a></p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18243 2022-07-17T07:52:23+09:00 2022-07-17T07:52:23+09:00 https://crieit.net/posts/JavaScript-Cookie JavaScript: Cookieから特定要素を見つけ出す際のトリム <p>jQuery.Cookieなどのラッピングされたものを使用する場合には、なんの考慮もいらないはずです。<br /> 裸のJavaScriptでハンドリングする場合に忘れがちなのがトリムです。</p> <h2 id="サンプル"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">サンプル</a></h2> <pre><code>function getCookie (key) { var cookies = document.cookie; var cookiesArray = cookies.split(';'); for (var tuple of cookiesArray){ var strArray = tuple.split('='); var str = strArray[0]; if (str.trim() == key) { // トリムしてあげましょう str = strArray[1]; return str.trim(); } } return ''; } </code></pre> <p>CやPerlなんかで書く場合には、まあ忘れることはないんですけどね。私の場合、JavaScriptの場合が特に油断しますね。<br /> トリムのオーバーヘッドなんてわずかでしょうからね、私の場合は、要らないんじゃない?と思える場合でもとりあえずトリムしますよ。</p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18242 2022-07-16T15:21:02+09:00 2022-07-16T15:21:02+09:00 https://crieit.net/posts/HTML-JavaScript HTML + JavaScript: 見出しやボタンなどの表示文字列を統一させたいとき <p>とあるシステムに登場する複数のWEB画面のあいだで、見出しやボタンの表示文字列を統一させたい場合に、私が使う手法のひとつがこれです。<br /> いろんな方が、これと同じような手法をとられていると思います。かな?</p> <h2 id="サンプル"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">サンプル</a></h2> <p>JavaScript側からです。これを各HTMLにインクルードして使います。</p> <pre><code>// JavaScript function label_print () { var arrVar = { label_lastname: "姓", label_firstname: "名", label_birthday: "生年月日", }; var arrVarButton = { button_save: "保存", button_cancel: "キャンセル", }; for (let key in arrVar) { if(document.getElementById(key) != null) { $('#' + key).html(arrVar[key]); } } for (let key in arrVarButton) { if(document.getElementById(key) != null) { $('#' + key).val(arrVarButton[key]); } } } </code></pre> <p>HTML側です。</p> <pre><code><!-- HTML --> ... <body class="" onload="label_print();"> ... <td class="label" style="white-space: nowrap;"> <div id="label_lastname"></div> </td> ... </code></pre> <p>サーバ・サイドで、データベースなどを用いて処理する手法もあったり、いろんな手法があっていずれも賛否両論だと思います。上記はレガシーで単純、必要なパーツも少なく取り回しが楽ですね。Simple is best.</p> COOL MAGIC PRODUCTS tag:crieit.net,2005:PublicArticle/18241 2022-07-16T02:44:09+09:00 2022-07-16T03:56:58+09:00 https://crieit.net/posts/PHP-62d1a769cd327 PHP - とある月の月末の日付を取得する <p>少々まわりくどいようですが、これが安全な手続きのうちのひとつです。</p> <h2 id="サンプル"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">サンプル</a></h2> <pre><code><?php // test.php $str = '2022-07-16'; $str = date('Y-m-01', strtotime('1 month', strtotime($str))); echo date('Y-m-d', strtotime('-1 day', strtotime($str))); ?> </code></pre> <p>テスト</p> <pre><code>$ php -f test.php 2022-07-31 $ </code></pre> COOL MAGIC PRODUCTS