IoTおよびメーカー向けのSVGWebページコンポーネント(パート2)
公開: 2022-03-10そのため、必要に応じてパネルをロードすることで反応するように作成されたSVGアイコンのメニューを動的にロードする方法はすでにありますが、アイコンは実際のコンポーネントではありませんでした。 アイコンごとにSVGを取り込み、それをVueアプリケーションに渡すという簡単なトリックを使用することができました。 アイコンのリストを生成するのは簡単で、データのわずかな違いを除いて、各アイコンは同じように反応しました。 データの違いにより、アイコンのボタンクリックのハンドラーがパネルの名前を渡すことができるように、パネルの名前を各アイコンにバインドすることが可能になりました。
パネルがVueコンポーネントの形式でロードされる場合、パネルとそのコンポーネント、テンプレート、JavaScriptなどに関するすべてがロードされる必要があります。 したがって、パネルのロードを管理するだけの作業は、このディスカッションでこれまでに遭遇した作業よりも大きくなります。
非同期ロード用のフックを提供するVueの方法を見てみましょう。 次のスニペットはVueガイドからのものです。
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // Pass the component definition to the resolve callback resolve({ template: '<div>I am async!</div>' }) }, 1000) })
このガイドでは、setTimeout関数がVueコンポーネントとの同期性を使用する方法の例であると説明しています。 以前はVue.component
の2番目のパラメーターとしてオブジェクトがあったところに、ファクトリ関数と呼ばれる関数があることに注意してください。 resolve
コールバック内にはコンポーネント定義があり、これは以前はVue.component
の2番目のパラメーターでした。
それで、私はそれが私に意味をなす前にこの例をしばらく見つめなければなりませんでした。 これが私にぴったりの別の例です:
Vue.component('async-example', function (resolve, reject) { // Vue will call this function and promise itself to handle // it when it gets back with data. // this function can then call a promising object loader // here the 'loader' function is some abstract function. // Most likely the application will use 'fetch' // but it could be something else. loader('/my/resource/on/server.json'). then(function (JSON_data) { var object = transformJSONToJSObject(JSON_data); resolve(object) }).catch( (error) => { handle it } );
このフォームを回避するためのより一般的な関数を作成するために行うのは正しいことのようです。
function componentLoader(c_name,resource_url) { Vue.component(c_name, function (resolve, reject) { loader(resource_url). then(function (JSON_data) { var object = transformJSONToJSObject(JSON_data); resolve(object) }).catch( (error) => { handle it } ); }
したがって、一般に、コンポーネントをロードするには、次のような行が必要です。
componentLoader('ThermoPanel','./JSON/thermo-panel.json');
では、ロードされているJSONは何ですか? コンポーネントに関するすべてを含めることができます。 この場合、パネルコンポーネントとして、温度計、マシンスイッチ、スライダー、ゲージなどを含めることができます。 コンポーネントパーツをWebページに保持する方が良いように見えましたが、実際には、以前に作成した「thermo-panel」や他の同様に構築されたパネルの長い例にあるサブコンポーネントフィールドを使用する方が適切な場合があります。 JSONには、完全なパネル構造が含まれます。
ただし、読者がtransformJSONToJSObject
への関数呼び出しが含まれていることに気付いた場合、JSONは、トランスポートを容易にし、サーバーが定義を処理しやすくするために何らかの方法でコーディングされている可能性があることを理解します。 結局のところ、定義には完全なSVGテンプレート、関数定義、およびその他のJavaScript式が含まれます。 また、JSONオブジェクトには、パネル定義以外のものが含まれている場合があります。これは、一部の情報が単に簿記や検証に役立つ場合があるためです。 したがって、受領時にオブジェクトの処理が行われることが期待できます。
符号化に関しては、サーバーから入ってくるデータはいくつかの方法で符号化することができます。 おそらくそれは単にURLエンコードされるでしょう。 または、より安全に、暗号化される可能性があります。 この説明では、URLエンコードを使用できます。
Vueアプリケーションの作成に使用できるツールのいくつかは、間違いなくJSON変換を処理します。 しかし、この議論はこれまでのところコマンドラインツールの使用を避けてきました。 CDNを参照するために1つのスクリプトタグのみを使用して、最小限のリソースでVueも使用したため、この省略はそれほど悪くありません。 ただし、特にプロジェクトを整理するために、コマンドラインツールを調べることをお勧めします。
JSONがページに到着すると、コンポーネントがサブコンポーネントで完全にアセンブルされているため、パーツをフェッチするためにこれ以上の作業を行う必要はありません。 この説明の残りの部分では、すべてのコンポーネントが完全に定義されていると想定できます。 ただし、完全なコンポーネント階層を組み立てるには、ある時点でコマンドラインツールが必要になります。
SVG編集プロセスにもいくつかの作業が必要になります。 SVG編集プロセスにより、設計者はパネルとその上のすべてのコンポーネントを描画できます。 ただし、各サブコンポーネントを識別するか、グループで呼び出すか、プレースホルダーを指定する必要があります。 図面を使用するためのアプローチでは、Vueコンポーネントタグがグループまたはグラフィック要素を置き換えることができるように、SVGをある程度処理する必要があります。 このようにして、アーティストのレンダリングをテンプレートにすることができます。 また、描画されたサブコンポーネントは、Vueサブコンポーネントのテンプレートに分解する必要があります。
この種の節約は、ほとんどのJavaScriptフレームワークのワークフローに反しています。 フレームワークは、ページの組み立てに関するものです。 しかし、編集や描画を行うと、アーティストがすでに組み立てたものになります。 実際には、編集の結果は、フレームワークコンポーネント定義に直接対応するテキストファイルを提供しません。
編集プロセスの詳細については、他のディスカッションで検討することができます。 それにはたくさんあります。 しかし、今のところ、階層コンポーネントをロードしてそれらを生き生きとさせるために必要なツールがあります。
怠惰なアプリケーション
IoTパネルの構築には、検索に応答する選択バーがすでにあります。 また、必要なときにコンポーネントをロードする方法があります。 これらの部品を接続するだけです。 そして最後に、パネルが表示され、表示されたときに機能し始めることを確認する必要があります。
上記の非同期コードによって行われるパネルの遅延読み込みは、アイデアのスケッチを提供します。 しかし、ありがたいことに、すべての種類のコンポーネントを確実にロードできるようにする方法を見つけるために実験を行った人もいます。 さまざまなタイプの新しいコンポーネントでVueアプリを更新する方法を示す1つのcodepenエントリがあります。 これは、ページの指定された部分をさまざまなタイプのパネルで更新するために必要なメカニズムです。
さまざまな種類のパネルを追加する機能と、それらの定義をロードする簡単なメカニズムにより、ついにパネル検索ページを作成できます。
Vueアプリがコンポーネントを動的に配置できるようにするためにページに必要なHTMLは次のとおりです。
<template v-for="(panel, index) in panelList"> <component :is="panel" :key="panel.name"></component> </template>
component
タグはVueメタタグです。 動的コンポーネントのリファレンスを参照してください。 この場合のcomponent
タグに使用されるプロパティ、特殊属性は、isとkeyです。 is
属性は、動的コンポーネントに存在します。 そして、 key
は、新しい子供たちが互いに異なるアイデンティティを持つことを保証し、Vueが何を描くかを決定するのに役立ちます。
「同じ共通の親の子供は、一意のキーを持っている必要があります。 キーが重複すると、レンダリングエラーが発生します。」
template
タグは、アプリケーションのpanelList
データフィールドで提供されるコンポーネントをループします。
そのため、アイコンアプリのアプリケーションレベルのVue定義から始めて、データ要素にpanelListを含めるように変更を加えることができます。 (これをpanelAppと呼びましょう)。
var panelApp = new Vue({ el: '#PanelApp', data: { iconList: [ // Where is the data? Still on the server. ], panelList: [ ], queryToken : "Thermo Batches" // picked a name for demo }, methods : { goGetPanel: function (pname) { // var url = panelURL(pname); // this is custom to the site. fetch(url).then((response) => { // this is now browser native response.text().then((text) => { var newData = decodeURIComponent(text); eval(pHat); // widgdef = object def, must be assignment pHat = widgdef; var pnameHat = pname + pcount++; pHat.name = pnameHat; // this is needed for the key this.panelList.push(pHat); // now it's there. }).catch( error => { /* handle it */ }); } } });
パネルに追加するだけでなく、 goGetPanel
は、データベースまたは他のストアからコンポーネント定義を取得するために必要な形式になりました。 サーバー側は、JavaScriptコードを正しい形式で配信することに注意する必要があります。 オブジェクトがサーバーから送信されたように見えることについては、すでに見てきました。 これは、 Vue.component
のパラメーターとして使用されるオブジェクトの種類です。
これは、検索結果としてメニューを提供するVueアプリの完全な本体であり、ユーザーがアイコンをクリックしたときにサーバーからフェッチされたパネルを配置する場所です。
<div> <!-- Recognize the name from the Vue doc --> <div> <h2 itemprop="name">Request MCU Groups</h2> <p itemprop="description">These are groups satistfying this query: {{queryToken}}.</p> <button>Find All</button> <button>Find 5 Point</button> <button>Find 6 Point</button> </div> <!-- Here is a Vue loop for generating a lit --> <div class="entryart"> <button v-for="iconEntry in iconList" @click="goGetPanel(iconEntry.name)" > <div v-html="iconEntry.icon"> </div> </button> </div> <div class="entryart" > <template v-for="(panel, index) in panelList"> <component :is="panel" :key="panel.name" :ref="panel.name" ></component> </template> </div> </div>
最後のdiv
で、 component
タグにパネル名にバインドされたref
パラメータが含まれるようになりました。 refパラメーターを使用すると、Vueアプリはデータで更新するコンポーネントを識別し、コンポーネントを分離しておくことができます。 ref
パラメーターを使用すると、アプリケーションは動的にロードされた新しいコンポーネントにアクセスすることもできます。
パネルアプリの1つのテストバージョンには、次のインターバルハンドラーがあります。
setInterval(() => { var refall = panelApp.$refs; // all named children that panels for ( var pname in refall ) { // in an object var pdata = refall[pname][0]; // off Vue translation, but it's there. pdata.temp1 = Math.round(Math.random()*100); // make thermos jump around. pdata.temp2 = Math.round(Math.random()*100); } },2000)
コードは、温度計をランダムに変更する小さなアニメーションを提供します。 各パネルには2つの温度計があり、アプリを使用すると、ユーザーはパネルを追加し続けることができます。 (最終バージョンでは、一部のパネルを破棄する必要があります。)refは、 component
のrefs
情報を指定してVueが作成するフィールドであるpanelApp.$refs
を使用してアクセスされます。
つまり、これはランダムにジャンプする温度計が1つのスナップショットでどのように見えるかです。
パネルをIoTデバイスに接続する
したがって、コードの最後の部分は、2秒ごとにランダムな値で温度計を更新するsetInterval
テストです。 しかし、私たちがやりたいのは、実際のマシンから実際のデータを読み込むことです。 そのためには、何らかのコミュニケーションが必要になります。
さまざまな方法があります。 ただし、pub / subメッセージシステムであるMQTTを使用してみましょう。 SPWAは、いつでもデバイスからのメッセージをサブスクライブできます。 これらのメッセージを取得すると、SPWAは、メッセージで識別されたデバイスにマップされたパネルの適切なデータハンドラーに各メッセージを送信できます。
したがって、基本的に必要なのは、 setInterval
を応答ハンドラーに置き換えることです。 そして、それは1つのパネルになります。 パネルがロードされるときに、パネルをハンドラーにマップすることをお勧めします。 そして、正しいマッピングが提供されるかどうかを確認するのはWebサーバー次第です。
WebサーバーとSPWAでページを操作できるようになると、Webサーバーはページとデバイス間のメッセージングを処理する必要がなくなります。 MQTTプロトコルは、pub / subを処理するルーティングサーバーを指定します。 多くのMQTTサーバーが作成されました。 それらのいくつかはオープンソースです。 非常に人気のあるものの1つはMosquitoであり、Node.js上で開発されたものがいくつかあります。
ページのプロセスは簡単です。 SPWAはトピックをサブスクライブします。 トピックの適切なバージョンの1つは、MACアドレスやシリアル番号などのMCUの識別子です。 または、SPWAはすべての温度測定値をサブスクライブできます。 ただし、その場合、ページはすべてのデバイスからのメッセージをフィルタリングする作業を行う必要があります。 MQTTでの公開は、基本的にブロードキャストまたはマルチキャストです。
SPWAがMQTTとどのようにインターフェースするかを見てみましょう。
SPWAでのMQTTの初期化
選択できるクライアントライブラリはいくつかあります。 たとえば、1つはMQTT.jsです。 もう一つは日食パホです。 もちろんもっとあります。 CDNが保存されているバージョンがあるので、EclipsePahoを使用してみましょう。 ページに次の行を追加するだけです。
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
MQTTクライアントは、メッセージを送受信する前にサーバーに接続する必要があります。 そのため、接続を設定する行もJavaScriptに含める必要があります。 接続管理とメッセージ受信のためのクライアントと応答を設定する関数MQTTinitialize
を追加できます。
var messagesReady = false; var mqttClient = null; function MQTTinitialize() { mqttClient = new Paho.MQTT.Client(MQTTHostname, Number(MQTTPort), "clientId"); mqttClient.onMessageArrived = onMessageArrived; // connect the client mqttClient.connect({ onSuccess: () => { messagesReady = true; } }); // set callback handlers mqttClient.onConnectionLost = (response) => { // messagesReady = false; // if (response.errorCode !== 0) { console.log("onConnectionLost:"+response.errorMessage); } setTimeout(() => { MQTTinitialize() },1000); // try again in a second }; }
サブスクリプションの設定
接続の準備ができたら、クライアントはメッセージチャネルにサブスクライブしたり、メッセージを送信したりできます。パネルをMQTTパスウェイに接続するために必要な作業のほとんどは、ほんの数個のルーチンで実行できます。
パネルSPWAの場合、サブスクリプションの瞬間を使用して、パネルとトピック、MCU識別子の間の関連付けを確立できます。
function panelSubcription(topic,panel) { gTopicToPanel[topic] = panel; gPanelToTopic[panel] = topic; mqttClient.subscribe(topic); }
MCUがそのトピックについて公開している場合、SPWAはメッセージを受信します。 ここで、Pahoメッセージが解凍されます。 そして、メッセージはアプリケーションの仕組みに渡されます。
function onMessageArrived(pmessage) { // var topic = pmessage.destinationName; var message = pmessage.payloadString; // var panel = gTopicToPanel[topic]; deliverToPanel(panel,message); }
したがって、今やらなければならないのは、以前のインターバルハンドラーにいくらか似ているはずのdeliverToPanel
を作成することだけです。 ただし、パネルは明確に識別されており、特定のメッセージで送信されたキー付きデータのみが更新される可能性があります。
function deliverToPanel(panel,message) { var refall = panelApp.$refs; // all named children that panels var pdata = refall[panel][0]; // off Vue translation, but it's there. var MCU_updates = JSON.parse(message); for ( var ky in MCU_updates ) { pdata[ky] = MCU_updates[ky] } }
このdeliverToPanel
関数は、アニメーション用の任意の数のデータポイントを持つ任意のパネル定義を可能にするのに十分抽象的です。
メッセージの送信
MCUとSPWA間のアプリケーションループを完了するために、メッセージを送信する関数を定義します。
function sendPanelMessage(panel,message) { var topic = gPanelToTopic[panel]; var pmessage = new Paho.MQTT.Message(message); pmessage.destinationName = topic; mqttClient.send(pmessage); }
sendPanelMessage
関数は、SPWAがサブスクライブしているのと同じトピックパスウェイでメッセージを送信するだけです。
アイコンボタンをMCUの単一クラスターにいくつかのパネルを取り込む役割を担うようにする予定であるため、処理するパネルは複数あります。 ただし、各パネルは1つのMCUに対応しているため、1対1のマッピングがあり、マップとその逆に2つのJavaScriptマップを使用できます。
では、いつメッセージを送信するのでしょうか。 通常、パネルアプリケーションは、MCUの状態を変更するときにメッセージを送信します。
ビュー(Vue)の状態をデバイスと同期させる
Vueの優れた点の1つは、フィールドの編集、ボタンのクリック、スライダーの使用などを行うユーザーのアクティビティとデータモデルの同期を非常に簡単に維持できることです。ボタンとフィールドの変更が確実に行われるようになります。コンポーネントのデータフィールドにすぐに反映されます。
ただし、変更が発生するとすぐに、変更によってMCUへのメッセージが送信されるようにする必要があります。 そのため、Vueが管理する可能性のあるインターフェイスイベントを利用しようとしています。 私たちはそのようなイベントに対応しようとしますが、それはVueデータモデルが現在の値で準備ができた後でのみです。
私は別の種類のパネルを作成しました。これはかなり芸術的な外観のボタンを備えたものです(おそらくジャクソン・ポロックに触発されたものです)。 そして、クリックすると状態が含まれているパネルに報告されるようなものに変えようとしました。 それはそれほど単純なプロセスではありませんでした。
私を失望させたのは、SVGの管理における奇妙な点のいくつかを忘れていたことです。 最初に、CSSスタイルのdisplay
フィールドが「なし」または「何か」になるようにスタイル文字列を変更しようとしました。 ただし、ブラウザがスタイル文字列を書き直すことはありません。 でも、面倒だったので、CSSクラスを変えてみました。 それも効果がありませんでした。 しかし、私たちのほとんどが古いHTML(おそらくバージョン1.0)から思い出すvisibility
属性がありますが、それはSVGでは非常に最新のものです。 そして、それはうまくいきます。 私がしなければならなかったのは、ボタンクリックイベントを伝播させることだけでした。
Vueは、親から子への一方向に伝播するプロパティを設計しました。 したがって、アプリケーションまたはパネルでデータを変更するには、変更イベントを親に送信する必要があります。 次に、データを変更できます。 ボタンを制御するデータ要素の変更により、Vueはプロパティを更新し、状態を示すために選択したSVG要素の可視性に影響を与えます。 次に例を示します。
波状のボタンパネルの各インスタンスは独立しています。 したがって、一部はオンで、一部はオフです。
このSVGのスニペットには、奇妙に見える黄色のインジケーターが含まれています。
<path :visibility="stateView" d="m -36.544616,12.266886 c 19.953088,17.062165 5.07961,-19.8251069 5.317463,8.531597 0.237853,28.356704 13.440044,-8.847959 -3.230451,10.779678 -16.670496,19.627638 14.254699,-2.017715 -11.652451,3.586456 -25.90715,5.60417 10.847826,19.889979 -8.095928,-1.546575 -18.943754,-21.436555 -1.177383,14.210702 -4.176821,-12.416207 -2.999438,-26.6269084 -17.110198,8.030902 2.14399,-8.927709 19.254188,-16.9586105 -19.075538,-8.0837048 9.448721,-5.4384245 28.52426,2.6452804 -9.707612,-11.6309807 10.245477,5.4311845 z" transform="translate(78.340803,6.1372042)" />
可視性は、状態ブール値をSVGの文字列にマップする計算変数であるstateView
によって入力されます。
パネルコンポーネント定義テンプレートは次のとおりです。
<script type="text/x-template"> <div> <control-switch :state="bstate" v-on:changed="saveChanges" ></control-switch> <gauge :level="fluidLevel" ></gauge> </div> </script>
そして、これはサブコンポーネントとして子を持つVueパネルのJavaScript定義です。
var widgdef = { data: function () { var currentPanel = { // at the top level, values controlling children bstate : true, fluidLevel : Math.round(Math.random()*100) } // return currentPanel }, template: '#mcu-control-panel-template', methods: { saveChanges: function() { // in real life, there is more specificity this.bstate = !this.bstate relayToMCU(this.name,"button",this.bstate) // to be defined } }, components: { 'control-switch' : { // the odd looking button props: ['state'], template: '#control-switch-template', // for demo it is in the page. computed: { // you saw this in the SVG above. stateView : function() { return ( this.state ) ? "visible" : "hidden" } }, methods : { // the button handler is in the SVG template at the top. stateChange : function () { // can send this.$emit('changed'); // tell the parent. See on the template instance } } }, 'gauge' : { // some other nice bit of SVG props: ['level'], template: '#gauge-template' } } }
これで、パネルに埋め込まれた単一のボタンのメカニズムがレイアウトされました。 そして、何かが起こったことをMCUに伝えるためのフックがなければなりません。 パネルコンポーネントのデータ状態が更新された直後に呼び出す必要があります。 ここで定義しましょう:
function relayToMCU(panel,switchName,bstate) { var message = switchName + ':' + bstate // a on element parameter string. sendPanelMessage(panel,message) }
たった2行のコードでハードウェアへの道のりで状態が変化します。
しかし、これはかなり単純なケースです。 どのスイッチも、世界中のハードウェアへの関数呼び出しと見なすことができます。 したがって、文字列にはスイッチ名と他のいくつかのデータ要素が含まれる場合があります。 したがって、変更を登録するコンポーネントメソッドは、パネル上のすべてのデータセットを収集し、それらを1つのコマンド文字列で送信するために、カスタム処理を行う必要があります。 コマンド文字列でさえ少し単純です。 MCUが非常に小さい場合は、コマンド文字列をコードに変換する必要がある場合があります。 MCUに多くの機能がある場合、コマンド文字列は実際にはJSON構造であるか、パネルがホストするすべてのデータである可能性があります。
この説明では、アイコンパネルのボタンには、フェッチするパネルの名前が含まれています。 それもかなり単純化されるかもしれません。 そのパラメータは、企業のデータベースに格納されている可能性のあるすべてのパネルを表すことができることは理にかなっているようです。 しかし、おそらくそれはいくつかの公式です。 おそらく、パネルに関する情報は、サーバーから受け取ったパネル定義にラップする必要があります。 いずれにせよ、SVGがクリックに適切に応答するようにするなど、特定の頭痛の種が邪魔にならないようになったら、基本を簡単に拡張できます。
結論
このディスカッションでは、IoTデバイスとインターフェイスできるシングルページWebアプリ(SPWA)の実現につながるいくつかの基本的な手順と決定について説明しました。 これで、Webサーバーからパネルを取得してMCUインターフェイスに変換する方法がわかりました。
この議論にはさらに多くのことがあり、その後に続く可能性のある他の議論もかなりあります。 Vueから始めることは考えるべきことの1つです。 しかし、MCUのストーリー全体がありますが、これについては簡単に触れただけです。
特に、通信基盤としてMQTTを選択することにより、相手側のIoTデバイスが何らかの形でMQTTによって支配される可能性があると想定しています。 しかし、それが常に当てはまるとは限りません。 MQTTがシリアルリンクまたはBluetoothを備えたデバイスにアクセスする場合は、ゲートウェイが必要になることがあります。 または、おそらくWebページで必要なのはWebSocketだけです。 それでも、MQTTを例として使用して、Vueがデータの状態をデバイスと同期させたままデータを送受信する方法を示しました。
繰り返しになりますが、ストーリーの一部しかありません。 今回は同期用です。ページはアラートを処理し、何か重大なことが起こった場合にユーザーを煩わせることができるはずだからです。 メッセージが失われることがあります。 したがって、確認応答のメカニズムが必要です。
最後に、Vueは受信時にデータを更新することを非常にエレガントにするというのが私の意見です。 ただし、状態変更の送信はそれほど簡単ではありません。 それは、バニラJavaScriptで実行できるよりもはるかに単純な作業にはならないようです。 しかし、方法があり、それは理にかなっています。
おそらく、クリーンなライブラリを構築して、すべてのパネルのコンポーネントのユニバーサルセットを作成できます。 このようなライブラリを作成し、データベースに保存するための要素について簡単に説明しました。 SVG画像を作成するだけではないツールを開発する必要があるかもしれません。 いずれにせよ、次のステップのためにできることはたくさんあるでしょう。