Vue.js と Node-RED を WebSocket でつなぐメモ

Vue.js と Node-RED を WebSocket でつなぐメモです。

やりたいこと

image

Vue.js と Node-RED を WebSocket でつないでリアルタイムに動かします。Vue.jsからは入力されたメッセージをNode-REDに送り、Node-REDからは1秒ごとにタイムスタンプを送って Vue.js の表示に反映させます。

フロントエンド側 WebSocket の文献

MDN の文献がよくまとまってますね。ありがたい。

WebSocket – Web API | MDN

そして、

WebSocket クライアントアプリケーションの記述 – Web API | MDN

こちらの方に具体的な実装が書いていて素晴らしい。

webSocket = new WebSocket(url, protocols);

サブプロトコルを指定できる第二引数とか知らなかった。

Node-RED の WebSocket 実装

image

さて、 Node-RED の良いところとして WebSocket かんたんに立ち上げられることです。

もちろん標準で HTTP ノードもあるので、 REST な API をつくってやり取りするのもアリですが、今回はリアルタイムなやり取りをしたいので WebSocket ノードを使います。

フローを作る

image

ごくシンプルに、タイムスタンプを WebSocket でデータ送り、WebSocket で受信したデータを debug ノードでやり取りするフローを作ります。

websocket out ノードの設定

image

websocket out ノードの設定です。

image

種類は 待ち受け に設定し、パスの設定は

image

送信受信設定を ペイロードの送信/受信 、パスの設定を /ws/vue にしています。

websocket in ノードの設定

image

websocket in ノードの設定です。

image

その上でパスの設定は websocket out ノードと同じものを指定しています。

inject ノードの設定

image

injectノードの設定です。

1秒ごとタイムスタンプを送る設定にしておきます。こうすると、Vueでリアルタイムに変わり分かりやすいです。

image

  • 繰り返し
    • 指定した時間間隔
    • 時間間隔 1 秒

で設定します。

JSONのフローデータはこちらです。

[{"id":"8778eae4.9f9b28","type":"websocket out","z":"a25f5900.269728","name":"","server":"d0e2edc9.b103f","client":"","x":740,"y":240,"wires":[]},{"id":"a0b6642d.9dac78","type":"websocket in","z":"a25f5900.269728","name":"","server":"d0e2edc9.b103f","client":"","x":530,"y":300,"wires":[["d7a0b10a.d7789"]]},{"id":"f24170c7.5b2fb","type":"inject","z":"a25f5900.269728","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":false,"onceDelay":0.1,"x":510,"y":240,"wires":[["8778eae4.9f9b28"]]},{"id":"d7a0b10a.d7789","type":"debug","z":"a25f5900.269728","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":740,"y":300,"wires":[]},{"id":"d0e2edc9.b103f","type":"websocket-listener","z":"","path":"/ws/vue","wholemsg":"false"}]

Vue のソースコード

image

今回は、サッとテストしたかったのでローカルでつなげていますが、もちろん、Node-REDをどこかに置いて公開されているサーバー間で対応することも可能です。

ソースコードはこちらです。見た目の調整に BootStrapVue を使っています。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue.js collaborate to Node-RED WebSocket</title>

  <!-- Load required Bootstrap and BootstrapVue CSS -->
  <link rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />

  <!-- Load polyfills to support older browsers -->
  <script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver"
    crossorigin="anonymous"></script>

  <!-- Load Vue followed by BootstrapVue -->
  <script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
  <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>

  <!-- Load the following for BootstrapVueIcons support -->
  <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue-icons.min.js"></script>

</head>

<body>
  <div class="container">

    <div id="appWebsocket">

      <div class="row">
        <div class="col">
          <h1>Vue.js collaborate to Node-RED WebSocket</h1>
        </div>
      </div>

      <div class="form-row">
        <div class="col">
          <input type="email" class="form-control" id="inputMessage" v-model="message">
        </div>
        <div class="col">
          <b-button v-on:click="hanlderSendMessage">SendMessage</b-button>
        </div>
      </div>

      <div class="row">
        <div class="col">
          <h2>Message from Node-RED WebSocket : </h2>
          <pre><code>{{ response }}</code></pre>
        </div>
      </div>

    </div>

  </div>

  <script>
    const app = new Vue({
      el: '#appWebsocket',
      data: {
        response: '',
        message: '',
        ws: null
      },
      methods: {

        // データを送るボタン
        hanlderSendMessage: async function () {
          // JSON データは JSON.stringify で文字列にしてから送っています
          let _message = { 'message': this.message };
          await this.ws.send(JSON.stringify(_message));
        },

        // WebSocket が接続された open イベント
        hanlderWebSocketOpen: function (event) {
          this.ws.send('Hello! Node-RED!');
        },

        // WebSocket からメッセージを受け取った message イベント
        hanlderWebSocketMessage: function (event) {
          console.log('timestamp : ', event.data);
          this.response = event.data;
        }

      }
      ,
      mounted() {
        console.log('mounted');
        // Node-RED のURL+WebSocketのパスでつなげる
        this.ws = new WebSocket('ws://localhost:1880/ws/vue');
        // WebSocket が接続された open イベント設定
        this.ws.onopen = this.hanlderWebSocketOpen;
        // WebSocket からメッセージを受け取った message イベント設定
        this.ws.onmessage = this.hanlderWebSocketMessage;
      }
    })
  </script>

</body>

</html>

動かしてみる

image

まず表示するだけで、Vue側から接続され 接続数 1 と表示されます。

Node-RED → Vue.js

Node-REDからは1秒ごとにタイムスタンプが送られています。

Message from Node-RED WebSocket : のところに、タイムスタンプが1秒ごと表示され、無事にNode-REDからデータを受信できていることが確認できます。

image

Vue.js → Node-RED

つづいて、Vue.jsからデータを送ってみましょう。

image

たとえば「やっほー!Node-RED!」とVue側で打ち込んでSendMessageボタンをクリックすると、Node-RED側でデータが受信されNode-REDのデバッグタブには以下のように表示されます。

image

JSON ノードで受信データを変換するとより扱いやすい

このままですと、Node-REDはJSONではなく文字列と把握しています。

image

ですので、JSON ノードを加えると扱いやすくなります。

image

このような形で、JSONデータが Node-RED 内部でオブジェクトとして使えます。

JSONのフローデータはこちらです。

[{"id":"8778eae4.9f9b28","type":"websocket out","z":"a25f5900.269728","name":"","server":"d0e2edc9.b103f","client":"","x":740,"y":240,"wires":[]},{"id":"a0b6642d.9dac78","type":"websocket in","z":"a25f5900.269728","name":"","server":"d0e2edc9.b103f","client":"","x":390,"y":300,"wires":[["34732b65.9f31c4"]]},{"id":"f24170c7.5b2fb","type":"inject","z":"a25f5900.269728","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":false,"onceDelay":0.1,"x":510,"y":240,"wires":[["8778eae4.9f9b28"]]},{"id":"d7a0b10a.d7789","type":"debug","z":"a25f5900.269728","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":740,"y":300,"wires":[]},{"id":"34732b65.9f31c4","type":"json","z":"a25f5900.269728","name":"","property":"payload","action":"","pretty":false,"x":570,"y":300,"wires":[["d7a0b10a.d7789"]]},{"id":"d0e2edc9.b103f","type":"websocket-listener","z":"","path":"/ws/vue","wholemsg":"false"}]