tag:crieit.net,2005:https://crieit.net/tags/JSON/feed 「JSON」の記事 - Crieit Crieitでタグ「JSON」に投稿された最近の記事 2022-08-11T22:46:08+09:00 https://crieit.net/tags/JSON/feed 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/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/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/16582 2021-01-12T23:48:54+09:00 2021-01-12T23:48:54+09:00 https://crieit.net/posts/powershell-access-key-and-value-in-psobject-20210112 (PowerShell) PSCustomObject をループさせつつキーと値の両方にアクセスする <p>引き続き PowerShell のお話。今度は PSCustomObject をループさせつつキーと値の両方にアクセスする方法が分からず躓きました。</p> <h2 id="現象"><a href="#%E7%8F%BE%E8%B1%A1">現象</a></h2> <pre><code class="json">{ "common": { "config": { "user.name": "testuser", "user.id": 12345, "user.email": "[email protected]" } } } </code></pre> <p>例えばこんな <code>config.json</code> があるとします。</p> <p>この JSONファイル を読み込んでループさせつつ、キーと値の双方にアクセスしたい。</p> <p>そこで <code>foreach($confgi in $configData.GetEnumerator()){}</code> としたり、 <code>foreach($confgi in $configData.Keys){}</code> としたりしましたが一向に中身が取得できず四苦八苦。</p> <h2 id="対処"><a href="#%E5%AF%BE%E5%87%A6">対処</a></h2> <pre><code class="powershell"># JSONファイル 読み込み [String]$configPath = Join-Path ( Convert-Path . ) 'config.json' $configData = Get-Content -Path $configPath -Raw -Encoding UTF8 | ConvertFrom-JSON # ループ foreach( $key in $configData.common.config.psobject.properties.name ) { Write-Host $key ' = ' $configData.common.config.$key } </code></pre> <p>最終的に、 <code>$Object.psobject.properties.name</code> という全然異なるアクセス方法だったことが分かりました。今回の場合はキーが階層で入り組んでいるのでさらに <code>.</code> (ピリオド(ドット)) の個数が増えています。</p> <pre><code class="bash">> PowerShell -ExecutionPolicy RemoteSigned .\test.ps1 user.name = testuser user.id = 12345 user.email = [email protected] </code></pre> <p>やっとキーと値の両方が取得できました……。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-pscustomobject?view=powershell-7.1">PSCustomObject について知りたかったことのすべて - PowerShell | Microsoft Docs</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/16579 2021-01-11T22:59:05+09:00 2021-01-11T22:59:05+09:00 https://crieit.net/posts/powershell-access-key-with-piriod-in-psobject-20210111 (PowerShell) PSCustomObject のピリオド(ドット)入りのキー名にアクセスする <p>Powershell で JSON から読み込んだオブジェクトにピリオド(ドット)入りのキー名があり、これにどうやってアクセスしようか悩みました。</p> <h2 id="現象"><a href="#%E7%8F%BE%E8%B1%A1">現象</a></h2> <pre><code class="json">{ "user": { "user.name": "testuser", "user.email": "[email protected]" } } </code></pre> <p>例えばこんな <code>config.json</code> があるとします。</p> <p>この JSONファイル を読み込み、ユーザの入力を受け付けて設定を上書きするスクリプトを書きました。</p> <pre><code class="powershell"># JSONファイル 読み込み [String]$configPath = Join-Path ( Convert-Path . ) 'config.json' $configData = Get-Content -Path $configPath -Raw -Encoding UTF8 | ConvertFrom-JSON # 一度出力 Write-Host $configData.user['user.name'] # 入力受付 [String]$username = Read-Host 'ユーザー名 を入力してください (半角英数字と一部記号 (-, _) )。' if (-not ($username -match "^[a-zA-z0-9\-_]+$") ) { Write-Host 'ERROR: ユーザ名 に使用できない文字が含まれています。' -BackgroundColor DarkRed Write-Host `r`n exit } $configData.user['user.name'] = $username # 再度出力 Write-Host $configData.user['user.name'] </code></pre> <p>ここで問題となったのが <code>user.name</code> という <code>.</code> (ピリオド(ドット))入りのキー名。オブジェクトで <code>.</code> でキー名を連結できるのが JavaScript に似ていたので、つい JavaScript のように <code>$configData.user['user.name']</code> と書いてしましました。</p> <p>すると</p> <pre><code class="bash">> PowerShell -ExecutionPolicy RemoteSigned .\test.ps1 ユーザー名 を入力してください (半角英数字と一部記号 (-, _) )。: aaa 型 System.Management.Automation.PSObject のオブジェクトにインデックスを付けることはできません。 発生場所 PATH\TO\PROJECT\test.ps1:16 文字:1 + $configData.user['user.name'] = $username + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) []、RuntimeException + FullyQualifiedErrorId : CannotIndex </code></pre> <p>怒られました。しかも、初出の位置ではなく変数で上書きする部分で。初出の <code>Write-Host</code> による標準出力はブランクを出力してスルーされたようです。</p> <p>以上より、</p> <ul> <li>変数の値を読み込む場合(出力など): 存在しないキーとしてブランク出力してそのままスルー</li> <li>変数の値を書き込む場合: エラー</li> </ul> <p>となるようです。困った。</p> <h2 id="対処"><a href="#%E5%AF%BE%E5%87%A6">対処</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-pscustomobject?view=powershell-7.1">PSCustomObject について知りたかったことのすべて - PowerShell | Microsoft Docs</a></li> </ul> <blockquote> <p>プロパティ名に文字列を使用しても、やはり機能します。</p> <p><code>$myObject.'Name'</code></p> </blockquote> <p>なん……だと……。</p> <p>ということで上記スクリプトを書き換えます。</p> <pre><code class="powershell"># JSONファイル 読み込み [String]$configPath = Join-Path ( Convert-Path . ) 'config.json' $configData = Get-Content -Path $configPath -Raw -Encoding UTF8 | ConvertFrom-JSON # 一度出力 Write-Host $configData.user.'user.name' # 入力受付 [String]$username = Read-Host 'ユーザー名 を入力してください (半角英数字と一部記号 (-, _) )。' if (-not ($username -match "^[a-zA-z0-9\-_]+$") ) { Write-Host 'ERROR: ユーザ名 に使用できない文字が含まれています。' -BackgroundColor DarkRed Write-Host `r`n exit } $configData.user.'user.name' = $username # 再度出力 Write-Host $configData.user.'user.name' </code></pre> <p>肝は <code>$configData.user.'user.name'</code> 。 <code>$configData.user['user.name']</code> の角括弧 (<code>[]</code>)を外してキー名の文字列をそのまま <code>.</code> (ピリオド(ドット)) で繋げました。</p> <pre><code class="bash">> PowerShell -ExecutionPolicy RemoteSigned .\test.ps1 testuser ユーザー名 を入力してください (半角英数字と一部記号 (-, _) )。: abc abc </code></pre> <p>上手く行きました。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-pscustomobject?view=powershell-7.1">PSCustomObject について知りたかったことのすべて - PowerShell | Microsoft Docs</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/15753 2020-03-09T10:04:51+09:00 2020-03-09T10:04:51+09:00 https://crieit.net/posts/Java-JSON-lib-JSON-Date 【Java】JSON-libを使ってJSON配列を生成する際に日付型(Date)を好きなフォーマットで変換する方法 <p>最近はWEBのサービス開発などでSPA(Single Page Applicatoin)が流行って?るので、<br /> サーバーとクライアントとのデータのやり取りをJSONで行うのはよくあるケースかと思います。</p> <p>サーバー側をJavaで開発している場合に、Javaオブジェクトや Bean を JSONに変換するのに、<br /> JSON-lib というのも結構主流なやり方かと思いますが、ちょっと日付型のところでハマったので備忘的な記事を。</p> <p><span style="color: #de2a2a;">※JSON-lib 自体の詳しい説明(利用方法)などは省略します。</span></p> <h2 id="JSON-libとは"><a href="#JSON-lib%E3%81%A8%E3%81%AF">JSON-libとは</a></h2> <blockquote><a target="_blank" rel="nofollow noopener" href="https://ja.osdn.net/projects/sfnet_json-lib/" target="_blank" rel="noopener noreferrer">OSDNサイト</a>より転載 Json-libは、beans, map, collection, Java配列、XML to JSON, DynaBeansを変革するJavaライブラリです。 Douglas Crockfordによる作業をベースにしています。</blockquote> <p>Javaオブジェクトを簡単に JSON文字列に変換したり、逆に JSON文字列からオブジェクトを生成したりといったことが可能です。</p> <p>昔、XMLへの相互変換ロジックを自作したことがありますが、こういう処理って実際に作ってみると結構大変だったりします。<br /> 便利な世の中ですね。</p> <h2 id="サンプル"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">サンプル</a></h2> <p>変換するサンプルクラスを作成。</p> <p>Person.java</p> <pre><code>package jp.co.doraxdora.sample; import java.io.Serializable; import java.util.Date; /** * パーソンクラス * * @author doraxdora * */ public class Person implements Serializable { /** ID */ private int id; /** 氏名 */ private String name; /** 年齢 */ private int age; /** 更新日時 */ private Date updateDate; public Person() { } /** * パーソンの生成 * * @param id * @param name * @param age */ public Person(int id, String name, int age, Date updateDate) { this.id = id; this.name = name; this.age = age; this.updateDate = updateDate; } /** * @return id */ public int getId() { return id; } /** * @param id セットする id */ public void setId(int id) { this.id = id; } /** * @return name */ public String getName() { return name; } /** * @param name セットする name */ public void setName(String name) { this.name = name; } /** * @return age */ public int getAge() { return age; } /** * @param age セットする age */ public void setAge(int age) { this.age = age; } /** * @return updateDate */ public Date getUpdateDate() { return updateDate; } /** * @param updateDate セットする updateDate */ public void setUpdateDate(Date updateDate) { this.updateDate = updateDate; } } </code></pre> <p>何故か更新日時を持つパーソンクラスです。</p> <h3 id="JavaオブジェクトからJSON文字列に変換"><a href="#Java%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%8B%E3%82%89JSON%E6%96%87%E5%AD%97%E5%88%97%E3%81%AB%E5%A4%89%E6%8F%9B">JavaオブジェクトからJSON文字列に変換</a></h3> <pre><code>import net.sf.json.JSONObject; public class HelloWorld { public static void main(String[] args) { Person person = new Person(1, "サンプル 一郎", 20, new Date()); JSONObject jsonObject = JSONObject.fromObject(person); System.out.println(String.valueOf(jsonObject)); } } </code></pre> <p>こんな感じで、Dateを持つクラスをJSONに変換すると次のような文字列が生成されます。</p> <pre><code>{ "age": 20, "id": 1, "name": "サンプル 一郎", "updateDate": { "date": 23, "day": 2, "hours": 10, "minutes": 22, "month": 3, "seconds": 44, "time": 1555982564167, "timezoneOffset": -540, "year": 119 } } </code></pre> <p>これはこれで正しいのですが、クライアント側で日時の部分が扱いづらい。。</p> <h2 id="対応"><a href="#%E5%AF%BE%E5%BF%9C">対応</a></h2> <p>JSON-lib には、「JsonValueProcessor」という、変換時に対象となる値に対して処理できるインターフェースが定義されているので、<br /> 実装したクラスを作成して日付型オブジェクトに対して別途変換する処理を組み込みます。</p> <p>DateJsonValueProcessor.java</p> <pre><code>package jp.co.doraxdora.sample; import java.util.Date; import net.sf.json.JsonConfig; import net.sf.json.processors.JsonValueProcessor; import java.text.SimpleDateFormat; /** * 日付型をJSON変換する際にフォーマットする * * @author doraxdora * */ public class DateJsonValueProcessor implements JsonValueProcessor { /* (非 Javadoc) * @see net.sf.json.processors.JsonValueProcessor#processArrayValue(java.lang.Object, net.sf.json.JsonConfig) */ @Override public Object processArrayValue(Object value, JsonConfig jsonConfig) { return process((Date) value, jsonConfig); } /* (非 Javadoc) * @see net.sf.json.processors.JsonValueProcessor#processObjectValue(java.lang.String, java.lang.Object, net.sf.json.JsonConfig) */ @Override public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) { return process((Date) value, jsonConfig); } /** * 日付型を文字列に変換. * * @param date * @param config * @return */ private Object process(Date date, JsonConfig config) { if (date == null) { return null; } SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd(E)"); return formatter.format(date); } } </code></pre> <h3 id="JsonConfig に DateValueProcessor を設定して変換"><a href="#JsonConfig+%E3%81%AB+DateValueProcessor+%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%97%E3%81%A6%E5%A4%89%E6%8F%9B">JsonConfig に DateValueProcessor を設定して変換</a></h3> <pre><code>import net.sf.json.JSONObject; import net.sf.json.JsonConfig; public class HelloWorld { public static void main(String[] args) { Person person = new Person(1, "サンプル 一郎", 20, new Date()); JsonConfig config = new JsonConfig(); config.registerJsonValueProcessor(java.util.Date.class, new DateJsonValueProcessor()); JSONObject jsonObject = JSONObject.fromObject(person, config); System.out.println(String.valueOf(jsonObject)); } } </code></pre> <p>という感じで、日付型だった場合の処理を設定して変換してあげると次のような文字列が生成されます。</p> <pre><code>{ "age": 20, "id": 1, "name": "サンプル 一郎", "updateDate": "2019/04/23(火)" } </code></pre> <p>大分シンプルになりました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ちょっとした単発のプログラムネタでした。</p> <p>実は以前 Python でも同様の問題にぶち当たったことがあって、<br /> だったら Java でも同じようになんとかできるだろうと調べてみたという感じです。</p> <p>何かのお役に立てれば。</p> <p>ではでは。</p> <p> </p> doraxdora tag:crieit.net,2005:PublicArticle/15437 2019-09-29T19:46:32+09:00 2019-09-29T19:46:32+09:00 https://crieit.net/posts/Data-Aeson Data.Aesonの使い方 <p><a target="_blank" rel="nofollow noopener" href="https://www.leo-leo.uno/2019/02/09/798/">自分のブログ記事</a>からのクロス投稿です。</p> <p>HaskellのJSONライブラリとしておそらくメジャーな<a target="_blank" rel="nofollow noopener" href="http://hackage.haskell.org/package/aeson">Data.Aeson</a>ですが、使い方が難しいので色々試行錯誤しています。</p> <p>以降のコードはghci上で実行することを想定しています。<br /> 適当にstackでプロジェクトを作って、aesonをcabalファイルのbuild-dependsに追加してビルドし、<code>stack ghci</code>とするのが良いと思います。あと、<a target="_blank" rel="nofollow noopener" href="http://hackage.haskell.org/package/text">Data.Text</a>や<a target="_blank" rel="nofollow noopener" href="http://hackage.haskell.org/package/unordered-containers">Data.HashMap</a>なども必要です。</p> <pre><code class="shell"># ここはターミナルで stack new sandbox-prj simple cd sandbox-prj # cabalファイルのbuild-dependsにaeson, text, unordered-containers, vectorを追記 stack build stack ghci </code></pre> <p>はじめに、以下をghci上でコピペしておいてください。以降のコードが全部動くはずです。</p> <pre><code class="haskell">-- ghci上で -- 最初にこれらをコピペしておくと、以降のコードが全部動くはず -- 後でGHC拡張を使う :set -XDeriveGeneric :set -XExistentialQuantification :set -XFlexibleContexts :set -XFlexibleInstances :set -XGADTs :set -XInstanceSigs :set -XOverloadedStrings :set -XTypeFamilies :set +m -- 後で色々使う import Control.Applicative (empty, pure) import Data.Aeson import qualified Data.Aeson.Encoding.Internal as DAE import Data.Aeson.Types import Data.Char (toLower, toUpper) import qualified Data.HashMap.Lazy as HML import qualified Data.HashMap.Strict as HMS import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Vector as V import GHC.Generics </code></pre> <h3 id="基本的な使い方"><a href="#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%BF%E3%81%84%E6%96%B9">基本的な使い方</a></h3> <p>基本的な型については特にパーサーを定義しなくて良いので、いきなりエンコード・デコードできます。</p> <p>エンコード</p> <pre><code class="haskell">encode 1 -- --> "1" encode "hello" -- --> "\"hello\"" encode [1,2,3] -- --> "[1,2,3]" encode (1,2) -- --> "[1,2]" encode (Just 1) -- --> "1" encode True -- --> "true" encode Null -- --> "null" encode (1234, "hello", True, False, [1,2,3], Just 10) -- --> "[1234,\"hello\",true,false,[1,2,3],10]" </code></pre> <p>デコードは、Haskellの型を指定する必要があります。</p> <pre><code class="haskell">-- 型を指定しないとデコードできない decode "1" -- --> Nothing decode "1" :: Maybe Int -- --> Just 1 decode "\"hello\"" :: Maybe String -- --> Just "hello" decode "[1234,\"hello\",true,false,[1,2,3],10]" :: Maybe (Int,String,Bool,Bool,[Int],Maybe Int) -- --> Just (1234,"hello",True,False,[1,2,3],Just 10) </code></pre> <h3 id="ToJSON, FromJSON"><a href="#ToJSON%2C+FromJSON">ToJSON, FromJSON</a></h3> <p>エンコード・デコードするためには、そのデータ型が<code>ToJSON</code>・<code>FromJSON</code>のインスタンスになっている必要があります。<br /> このように定義します。<code>DeriveGeneric</code>拡張(はじめにセットしたやつ)と<code>GHC.Generics</code>が必要です。</p> <pre><code class="haskell">data MyData = MyData { field :: String } deriving (Show, Generic) instance FromJSON MyData instance ToJSON MyData myData = MyData "my data!!" encode myData -- --> "{\"field\":\"my data!!\"}" decode "{\"field\":\"my data!!\"}" :: Maybe MyData -- --> Just (MyData {field = "my data!!"}) </code></pre> <p>簡単ですね。基本的には、エンコード・デコードの実装を自分で定義しなくても自動的に導出してくれます。</p> <pre><code class="haskell">-- :{ :} はghciで複数行定義をするときに使う記号 :{ data Person = Person { name :: String , age :: Int , group :: [String] } deriving (Show, Generic) :} instance FromJSON Person instance ToJSON Person p1 = Person "Neo" 30 ["groupA", "groupB"] p2 = Person "Morpheus" 40 ["groupA"] p3 = Person "Trinity" 30 [] encode p1 -- --> "{\"group\":[\"groupA\",\"groupB\"],\"age\":30,\"name\":\"Neo\"}" decode "{\"group\":[\"groupA\",\"groupB\"],\"age\":30,\"name\":\"Neo\"}" :: Maybe Person -- --> Just (Person {name = "Neo", age = 30, group = ["groupA","groupB"]}) -- リストはもともとToJSONとFromJSONのインスタンスなので、 encode [p1, p2, p3] -- --> "[{\"group\":[\"groupA\",\"groupB\"],\"age\":30,\"name\":\"Neo\"},{\"group\":[\"groupA\"],\"age\":40,\"name\":\"Morpheus\"},{\"group\":[],\"age\":30,\"name\":\"Trinity\"}]" decode "[{\"group\":[\"groupA\",\"groupB\"],\"age\":30,\"name\":\"Neo\"},{\"group\":[\"groupA\"],\"age\":40,\"name\":\"Morpheus\"},{\"group\":[],\"age\":30,\"name\":\"Trinity\"}]" :: Maybe [Person] -- --> Just [Person {name = "Neo", age = 30, group = ["groupA","groupB"]},Person {name = "Morpheus", age = 40, group = ["groupA"]},Person {name = "Trinity", age = 30, group = []}] </code></pre> <p>※ 彼らの年齢は間違っているかもしれません。</p> <p>Haskellのレコードを使うときは、以下のように型名のプレフィックスをフィールド名につけて定義することが多いかもしれません(あるいはlensなどを使うのかもしれませんが)。それでも、JSONのフィールド名はシンプルに"title", "created"としておきたいですよね。<br /> こういう場合、<a target="_blank" rel="nofollow noopener" href="http://hackage.haskell.org/package/aeson-1.4.2.0/docs/Data-Aeson.html#v:defaultOptions"><code>defaultOptions</code></a>というのを使います。<code>fieldLabelModifier</code>に、フィールド名の変換関数を定義しておけば、エンコード時に適用してくれます。ここでは、プレフィックス("movie"の5文字)を取り除き、かつ小文字にするというルールにしました。</p> <pre><code class="haskell">:{ data Movie = Movie { movieTitle :: String , movieCreated :: Int } deriving (Show, Generic) :} movieOptions = defaultOptions {fieldLabelModifier = map toLower . drop 5} instance ToJSON Movie where toEncoding = genericToEncoding movieOptions instance FromJSON Movie where parseJSON = genericParseJSON movieOptions movie = Movie "The Matrix" 1999 encode movie -- --> "{\"title\":\"The Matrix\",\"created\":1999}" decode "{\"title\":\"The Matrix\",\"created\":1999}" :: Maybe Movie -- --> Just (Movie {movieTitle = "The Matrix", movieCreated = 1999}) </code></pre> <h3 id="エンコーダー・デコーダーを自前で定義"><a href="#%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%80%E3%83%BC%E3%83%BB%E3%83%87%E3%82%B3%E3%83%BC%E3%83%80%E3%83%BC%E3%82%92%E8%87%AA%E5%89%8D%E3%81%A7%E5%AE%9A%E7%BE%A9">エンコーダー・デコーダーを自前で定義</a></h3> <p>では、代数データ型の場合はどうでしょうか。基本的には以下のように自動導出できます。</p> <pre><code class="haskell">data Size = Small|Medium|Large deriving (Show, Generic) instance ToJSON Size instance FromJSON Size encode [Small, Medium, Large] -- --> "[\"Small\",\"Medium\",\"Large\"]" decode "[\"Small\",\"Medium\",\"Large\"]" :: Maybe [Size] -- --> Just [Small,Medium,Large] </code></pre> <p>ただ、JSONデータではキャメルケースでなく<code>small</code>, <code>medium</code>, <code>large</code>のように小文字で表現したいかもしれません。その場合は、以下のように自分で定義する必要があります。<code>ToJSON</code>のインスタンスにするには、<code>toJSON</code>か、<code>toEncoding</code>を自分で定義します。以下のような型です。</p> <pre><code class="haskell">toJSON :: a -> Value toEncoding :: a -> Encoding </code></pre> <p><code>toJSON</code>は任意の型をData.Aesonの<code>Value</code>型にします。したがって、<code>toJSON</code>のみを定義した場合は、<code>toEncoding</code>が自動導出されます。JSONにエンコードするときは、一旦<code>Value</code>に変換され、その後JSONの文字列に変換されます。<br /> 一方、<code>toEncoding</code>を直接定義することもできて、その場合は、<code>Value</code>を経由せず直接エンコードされることになります。ここには<a target="_blank" rel="nofollow noopener" href="http://hackage.haskell.org/package/aeson-1.4.2.0/docs/Data-Aeson.html#g:7">歴史的経緯</a>があるようです。</p> <pre><code class="haskell">-- toJSONを定義する場合 data Size = Small|Medium|Large deriving (Show, Generic) instance ToJSON Size where toJSON Small = String "small" toJSON Medium = String "medium" toJSON Large = String "large" encode [Small, Medium, Large] -- --> "[\"small\",\"medium\",\"large\"]" </code></pre> <pre><code class="haskell">-- toEncodingを定義する場合 data Size = Small|Medium|Large deriving (Show, Generic) instance ToJSON Size where toEncoding Small = DAE.string "small" toEncoding Medium = DAE.string "medium" toEncoding Large = DAE.string "large" encode [Small, Medium, Large] -- --> "[\"small\",\"medium\",\"large\"]" </code></pre> <p><code>FromJSON</code>のインスタンスについては、<code>parseJSON</code>を定義します。こちらは少し面倒です。以下のような型をしています。</p> <pre><code class="haskell">parseJSON :: Value -> Parser a </code></pre> <pre><code class="haskell">-- withTextの第一引数("Size")は、どんな文字列でも良いみたい。 -- なんのための引数なのかまだわかっていない。 instance FromJSON Size where parseJSON = withText "Size" $ \t -> fromString (TL.unpack (TL.fromStrict t)) where fromString :: String -> Parser Size fromString "small" = pure Small fromString "medium" = pure Medium fromString "large" = pure Large fromString _ = empty decode "[\"small\",\"medium\",\"large\"]" :: Maybe [Size] -- --> Just [Small,Medium,Large] </code></pre> <p>これで、<code>Size</code>データを使ったレコードもエンコード・デコードすることができます。</p> <pre><code class="haskell">:{ data Tomato = Tomato { size :: Size , brand :: String } deriving (Show, Generic) :} instance ToJSON Tomato instance FromJSON Tomato mini = Tomato Small "famous brand name" encode mini -- --> "{\"size\":\"small\",\"brand\":\"famous brand name\"}" decode "{\"size\":\"small\",\"brand\":\"famous brand name\"}" -- --> Just (Tomato {size = Small, brand = "famous brand name"}) </code></pre> <h3 id="多相型"><a href="#%E5%A4%9A%E7%9B%B8%E5%9E%8B">多相型</a></h3> <p>このようなデータ型でもこれまでと同じように<code>ToJSON</code>・<code>FromJSON</code>のインスタンスにすることができます。自動導出の場合は、以下の例のようにエンコード時に<code>Wrap</code>が外れます。</p> <pre><code class="haskell">data Wrap a = Wrap a deriving (Show, Generic) instance ToJSON a => ToJSON (Wrap a) instance FromJSON a => FromJSON (Wrap a) encode $ Wrap 1 -- --> "1" encode $ Wrap "wrapped" -- --> "\"wrapped\"" decode "1" :: Maybe (Wrap Int) -- --> Just (Wrap 1) decode "\"wrapped\"" :: Maybe (Wrap String) -- --> Just (Wrap "wrapped") </code></pre> <p>今度は<code>Point a</code>型で考えます。自動導出で問題ない場合は良いのですが、自分で定義しようとした時に、少し悩みました。<code>toJSON</code>も<code>parseJSON</code>も、<code>a</code>の部分の値をエンコード・デコードする処理を自分で定義してしまうと型を決めなくてはなりません。そうすると<code>a</code>のままではできず、<code>Point Int</code>などの具体的な型で定義せざるを得ません。そこで、<code>a</code>の部分については、自分で定義せず、<code>toJSON</code>や<code>parseJSON</code>をそのまま使います。もともと<code>a</code>はそれらのインスタンスであることを仮定しているので、自分で定義する必要がありません。以下のように書くと、型引数をとる型についても定義できます。</p> <pre><code>data Point a = Point a a deriving (Show, Generic) instance ToJSON a => ToJSON (Point a) where toJSON (Point x y) = Object $ HMS.fromList [("point", toJSON [x,y])] :{ instance FromJSON a => FromJSON (Point a) where parseJSON (Object o) = case HML.lookup "point" o of Just arr -> let pts = parseJSON arr :: FromJSON b => Parser (b, b) in Point <$> (fst <$> pts) <*> (snd <$> pts) Nothing -> empty :} pi = Point 1 2 ps = Point "one" "two" encode pi -- --> "{\"point\":[1,2]}" encode ps -- --> "{\"point\":[\"one\",\"two\"]}" decode "{\"point\":[1,2]}" :: Maybe (Point Int) -- --> Just (Point 1 2) decode "{\"point\":[\"one\",\"two\"]}" :: Maybe (Point String) -- --> Just (Point "one" "two") </code></pre> <h3 id="少し複雑な代数データ型"><a href="#%E5%B0%91%E3%81%97%E8%A4%87%E9%9B%91%E3%81%AA%E4%BB%A3%E6%95%B0%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B">少し複雑な代数データ型</a></h3> <pre><code class="haskell">data Gender = Female|Male deriving (Show, Generic) data Property = Name String|Age Int|Gender Gender deriving (Show, Generic) instance ToJSON Gender instance FromJSON Gender instance ToJSON Property instance FromJSON Property props = [Name "Neo", Age 30, Gender Male] encode props -- --> "[{\"tag\":\"Name\",\"contents\":\"Neo\"},{\"tag\":\"Age\",\"contents\":30},{\"tag\":\"Gender\",\"contents\":\"Male\"}]" decode "[{\"tag\":\"Name\",\"contents\":\"Neo\"},{\"tag\":\"Age\",\"contents\":30},{\"tag\":\"Gender\",\"contents\":\"Male\"}]" :: Maybe [Property] -- --> Just [Name "Neo",Age 30,Gender Male] </code></pre> <p>このような型でもすべて自動導出できました。しかし、JSONのフォーマットは微妙ですね。以下のようになって欲しいです。(レコード型で定義すれば良いだけの話なのですが、今は例ということで。。)</p> <pre><code class="json">{ "name": "Neo", "age": 30, "gender": "male" } </code></pre> <p>自前で定義しましょう。名前を変えるため別の例にしますが、データ構造はほぼ一緒です。</p> <pre><code class="haskell">data CarType = Coupe|SUV deriving (Show, Generic) data Car = Model String|Release Int|CarType CarType deriving (Show, Generic) instance ToJSON CarType where toJSON Coupe = String "coupe" toJSON SUV = String "suv" instance FromJSON CarType where parseJSON = withText "CarType" $ \t -> fromString (T.unpack t) where fromString :: String -> Parser CarType fromString "coupe" = pure Coupe fromString "suv" = pure SUV fromString _ = empty instance ToJSON Car where toJSON (Model s) = object [("model", toJSON s)] toJSON (Release i) = object [("release", toJSON i)] toJSON (CarType c) = object [("car_type", toJSON c)] instance FromJSON Car where parseJSON (Object o) = case HML.lookup "model" o of Just (String s) -> pure (Model $ T.unpack s) _ -> case HML.lookup "release" o of Just i -> Release <$> (parseJSON i :: Parser Int) _ -> case HML.lookup "car_type" o of Just t -> CarType <$> (parseJSON t :: Parser CarType) myCar = [Model "Land Cruiser", Release 1954, CarType SUV] encode myCar -- --> "[{\"model\":\"Land Cruiser\"},{\"release\":1954},{\"car_type\":\"suv\"}]" decode "[{\"model\":\"Land Cruiser\"},{\"release\":1954},{\"car_type\":\"suv\"}]" :: Maybe [Car] -- --> Just [Model "Land Cruiser",Release 1954,CarType SUV] </code></pre> <p>これで、望みのJSON形式でエンコード・デコードできるようになりました。</p> <h3 id="Either型"><a href="#Either%E5%9E%8B">Either型</a></h3> <p><code>Either</code>型の場合はどうなるでしょうか。わからないのでまずは自動導出して見ましょう。</p> <pre><code class="haskell">:{ data Filter = Filter { value :: Either String [String] } deriving (Show, Generic) :} instance ToJSON Filter instance FromJSON Filter filterL = Filter (Left "eq") filterR = Filter (Right ["eq", "gt"]) encode filterL -- --> "{\"value\":{\"Left\":\"eq\"<span>}</span><span>}</span>" encode filterR -- --> "{\"value\":{\"Right\":[\"eq\",\"gt\"]<span>}</span><span>}</span>" decode "{\"value\":{\"Left\":\"eq\"<span>}</span><span>}</span>" :: Maybe (Filter) -- --> Just (Filter {value = Left "eq"}) decode "{\"value\":{\"Right\":[\"eq\",\"gt\"]<span>}</span><span>}</span>" :: Maybe (Filter) -- --> Just (Filter {value = Right ["eq","gt"]}) </code></pre> <p>一応できました。しかし、JSONデータの中に"Left"や"Right"が入ってしまって冗長です。この場合は、以下のようになって欲しいですね。</p> <pre><code class="json"># Leftの場合 {"value": "eq"} # Rightの場合 {"value": ["eq", "gt"]} </code></pre> <p>以下のように定義しましょう。別の例にするのが面倒なので、同じデータを再定義します。</p> <pre><code>:{ data Filter = Filter { value :: Either String [String] } deriving (Show, Generic) :} instance ToJSON Filter where toJSON (Filter (Left s)) = object [("value", toJSON s)] toJSON (Filter (Right ss)) = object [("value", toJSON ss)] -- Arrayの時に、判定できていないのが難点 -- でも、失敗した時はちゃんとNothingを返してくれるのでとりあえず良いか instance FromJSON Filter where parseJSON (Object o) = case HML.lookup "value" o of Just (String s) -> pure (Filter (Left $ T.unpack s)) Just arr -> (Filter . Right) <$> (parseJSON arr :: Parser [String]) _ -> empty filterL = Filter (Left "eq") filterR = Filter (Right ["eq", "gt"]) encode filterL -- --> "{\"value\":\"eq\"}" encode filterR -- --> "{\"value\":[\"eq\",\"gt\"]}" decode "{\"value\":\"eq\"}" :: Maybe Filter -- --> Just (Filter {value = Left "eq"}) decode "{\"value\":[\"eq\",\"gt\"]}" :: Maybe Filter -- --> Just (Filter {value = Right ["eq","gt"]}) </code></pre> <p><code>Either</code>型の場合もこのように定義することで自由にJSONとの変換ができます。ちなみに上記の例のようにではなく、<code>Either String a</code>としてエラーメッセージ付きの<code>Maybe</code>のような扱いをしたい場合は、<a target="_blank" rel="nofollow noopener" href="https://www.stackage.org/haddock/lts-13.6/aeson-1.4.2.0/Data-Aeson-Types.html#v:parseEither">専用の関数がすでに用意されている</a>ようです。</p> <h3 id="GADT"><a href="#GADT">GADT</a></h3> <p>この場合は、<code>Field String</code>と<code>Field Bool</code>の場合でそれぞれ定義するしかなさそうです。もしまとめて定義する方法があれば誰か教えてください。</p> <pre><code>data Field typ = (typ ~ String) => Title | (typ ~ Bool) => Done instance Show (Field typ) where show Title = "Title" show Done = "Done" instance ToJSON (Field typ) where toJSON Title = String "title" toJSON Done = String "done" :{ instance FromJSON (Field String) where parseJSON = withText "Field" $ \v -> case v of "title" -> pure Title _ -> empty instance FromJSON (Field Bool) where parseJSON = withText "Field" $ \v -> case v of "done" -> pure Done _ -> empty :} encode Title -- --> "\"title\"" encode Done -- --> "\"done\"" decode "\"title\"" :: Maybe (Field String) -- --> Just Title decode "\"done\"" :: Maybe (Field Bool) -- --> Just Done </code></pre> <p>で、この<code>Field</code>を使って、以下のようなレコードを定義します。</p> <pre><code>:{ data Filter = forall typ. Show typ => Filter { field :: Field typ , value :: typ } :} </code></pre> <p>なぜこんなデータ型を考えているかというと、実は<a target="_blank" rel="nofollow noopener" href="http://hackage.haskell.org/package/persistent">persistent</a>のクラスに、<a target="_blank" rel="nofollow noopener" href="https://github.com/yesodweb/persistent/blob/master/persistent/Database/Persist/Class/PersistEntity.hs#L135">Filter</a>っていうのがあって、これを<code>FromJSON</code>のインスタンスにしたかったのです。このデータをもう少し簡易にしたものが、上で定義した<code>Filter</code>です。ここでの肝は、型引数の<code>typ</code>が型としては明示的に現れていない点です。つまり<code>Filter Title "some title"</code>も、<code>Filter Done True</code>も型としては単に<code>Filter</code>型ですが、前者の<code>typ</code>は<code>String</code>で後者は<code>Bool</code>です。</p> <pre><code>-- どちらも単にFilter型 :t Filter Title "some title" -- --> Filter Title "some title" :: Filter :t Filter Done True -- --> Filter Done True :: Filter </code></pre> <p>このようなデータ型の場合、先ほど<code>Field</code>の<code>FromJSON</code>を<code>Field String</code>と<code>Field Bool</code>でそれぞれ定義した時のようなことができません。<code>field</code>の値が<code>Title</code>なら<code>typ</code>は<code>String</code>、<code>Done</code>なら<code>Bool</code>というような場合分けをする必要があります。色々試行錯誤してようやくできた定義が以下です。<code>mayTitle</code>と<code>mayDone</code>の場合分けのところは、この2つデータの型が違うので、それぞれ別の変数にせざるを得ませんでした。そのため<code>mayField</code>を必ず2回評価することになり、無駄の多い実装になってしまいました。</p> <pre><code>instance FromJSON Filter where parseJSON (Object o) = do let mayField = HMS.lookup "field" o let mayValue = HMS.lookup "value" o let mayTitle = case mayField of Just (String "title") -> Just Title _ -> Nothing let mayDone = case mayField of Just (String "done") -> Just Done _ -> Nothing case mayValue of Just value -> do case mayTitle of Just title -> Filter title <$> (parseJSON value :: Parser String) Nothing -> case mayDone of Just done -> Filter done <$> (parseJSON value :: Parser Bool) Nothing -> empty Nothing -> empty -- Filter Title "some title" case decode "{\"field\":\"title\", \"value\":\"some title\"}" :: Maybe Filter of Just (Filter field value) -> print value Nothing -> print Nothing -- Filter Done True case decode "{\"field\":\"done\", \"value\":true}" :: Maybe Filter of Just (Filter field value) -> print value Nothing -> print Nothing </code></pre> reouno tag:crieit.net,2005:PublicArticle/15008 2019-05-21T16:05:34+09:00 2019-05-23T12:13:09+09:00 https://crieit.net/posts/Laravel5-8-FormRequest-JSON-API Laravel5.8現在FormRequestのバリデーション結果をJSON APIで返す <p>LaravelはFormRequestという仕組みでバリデーションを分離できる。APIの場合はコードを変えずにバリデーション結果のJSONを取得することができるのでその方法。</p> <p>ちなみにAPIでない通常のWebページの場合は<code>$errors</code>オブジェクトを使ってページ上にバリデーション結果を表示することができる。これのために作ったFormRequestをそのままAPIでも利用することができる。</p> <h2 id="やりかた"><a href="#%E3%82%84%E3%82%8A%E3%81%8B%E3%81%9F">やりかた</a></h2> <p>リクエストする際に<code>Accept: application/json</code>ヘッダーを送るだけ。これでJSONで取得できる。</p> <h2 id="以前のやりかた"><a href="#%E4%BB%A5%E5%89%8D%E3%81%AE%E3%82%84%E3%82%8A%E3%81%8B%E3%81%9F">以前のやりかた</a></h2> <p>ちなみに以前はこんな感じでFormRequestのメソッドをオーバーライドして対応していたっぽい。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/junsan50/items/ec7f810decd3b82d3d76">【Laravel5】FormRequestのバリデーション結果をJSON APIで返す</a></p> <p>もしかしたらこの時もAcceptヘッダーを送ればできたのかもしれないが。何にしろ、リクエスト側の仕様の問題でAcceptヘッダーを正しく送ることができないのであればこの方法でやるしかなさそう。</p> だら@Crieit開発者