Node-REDのインストール
Node-REDのインストールを行います。
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
Sakura VPSのパケットフィルターで1880をTCPで開けます。
そのままだとUFWでひっかかるので、
$ sudo ufw allow 1880
これで、1880もあけます。
無事にNode-REDがインストールされました。
Node-REDとダッシュボードの連携
WebSocketを使ってNode-REDからデータを送り、ダッシュボードに表示させます。
まずは、温度のデータをNode-REDから受信します。
index.html側
下記のようなコードにします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リアルタイムセンサーデータ表示</title>
<!-- BootstrapのCSSをCDNから読み込み -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<!-- Chart.jsのCDNを読み込み -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
background-color: #000;
color: #fff;
}
.card {
background-color: #222;
border: none;
}
.card-header {
background-color: #333;
}
.chart-container {
width: 80%;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center my-4">設備情報</h1>
<div class="row">
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">発電電力</div>
<div class="card-body">
<p>本日の合計: <span id="daily-power">24.3</span> kWh</p>
<p>今月の合計: <span id="monthly-power">153.5</span> kWh</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">CO<sub>2</sub>削減量</div>
<div class="card-body">
<p>本日の合計: <span id="daily-co2">96</span> kg-CO<sub>2</sub></p>
<p>今月の合計: <span id="monthly-co2">2466</span> kg-CO<sub>2</sub></p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">温度</div>
<div class="card-body">
<p><span id="temperature">25</span> °C</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">湿度</div>
<div class="card-body">
<p><span id="humidity">31</span> %</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">日射強度</div>
<div class="card-body">
<p><span id="solar-intensity">0.86</span> kW/m<sup>2</sup></p>
</div>
</div>
</div>
</div>
<div class="chart-container">
<canvas id="powerChart"></canvas>
</div>
</div>
<!-- jQueryとBootstrapのJSをCDNから読み込み -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// WebSocket接続を作成
const ws = new WebSocket('ws://NODERED-SERVER-IP:1880/ws/temperature');
// メッセージ送信関数(非同期)
function sendMessage(ws, message) {
return new Promise((resolve, reject) => {
ws.onopen = () => {
ws.send(message);
resolve("メッセージが送信されました: " + message);
};
ws.onerror = (error) => {
reject("WebSocketエラー: " + error);
};
});
}
// WebSocketでメッセージを受信した時の処理
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
document.getElementById('temperature').innerText = data.temperature;
console.log("受信したデータ: ", data);
};
// 接続が開かれたときの処理
ws.onopen = function() {
console.log("WebSocket接続が開かれました");
// サーバーへのメッセージ送信
sendMessage(ws, JSON.stringify({ temperature: 25 }))
.then(response => console.log(response))
.catch(error => console.error(error));
};
// 接続が閉じられたときの処理
ws.onclose = function() {
console.log("WebSocket接続が閉じられました");
};
// データを更新する関数
function updateSensorData() {
// 他のデータはランダムに生成(例示のため)
document.getElementById('daily-power').innerText = (Math.random() * 30).toFixed(1);
document.getElementById('monthly-power').innerText = (Math.random() * 200).toFixed(1);
document.getElementById('daily-co2').innerText = Math.floor(Math.random() * 100);
document.getElementById('monthly-co2').innerText = Math.floor(Math.random() * 3000);
document.getElementById('humidity').innerText = Math.floor(20 + Math.random() * 60);
document.getElementById('solar-intensity').innerText = (Math.random() * 1).toFixed(2);
}
// 5秒ごとにデータを更新
setInterval(updateSensorData, 5000);
// グラフの設定
const ctx = document.getElementById('powerChart').getContext('2d');
const powerChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['0時', '3時', '6時', '9時', '12時', '15時', '18時', '21時'],
datasets: [{
label: '本日の発電量',
data: [0, 0, 5, 15, 25, 30, 20, 5],
backgroundColor: 'rgba(255, 165, 0, 0.7)',
borderColor: 'rgba(255, 165, 0, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true,
max: 30
}
}
}
});
</script>
</body>
</html>
WebSocketの接続にはNode-REDサーバーのIPアドレスを使用します。
Node-RED側
[{"id":"a1f7d9c0.7b45d8","type":"inject","z":"1c01e343d026d694","name":"5秒ごとに実行","props":[],"repeat":"5","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":240,"y":240,"wires":[["f983bc45.957b"]]},{"id":"f983bc45.957b","type":"function","z":"1c01e343d026d694","name":"温度データ生成","func":"msg.payload = {\n temperature: (20 + Math.random() * 10).toFixed(1)\n};\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":300,"wires":[["1e6d1296.f233be"]]},{"id":"1e6d1296.f233be","type":"websocket out","z":"1c01e343d026d694","name":"","server":"d44f4b57.6b7198","client":"","x":660,"y":380,"wires":[]},{"id":"d44f4b57.6b7198","type":"websocket-listener","path":"/ws/temperature","wholemsg":"false"}]
このようなフローを作成し、functionノードで作成した温度データをWebSocketOutノードで取得できるようにします。
これで、index.html側で受け取れるようになるのですが、Node-RED側を修正すると
WebSocketが切断されてしまいます。
一度閉じられたWebSocket通信を復活させる方法
一度閉じられたWebSocket通信を再接続させるためには、WebSocketのonclose
やonerror
イベントを使用して、接続が切れた際に再接続を試みるロジックを実装する必要があります。
下記のコードをindex.htmlに追加します。
// WebSocket接続の初期化
function initWebSocket() {
ws = new WebSocket('ws://NODERED-SERVER-IP:1880/ws/temperature');
// WebSocketが開いたときの処理
ws.onopen = function() {
console.log("WebSocket接続が開かれました");
};
// WebSocketでメッセージを受信した時の処理
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
document.getElementById('temperature').innerText = data.temperature;
console.log("受信したデータ: ", data);
};
// エラー時の処理
ws.onerror = function(error) {
console.error("WebSocketエラー: ", error);
};
// 接続が閉じられた時の再接続処理
ws.onclose = function() {
console.log("WebSocket接続が閉じられました。再接続を試みます...");
setTimeout(initWebSocket, reconnectInterval); // 再接続を5秒後に試みる
};
}
こうすることで
このように、WebSocket接続が閉じられたら再接続をするようになりました。
他のデータもNode-REDから受信できるようにする
発電電力、CO2削減量、湿度、日射強度もWebSocketでNode-REDから受信できるようにします。また、WebSocketのEndpointがtemperatureだったのでこれをdataに修正します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リアルタイムセンサーデータ表示</title>
<!-- BootstrapのCSSをCDNから読み込み -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<!-- Chart.jsのCDNを読み込み -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
background-color: #000;
color: #fff;
}
.card {
background-color: #222;
border: none;
}
.card-header {
background-color: #333;
}
.chart-container {
width: 80%;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center my-4">設備情報</h1>
<div class="row">
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">発電電力</div>
<div class="card-body">
<p>本日の合計: <span id="daily-power">24.3</span> kWh</p>
<p>今月の合計: <span id="monthly-power">153.5</span> kWh</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">CO<sub>2</sub>削減量</div>
<div class="card-body">
<p>本日の合計: <span id="daily-co2">96</span> kg-CO<sub>2</sub></p>
<p>今月の合計: <span id="monthly-co2">2466</span> kg-CO<sub>2</sub></p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">気温</div>
<div class="card-body">
<p><span id="temperature">25</span> °C</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">湿度</div>
<div class="card-body">
<p><span id="humidity">31</span> %</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card text-center">
<div class="card-header">日射強度</div>
<div class="card-body">
<p><span id="solar-intensity">0.86</span> kW/m<sup>2</sup></p>
</div>
</div>
</div>
</div>
<div class="chart-container">
<canvas id="powerChart"></canvas>
</div>
</div>
<!-- jQueryとBootstrapのJSをCDNから読み込み -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
let ws;
const reconnectInterval = 5000; // 5秒間隔で再接続
// WebSocket接続の初期化
function initWebSocket() {
ws = new WebSocket('ws://NODERED-SERVER-IP:1880/ws/data');
// WebSocketが開いたときの処理
ws.onopen = function() {
console.log("WebSocket接続が開かれました");
};
// WebSocketでメッセージを受信した時の処理
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
// 受信データをそれぞれの要素に反映
if (data.temperature !== undefined) {
document.getElementById('temperature').innerText = data.temperature;
}
if (data.dailyPower !== undefined) {
document.getElementById('daily-power').innerText = data.dailyPower;
}
if (data.monthlyPower !== undefined) {
document.getElementById('monthly-power').innerText = data.monthlyPower;
}
if (data.dailyCO2 !== undefined) {
document.getElementById('daily-co2').innerText = data.dailyCO2;
}
if (data.monthlyCO2 !== undefined) {
document.getElementById('monthly-co2').innerText = data.monthlyCO2;
}
if (data.humidity !== undefined) {
document.getElementById('humidity').innerText = data.humidity;
}
if (data.solarIntensity !== undefined) {
document.getElementById('solar-intensity').innerText = data.solarIntensity;
}
console.log("受信したデータ: ", data);
};
// エラー時の処理
ws.onerror = function(error) {
console.error("WebSocketエラー: ", error);
};
// 接続が閉じられた時の再接続処理
ws.onclose = function() {
console.log("WebSocket接続が閉じられました。再接続を試みます...");
setTimeout(initWebSocket, reconnectInterval); // 再接続を5秒後に試みる
};
}
// WebSocket接続の開始
initWebSocket();
// グラフの設定
const ctx = document.getElementById('powerChart').getContext('2d');
const powerChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['0時', '3時', '6時', '9時', '12時', '15時', '18時', '21時'],
datasets: [{
label: '本日の発電量',
data: [0, 0, 5, 15, 25, 30, 20, 5],
backgroundColor: 'rgba(255, 165, 0, 0.7)',
borderColor: 'rgba(255, 165, 0, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true,
max: 30
}
}
}
});
</script>
</body>
</html>
Node-RED側も修正します
msg.payload = {
temperature: (20 + Math.random() * 10).toFixed(1), // 気温 20°C〜30°C
dailyPower: (Math.random() * 50).toFixed(1), // 本日の発電電力 0〜50 kWh
monthlyPower: (Math.random() * 200).toFixed(1), // 今月の発電電力 0〜200 kWh
dailyCO2: Math.floor(Math.random() * 300), // 本日のCO2削減量 0〜300 kg-CO2
monthlyCO2: Math.floor(Math.random() * 3000), // 今月のCO2削減量 0〜3000 kg-CO2
humidity: Math.floor(20 + Math.random() * 40), // 湿度 20〜60%
solarIntensity: (Math.random()).toFixed(2) // 日射強度 0〜1 kW/m²
};
return msg;
functionノードをこのように修正
結果
これで、Node-RED側からデータを受け取ることができました。
まとめ
以上、Sakura VPSでnginxを使いIoTダッシュボードを作成する方法 その2を紹介しました。
次回はNode-RED側でグラフの値を生成して、index.htmlで表示する方法を紹介します。