IoT 監控面板
WebSocket 與 Node.js 建立 IoT 監控面板教學
目錄
- 專案概述
- 環境設置
- 後端實現
- 前端開發
- 數據可視化
- 系統部署
- 進階功能
1. 專案概述
1.1 功能特點
- 即時數據監控
- 多種圖表展示
- 警報系統
- 歷史數據查詢
- 設備管理
1.2 技術堆疊
- Node.js + Express
- WebSocket (Socket.io)
- MongoDB
- Chart.js
- Bootstrap
2. 環境設置
2.1 專案初始化
mkdir iot-dashboard
cd iot-dashboard
npm init -y
2.2 安裝依賴
npm install express socket.io mongodb mongoose dotenv
npm install --save-dev nodemon
2.3 專案結構
iot-dashboard/
├── src/
│ ├── models/
│ │ ├── device.js
│ │ └── sensorData.js
│ ├── public/
│ │ ├── css/
│ │ ├── js/
│ │ └── index.html
│ └── server.js
├── package.json
└── .env
3. 後端實現
3.1 基本服務器設置
// src/server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
app.use(express.static('public'));
app.use(express.json());
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
3.2 數據模型定義
// src/models/device.js
const mongoose = require('mongoose');
const deviceSchema = new mongoose.Schema({
deviceId: {
type: String,
required: true,
unique: true
},
name: String,
type: String,
location: String,
status: {
type: String,
enum: ['online', 'offline'],
default: 'offline'
}
});
module.exports = mongoose.model('Device', deviceSchema);
// src/models/sensorData.js
const mongoose = require('mongoose');
const sensorDataSchema = new mongoose.Schema({
deviceId: {
type: String,
required: true
},
temperature: Number,
humidity: Number,
pressure: Number,
timestamp: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('SensorData', sensorDataSchema);
3.3 WebSocket 處理
// src/server.js
const SensorData = require('./models/sensorData');
const Device = require('./models/device');
io.on('connection', (socket) => {
console.log('Client connected');
// 處理設備數據
socket.on('sensorData', async (data) => {
try {
const sensorData = new SensorData(data);
await sensorData.save();
// 更新設備狀態
await Device.findOneAndUpdate(
{ deviceId: data.deviceId },
{ status: 'online' }
);
// 廣播數據給所有客戶端
io.emit('newSensorData', data);
// 檢查警報條件
checkAlertConditions(data);
} catch (error) {
console.error('Error saving sensor data:', error);
}
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
// 警報條件檢查
function checkAlertConditions(data) {
if (data.temperature > 30) {
io.emit('alert', {
type: 'temperature',
message: `High temperature detected: ${data.temperature}°C`,
deviceId: data.deviceId
});
}
}
4. 前端開發
4.1 HTML 結構
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IoT 監控面板</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- 側邊欄 -->
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
<h6 class="sidebar-heading">設備列表</h6>
<ul class="nav flex-column" id="deviceList"></ul>
</div>
</nav>
<!-- 主要內容區 -->
<main class="col-md-10 ml-sm-auto px-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1>IoT 監控面板</h1>
</div>
<!-- 圖表區域 -->
<div class="row">
<div class="col-md-6">
<canvas id="temperatureChart"></canvas>
</div>
<div class="col-md-6">
<canvas id="humidityChart"></canvas>
</div>
</div>
<!-- 警報區域 -->
<div class="alert-container mt-4"></div>
<!-- 實時數據表格 -->
<div class="table-responsive mt-4">
<table class="table table-striped">
<thead>
<tr>
<th>設備ID</th>
<th>溫度</th>
<th>濕度</th>
<th>氣壓</th>
<th>時間</th>
</tr>
</thead>
<tbody id="sensorDataTable"></tbody>
</table>
</div>
</main>
</div>
</div>
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="js/dashboard.js"></script>
</body>
</html>
4.2 JavaScript 實現
// public/js/dashboard.js
const socket = io();
// 圖表配置
const temperatureCtx = document.getElementById('temperatureChart').getContext('2d');
const humidityCtx = document.getElementById('humidityChart').getContext('2d');
const temperatureChart = new Chart(temperatureCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '溫度 (°C)',
data: [],
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
const humidityChart = new Chart(humidityCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '濕度 (%)',
data: [],
borderColor: 'rgb(54, 162, 235)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
// 處理新的感測器數據
socket.on('newSensorData', (data) => {
updateCharts(data);
updateTable(data);
});
// 處理警報
socket.on('alert', (alert) => {
showAlert(alert);
});
// 更新圖表
function updateCharts(data) {
const timestamp = new Date(data.timestamp).toLocaleTimeString();
// 更新溫度圖表
temperatureChart.data.labels.push(timestamp);
temperatureChart.data.datasets[0].data.push(data.temperature);
if (temperatureChart.data.labels.length > 10) {
temperatureChart.data.labels.shift();
temperatureChart.data.datasets[0].data.shift();
}
temperatureChart.update();
// 更新濕度圖表
humidityChart.data.labels.push(timestamp);
humidityChart.data.datasets[0].data.push(data.humidity);
if (humidityChart.data.labels.length > 10) {
humidityChart.data.labels.shift();
humidityChart.data.datasets[0].data.shift();
}
humidityChart.update();
}
// 更新數據表格
function updateTable(data) {
const table = document.getElementById('sensorDataTable');
const row = table.insertRow(0);
row.innerHTML = `
<td>${data.deviceId}</td>
<td>${data.temperature}°C</td>
<td>${data.humidity}%</td>
<td>${data.pressure}hPa</td>
<td>${new Date(data.timestamp).toLocaleString()}</td>
`;
if (table.rows.length > 10) {
table.deleteRow(table.rows.length - 1);
}
}
// 顯示警報
function showAlert(alert) {
const alertContainer = document.querySelector('.alert-container');
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
alertDiv.innerHTML = `
<strong>警報!</strong> ${alert.message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
alertContainer.appendChild(alertDiv);
// 5秒後自動關閉警報
setTimeout(() => {
alertDiv.remove();
}, 5000);
}
5. 數據可視化
5.1 圖表類型
-
實時折線圖
- 溫度變化
- 濕度變化
- 氣壓變化
-
統計圖表
- 日平均值
- 最高/最低值
- 異常值分布
5.2 警報系統
// 警報閾值設置
const alertThresholds = {
temperature: {
high: 30,
low: 10
},
humidity: {
high: 80,
low: 20
}
};
// 警報檢查函數
function checkAlerts(data) {
const alerts = [];
if (data.temperature > alertThresholds.temperature.high) {
alerts.push({
type: 'danger',
message: `溫度過高: ${data.temperature}°C`
});
}
if (data.humidity > alertThresholds.humidity.high) {
alerts.push({
type: 'warning',
message: `濕度過高: ${data.humidity}%`
});
}
return alerts;
}
6. 系統部署
6.1 環境變數設置
PORT=3000
MONGODB_URI=mongodb://localhost:27017/iot-dashboard
6.2 啟動腳本
{
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js"
}
}
6.3 部署步驟
-
安裝依賴
npm install
-
設置環境變數
-
啟動應用
npm start
7. 進階功能
7.1 數據匯出
// 數據匯出功能
async function exportData(startDate, endDate) {
try {
const data = await SensorData.find({
timestamp: {
$gte: startDate,
$lte: endDate
}
});
const csv = convertToCSV(data);
downloadCSV(csv, `sensor-data-${startDate}-${endDate}.csv`);
} catch (error) {
console.error('Export error:', error);
}
}
7.2 設備管理
// 設備註冊
async function registerDevice(deviceInfo) {
try {
const device = new Device(deviceInfo);
await device.save();
return device;
} catch (error) {
console.error('Device registration error:', error);
throw error;
}
}
// 設備狀態更新
async function updateDeviceStatus(deviceId, status) {
try {
await Device.findOneAndUpdate(
{ deviceId },
{ status },
{ new: true }
);
} catch (error) {
console.error('Status update error:', error);
throw error;
}
}
結論
這個 IoT 監控面板提供了:
- 即時數據監控
- 視覺化圖表
- 警報系統
- 數據存儲
- 設備管理
您可以根據需求進一步擴展功能,例如:
- 添加用戶認證
- 實現更多類型的圖表
- 添加數據分析功能
- 優化性能
- 增加更多的警報類型