EnOceanのマルチセンサー STM550Jから温湿度と照度をSakuraVPSで動かしているGrafanaで時系列データとして見える化する方法をご紹介します。
システムイメージ
弊社事務所に設置したSTM550JからEnOceanドングル(USB400J)を刺したReTerminalをゲートウエイにして、そこからさくらのVPSで構築した、Node-REDにMQTTで送信。受け取ったデータをinfluxDBに格納してGrafanaで表示するというイメージです。
今回、STM550Jは3つ用意しました。
ローカル側のNode-RED
ローカル側ではEnOceanセンサーからReTerminalでデータを受信します。
フローはこのとおり。
シリアルノードでUSB400Jのデータを受信。EnOcean data_chgでEnOceanの基本データをデコード。
Switchノードで利用するセンサーのIDだけをフィルタリングして、STM550Jデコードで、温度、湿度、照度、マグネットコンタクト状態を送ります。
MQTT Brokerに送るデータはこのようなJSONになります。
時系列データになるので、ここでタイムコードをしっかり入れておきましょう。
↓ フローはこちら
[{"id":"f4575ef8.576c9","type":"function","z":"73fa502e5317a230","name":"EnOcean data_chg","func":"// データそのもの\nvar buf = msg.payload;\n// データのコピー(検証用)\nvar rawData = buf.slice( 0 , buf.length );\n// 取り出しやすいようにhex変換したもの\nvar rawByte = buf.toString( \"hex\" );\n// ESP3仕様のdataLengthを拾う\nvar dataLength = 255 * buf[ 1 ] + buf[ 2 ];\n// ESP3仕様のoptionalLengthを拾う\nvar optionalLength = buf[ 3 ];\n// packetType\nvar packetType = buf[ 4 ];\n// headerCRC\nvar headerCRC = buf[ 5 ];\n// dataLengthに基づいたデータ部分の切り出し\nvar rawDataByte = buf.slice( 7 , 7 + dataLength+2);\n\nvar id = rawDataByte.slice( 0 , 4 ).toString( \"hex\" );\n\n// payloadに格納\nmsg.payload = {\n rawData:rawData,\n rawByte:rawByte,\n dataLength:dataLength,\n optionalLength:optionalLength,\n packetType:packetType,\n headerCRC:headerCRC,\n rawDataByte:rawDataByte,\n ID:id\n} \n\n\n\nreturn msg;\n\n\n\n ","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":260,"wires":[["3e6ff4537e4f3c94"]]},{"id":"b457aedf2737e527","type":"serial in","z":"73fa502e5317a230","name":"USB400J","serial":"","x":300,"y":180,"wires":[["f4575ef8.576c9"]]},{"id":"3e6ff4537e4f3c94","type":"switch","z":"73fa502e5317a230","name":"","property":"payload.ID","propertyType":"msg","rules":[{"t":"else"}],"checkall":"true","repair":false,"outputs":1,"x":490,"y":380,"wires":[["adda7dcec6b45781"]]},{"id":"341622361af9c320","type":"mqtt out","z":"73fa502e5317a230","name":"Mosquitto","topic":"001/enocean","qos":"0","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"","x":860,"y":500,"wires":[]},{"id":"adda7dcec6b45781","type":"function","z":"73fa502e5317a230","name":"STM550J decode","func":"const rawData = msg.payload.rawDataByte;\n\n// Extract basic information\nmsg.id = rawData.slice(0, 4).toString('hex');\nmsg.dbm = rawData[15] * -1;\nmsg.stm550count = 1;\nmsg.type = 'STM550';\n\n// Extract data bytes\nconst d0 = 4;\nconst data = Array.from({ length: 9 }, (_, i) => rawData[d0 + i].toString(2).padStart(8, '0'));\n\nmsg.data = {\n data_8: data[0],\n data_7: data[1],\n data_6: data[2],\n data_5: data[3],\n data_4: data[4],\n data_3: data[5],\n data_2: data[6],\n data_1: data[7],\n data_0: data[8]\n};\n\n// Calculate temperature\nconst temp = data[0] + data[1].slice(0, 2);\nmsg.temperature = Math.round((parseInt(temp, 2) / 10 - 40) * 100) / 100;\n\n// Calculate magnet\nmsg.magnet = parseInt(data[8].slice(3, 4), 2);\n\n// Calculate illumination\nconst illumination = data[2].slice(2) + data[3] + data[4].slice(0, 3);\nmsg.illumination = parseInt(illumination, 2);\n\nconst IllValue = msg.illumination;\nlet IllmaxValue = global.get('maxIll2') || 0;\nlet IllminValue = global.get('minIll2') || 1000;\n\nif (IllmaxValue < IllValue) {\n IllmaxValue = IllValue;\n global.set('maxIll2', IllmaxValue);\n}\nif (IllminValue > IllValue) {\n IllminValue = IllValue;\n global.set('minIll2', IllminValue);\n}\n\nglobal.set('Ill', IllValue);\n\n// Calculate humidity\nconst humidity = data[1].slice(2, 6) + data[2].slice(0, 2);\nmsg.humidity = Math.round(parseInt(humidity, 2) * 20) / 10;\n\n// Aggregate data\nmsg.payload = {\n id: msg.id,\n dbm: msg.dbm,\n stm550count: msg.stm550count,\n type: msg.type,\n temperature: msg.temperature,\n magnet: msg.magnet,\n illumination: msg.illumination,\n humidity: msg.humidity,\n time:Date.now()\n};\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":400,"wires":[["3767cfcb4e0c77b5","341622361af9c320"]]},{"id":"3767cfcb4e0c77b5","type":"debug","z":"73fa502e5317a230","name":"Output","active":false,"console":"false","complete":"payload","x":870,"y":320,"wires":[]}]
クラウド側 Node-RED
クラウド側のNode-REDは、MQTT OutからJSONで出力。それをそのままinfluxDBに格納するだけです。
クラウド側 Grafana
Grafanaでは、温度、湿度、照度のデータを、3つのセンサー分、折れ線グラフで表示させます。
Grafanaは7を使っています。
+マークを押してPanelを追加。
まずはデータソースの設定をします。
まずはセンサーAの温度を表示するようにします。
センサーBの値を表示するには、下にある+Queryをクリック
2つ目のセンサーの温度が表示されました。
同様に3つ目のセンサーも設定します。
このように3つのセンサーの温度が表示されました。
また、先ほどのパネルをDuplicateしてVisualizationでGaugeを選ぶとこのように表示されます。
FieldからThresholdsを開き、データごとの色を設定することもできます。
まずは、温度の折れ線グラフと現在の温度を表示することができました。