はじめに
今回は社内で動いているEnOceanの「STM 550J-Multisensor Module」があるので、どういうフローで動いているか解析して自分の環境で動かしてGrafanaで見える化までしていきます
EnOcean:https://www.enocean.com/en/
製品
本センサは、温度、湿度、照度、3軸加速度センサー、コンタクトセンサーの5つの機能を有しています
ソーラーパネルを搭載しているため、室内光ぐらいで発電できます
またコイン電池を搭載可能なため暗い場所でも長時間稼働可能です
公式URL
https://www.enocean.com/en/product/stm-550-multisensor-module/?frequency=928
フロー解析
まずは社内に既に動かしている例があるのでNode-REDのフローを解析して自分で動かせるように理解します
既に動いているNode-RED

・Serial portノード

写真での赤い矢印がある所です
このノードは「STM550J」が取ったデータを受信しているノードになります
今回はUSB400JというUSBドングル受信機がデータを受け取っています
・Functionノード (デコード)

ここではデータのデコードを行っています
STM550Jから来たデータは、バイナリデータという人間が見ても分からない形なので必要なデータの部分を抽出と個体番号を抽出しています
中身
// データそのもの
var buf = msg.payload;
// データのコピー(検証用)
var rawData = buf.slice(0, buf.length);
// 取り出しやすいようにhex変換したもの
var rawByte = buf.toString("hex");
// ESP3仕様のdataLengthを拾う
var dataLength = 255 * buf
+ buf
;
// ESP3仕様のoptionalLengthを拾う
var optionalLength = buf
;
// packetType
var packetType = buf
;
// headerCRC
var headerCRC = buf[5];
// dataLengthに基づいたデータ部分の切り出し
var rawDataByte = buf.slice(7, 7 + dataLength + 2);
var id = rawDataByte.slice(0, 4).toString("hex");
// payloadに格納
msg.payload = {
rawData: rawData,
rawByte: rawByte,
dataLength: dataLength,
optionalLength: optionalLength,
packetType: packetType,
headerCRC: headerCRC,
rawDataByte: rawDataByte,
ID: id
}
return msg;
・Switchノード

先ほどFuctionノードでデコードしたデータから個体番号が取れています
Switchノードでは予め通す個体番号を決めて置いて、それ以外は次のステップに飛ばさせない様にしています
中身(黒い所はIDが書いています)

・Functionノード(データ成形)

このノードでは切り出されたデータ(rawDataByte)を人が見ても分かるように成形しています
出されるものは
1.ID:個体番号
2.dbm:電波強度(デバイスから送信された電場の強度)
3.stm550count:デバイスからのデータの受信回数
4.type:デバイスの種類
5.temperature:湿度(摂氏)
6.magnet:マグネットが接近しているか(1なら近い)
7.illumination:照度
8.humidity:湿度
9.time:タイムスタンプ
const rawData = msg.payload.rawDataByte;
// Extract basic information
msg.id = rawData.slice(0, 4).toString('hex');
msg.dbm = rawData[15] * -1;
msg.stm550count = 1;
msg.type = 'STM550';
// Extract data bytes
const d0 = 4;
const data = Array.from({ length: 9 }, (_, i) => rawData[d0 + i].toString(2).padStart(8, '0'));
msg.data = {
data_8: data[0],
data_7: data
,
data_6: data
,
data_5: data
,
data_4: data
,
data_3: data[5],
data_2: data[6],
data_1: data[7],
data_0: data[8]
};
// Calculate temperature
const temp = data[0] + data
.slice(0, 2);
msg.temperature = Math.round((parseInt(temp, 2) / 10 - 40) * 100) / 100;
// Calculate magnet
msg.magnet = parseInt(data[8].slice(3, 4), 2);
// Calculate illumination
const illumination = data
.slice(2) + data
+ data
.slice(0, 3);
msg.illumination = parseInt(illumination, 2);
const IllValue = msg.illumination;
let IllmaxValue = global.get('maxIll2') || 0;
let IllminValue = global.get('minIll2') || 1000;
if (IllmaxValue < IllValue) {
IllmaxValue = IllValue;
global.set('maxIll2', IllmaxValue);
}
if (IllminValue > IllValue) {
IllminValue = IllValue;
global.set('minIll2', IllminValue);
}
global.set('Ill', IllValue);
// Calculate humidity
const humidity = data
.slice(2, 6) + data
.slice(0, 2);
msg.humidity = Math.round(parseInt(humidity, 2) * 20) / 10;
// Aggregate data
msg.payload = {
id: msg.id,
dbm: msg.dbm,
stm550count: msg.stm550count,
type: msg.type,
temperature: msg.temperature,
magnet: msg.magnet,
illumination: msg.illumination,
humidity: msg.humidity,
time: Date.now()
};
return msg;
以下出力例(IDだけは隠しています)
{"id":"*******",
"dbm":-57,"stm550count":1,
"type":"STM550",
"temperature":26.7,
"magnet":0,
"illumination":842,
"humidity":70,
"time":1719542672595}
・Debugノード

このノードは先ほどのfunctionノードから来たデータをNode-REDでどういう結果に成っているかを見るための出力ノードです
・MQTT outノード

このノードはFunctionノード作成したデータをmqttで送る物です
今回は指定されたサーバに送っていました
自分でも動かして見る
私の方はホストマシンにUSBドングルを差して直接データを受信してきます
センサは二つあったので両方受信してみることにしました
Switchノードで設定個体番号以外は排除しています
フローとしては殆ど変わりませんが、送り先がInfluxDBになっています

ホストマシンで展開しているNode-REDとDokcerコンテナで展開しているInfluxDBの接続方法
過去ブログ:https://blog.smartlight.co.jp/?p=4539
注意
赤枠の所をnsでやるとデータが格納されない場合がありました

送っているデータ

Influxdbに格納されたデータ(個体番号)

見える化(Grafana)
折角データを取ったので、Grafanaに表示していきます
まずはInfluxdbとGrafanaを接続します
接続参考:https://blog.smartlight.co.jp/?p=4485
接続が出来ました

パネルを作成して見ましょう
照度と湿度を作成してみます
・湿度
今回は2つのセンサからデータが来ているので片方(0420a6de)が読み取った湿度をグラフ化してみます
結果

呼び出したときのデータ

クエリ
deviceID = from(bucket: "test2")
|> range(start:-2h)
|> filter(fn: (r) => r["_measurement"] == "stm550j" and r["_field"] == "id" and r["_value"] == "0420a6de")
|> keep(columns: ["_time", "_start", "_stop", "_measurement", "_value"])
|> rename(columns: {"_value": "deviceID"})
humidityData = from(bucket: "test2")
|> range(start:-2h)
|> filter(fn: (r) => r["_measurement"] == "stm550j" and r["_field"] == "humidity")
|> keep(columns: ["_time", "_start", "_stop", "_measurement", "_value"])
|> rename(columns: {"_value": "humidity"})
join(
tables: {deviceID: deviceID, humidityData: humidityData},
on: ["_time", "_measurement"]
)
|> keep(columns: ["_time", "humidity", "deviceID"])
|> yield(name: "humidityData")
・照度
湿度と同じように(0420a6de)が読み取った湿度をグラフ化してみます
結果

呼び出したデータ

クエリ
deviceID = from(bucket: "test2")
|> range(start:-2h)
|> filter(fn: (r) => r["_measurement"] == "stm550j" and r["_field"] == "id" and r["_value"] == "0420a6de")
|> keep(columns: ["_time", "_start", "_stop", "_measurement", "_value"])
|> rename(columns: {"_value": "deviceID"})
illuminationData = from(bucket: "test2")
|> range(start:-2h)
|> filter(fn: (r) => r["_measurement"] == "stm550j" and r["_field"] == "illumination")
|> keep(columns: ["_time", "_start", "_stop", "_measurement", "_value"])
|> rename(columns: {"_value": "illumination"})
join(
tables: {deviceID: deviceID, illuminationData: illuminationData},
on: ["_time", "_measurement"]
)
|> keep(columns: ["_time", "illumination", "deviceID"])
|> yield(name: "illuminationData")
まとめ
今回は解析から自分の環境で動かすまでをやりました
Node-REDからInfluxDBに適当設定のせいでデータが格納されなくて時間掛かってしまいました
何を設定しているかの意味を知るって大切ですね