IoT物联网平台 - Part 6: WebSocket Web前端JavaScript实现
本文介绍了一个基于WebSocket的IoT平台前端仪表板实现。该仪表板采用单例模式(Dashboard类)管理全局状态,通过WebSocket连接实时接收设备数据,并使用观察者模式处理事件通知。主要功能包括: 实时数据展示:通过图表可视化温度、湿度等传感器数据 设备管理:显示设备状态、最后活跃时间、电池电量等信息 告警通知:支持不同级别的告警显示和声音提示 设备控制:提供设备重启、配置获取等控制
文件7: frontend/dashboard.js - 仪表板控制脚本
/**
* @file dashboard.js
* @brief IoT平台仪表板控制脚本
* @author IoT Platform Team
* @version 1.0
* @date 2024
*
* 实现前端数据展示和交互逻辑
* 使用观察者模式和单例模式
*/
/**
* @class Dashboard
* @brief 仪表板主类
* @details 管理所有仪表板组件和状态
*
* 设计模式:单例模式(Singleton Pattern)
* - 处理突发事件:多个组件状态同步、资源重复初始化
* - 工程作用:确保全局状态一致性,避免重复实例化
*/
class Dashboard {
/**
* @private @static
* @brief 单例实例
*/
static #instance = null;
/**
* @private
* @brief WebSocket连接对象
*/
#websocket = null;
/**
* @private
* @brief 服务器连接状态
*/
#connected = false;
/**
* @private
* @brief 重连尝试次数
*/
#reconnectAttempts = 0;
/**
* @private
* @brief 最大重连尝试次数
*/
#maxReconnectAttempts = 10;
/**
* @private
* @brief 重连延迟(毫秒)
*/
#reconnectDelay = 5000;
/**
* @private
* @brief 设备数据缓存
* @type {Map<string, Object>}
*/
#deviceCache = new Map();
/**
* @private
* @brief 图表实例映射
* @type {Map<string, Chart>}
*/
#charts = new Map();
/**
* @private
* @brief 观察者回调函数数组
* @type {Array<Function>}
*/
#observers = [];
/**
* @private
* @brief 实时数据缓冲区
*/
#dataBuffer = {
temperature: [],
humidity: [],
power: [],
timestamp: []
};
/**
* @private
* @brief 最大缓冲区大小
*/
#maxBufferSize = 1000;
/**
* @private
* @brief 构造函数(私有,实现单例模式)
*/
constructor() {
console.log('Dashboard instance created');
this.#initializeComponents();
}
/**
* @static
* @brief 获取Dashboard单例实例
* @return {Dashboard} Dashboard实例
*/
static getInstance() {
if (!Dashboard.#instance) {
Dashboard.#instance = new Dashboard();
}
return Dashboard.#instance;
}
/**
* @private
* @brief 初始化所有组件
*/
#initializeComponents() {
this.#initializeWebSocket();
this.#initializeCharts();
this.#initializeEventListeners();
this.#startDataUpdateLoop();
console.log('Dashboard components initialized');
}
/**
* @private
* @brief 初始化WebSocket连接
*/
#initializeWebSocket() {
// 获取WebSocket服务器地址
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
const wsUrl = `${protocol}//${host}/ws`;
console.log(`Connecting to WebSocket: ${wsUrl}`);
try {
this.#websocket = new WebSocket(wsUrl);
// 设置事件处理函数
this.#websocket.onopen = (event) => this.#handleWebSocketOpen(event);
this.#websocket.onmessage = (event) => this.#handleWebSocketMessage(event);
this.#websocket.onclose = (event) => this.#handleWebSocketClose(event);
this.#websocket.onerror = (event) => this.#handleWebSocketError(event);
} catch (error) {
console.error('Failed to create WebSocket connection:', error);
this.#showConnectionError('创建WebSocket连接失败');
}
}
/**
* @private
* @brief 处理WebSocket连接打开
* @param {Event} event WebSocket打开事件
*/
#handleWebSocketOpen(event) {
console.log('WebSocket connection established');
this.#connected = true;
this.#reconnectAttempts = 0;
// 更新UI状态
this.#updateConnectionStatus(true);
// 发送连接成功消息
this.#sendWebSocketMessage({
type: 'connection_established',
timestamp: Date.now(),
client: 'web_dashboard'
});
// 请求初始数据
this.#requestInitialData();
// 通知观察者
this.#notifyObservers('connected', { connected: true });
}
/**
* @private
* @brief 处理WebSocket消息
* @param {MessageEvent} event WebSocket消息事件
*/
#handleWebSocketMessage(event) {
try {
const data = JSON.parse(event.data);
this.#processIncomingData(data);
} catch (error) {
console.error('Failed to parse WebSocket message:', error, event.data);
}
}
/**
* @private
* @brief 处理WebSocket连接关闭
* @param {CloseEvent} event WebSocket关闭事件
*/
#handleWebSocketClose(event) {
console.log(`WebSocket connection closed: ${event.code} ${event.reason}`);
this.#connected = false;
// 更新UI状态
this.#updateConnectionStatus(false);
// 通知观察者
this.#notifyObservers('disconnected', {
code: event.code,
reason: event.reason
});
// 尝试重新连接
if (this.#reconnectAttempts < this.#maxReconnectAttempts) {
this.#scheduleReconnect();
} else {
this.#showConnectionError('无法连接到服务器,请刷新页面重试');
}
}
/**
* @private
* @brief 处理WebSocket错误
* @param {Event} event WebSocket错误事件
*/
#handleWebSocketError(event) {
console.error('WebSocket error:', event);
this.#showConnectionError('WebSocket连接错误');
}
/**
* @private
* @brief 安排重新连接
*/
#scheduleReconnect() {
this.#reconnectAttempts++;
const delay = this.#reconnectDelay * Math.min(this.#reconnectAttempts, 5);
console.log(`Scheduling reconnect in ${delay}ms (attempt ${this.#reconnectAttempts})`);
setTimeout(() => {
if (!this.#connected) {
console.log('Attempting to reconnect...');
this.#initializeWebSocket();
}
}, delay);
}
/**
* @private
* @brief 发送WebSocket消息
* @param {Object} message 要发送的消息对象
*/
#sendWebSocketMessage(message) {
if (this.#websocket && this.#websocket.readyState === WebSocket.OPEN) {
try {
const jsonMessage = JSON.stringify(message);
this.#websocket.send(jsonMessage);
} catch (error) {
console.error('Failed to send WebSocket message:', error);
}
} else {
console.warn('WebSocket is not connected, message not sent:', message);
}
}
/**
* @private
* @brief 处理传入数据
* @param {Object} data 接收到的数据
*/
#processIncomingData(data) {
// 根据数据类型分发处理
switch (data.type) {
case 'device_data':
this.#processDeviceData(data);
break;
case 'device_status':
this.#processDeviceStatus(data);
break;
case 'system_stats':
this.#processSystemStats(data);
break;
case 'alert':
this.#processAlert(data);
break;
case 'command_response':
this.#processCommandResponse(data);
break;
default:
console.log('Unknown message type:', data.type);
break;
}
// 通知观察者新数据到达
this.#notifyObservers('data_received', data);
}
/**
* @private
* @brief 处理设备数据
* @param {Object} data 设备数据
*/
#processDeviceData(data) {
const deviceId = data.device_id;
const sensorData = data.data;
// 更新设备缓存
if (!this.#deviceCache.has(deviceId)) {
this.#deviceCache.set(deviceId, {
id: deviceId,
type: data.device_type,
last_seen: Date.now(),
data_history: []
});
}
const device = this.#deviceCache.get(deviceId);
device.last_seen = Date.now();
// 添加到历史数据(限制大小)
device.data_history.push({
timestamp: data.timestamp,
data: sensorData
});
if (device.data_history.length > 100) {
device.data_history.shift(); // 移除最旧的数据
}
// 更新实时数据缓冲区
this.#updateDataBuffer(data);
// 更新设备表格
this.#updateDeviceTable(deviceId, device);
// 更新实时图表
this.#updateRealtimeChart(data);
console.log(`Device data processed: ${deviceId}`, sensorData);
}
/**
* @private
* @brief 处理设备状态
* @param {Object} data 设备状态数据
*/
#processDeviceStatus(data) {
const deviceId = data.device_id;
const status = data.status;
const online = status === 'online';
// 更新设备缓存
if (this.#deviceCache.has(deviceId)) {
const device = this.#deviceCache.get(deviceId);
device.online = online;
device.last_status_update = Date.now();
if (!online) {
device.last_seen = Date.now();
}
} else {
// 如果设备不存在,创建新记录
this.#deviceCache.set(deviceId, {
id: deviceId,
type: data.device_type || 'unknown',
online: online,
last_seen: Date.now(),
last_status_update: Date.now(),
data_history: []
});
}
// 更新设备表格
this.#updateDeviceTable(deviceId, this.#deviceCache.get(deviceId));
// 更新连接统计
this.#updateConnectionStats();
console.log(`Device status updated: ${deviceId} -> ${status}`);
}
/**
* @private
* @brief 处理系统统计信息
* @param {Object} data 系统统计数据
*/
#processSystemStats(data) {
// 更新服务器状态显示
this.#updateServerStats(data);
// 更新消息统计
this.#updateMessageStats(data);
// 更新图表数据
this.#updateStatsCharts(data);
// 更新最后更新时间
this.#updateLastUpdateTime();
}
/**
* @private
* @brief 处理告警信息
* @param {Object} data 告警数据
*/
#processAlert(data) {
// 添加告警到列表
this.#addAlertToList(data);
// 更新告警计数
this.#updateAlertCount();
// 显示通知(如果浏览器支持)
if (data.level === 'critical' && 'Notification' in window) {
this.#showDesktopNotification(data);
}
// 播放声音提示(可选)
if (data.level === 'critical' || data.level === 'warning') {
this.#playAlertSound(data.level);
}
console.log(`Alert received: ${data.level} - ${data.message}`);
}
/**
* @private
* @brief 处理命令响应
* @param {Object} data 命令响应数据
*/
#processCommandResponse(data) {
const commandId = data.command_id;
const success = data.success;
const message = data.message || '';
// 显示响应消息
this.#showCommandResponse(commandId, success, message);
// 如果命令失败,记录错误
if (!success) {
console.error(`Command ${commandId} failed: ${message}`);
}
}
/**
* @private
* @brief 更新数据缓冲区
* @param {Object} data 设备数据
*/
#updateDataBuffer(data) {
const timestamp = new Date(data.timestamp);
// 添加到缓冲区
this.#dataBuffer.timestamp.push(timestamp);
// 根据数据类型添加数据
if (data.data.temperature !== undefined) {
this.#dataBuffer.temperature.push(data.data.temperature);
}
if (data.data.humidity !== undefined) {
this.#dataBuffer.humidity.push(data.data.humidity);
}
if (data.data.power !== undefined) {
this.#dataBuffer.power.push(data.data.power);
}
// 保持缓冲区大小
if (this.#dataBuffer.timestamp.length > this.#maxBufferSize) {
this.#dataBuffer.timestamp.shift();
this.#dataBuffer.temperature.shift();
this.#dataBuffer.humidity.shift();
this.#dataBuffer.power.shift();
}
}
/**
* @private
* @brief 初始化图表
*/
#initializeCharts() {
// 实时数据图表
this.#initializeRealtimeChart();
// 消息速率图表
this.#initializeMessageRateChart();
// 其他图表可以根据需要添加
console.log('Charts initialized');
}
/**
* @private
* @brief 初始化实时数据图表
*/
#initializeRealtimeChart() {
const ctx = document.getElementById('realtime-chart').getContext('2d');
// 创建图表实例
const chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: '温度 (°C)',
data: [],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.4,
fill: true,
yAxisID: 'y'
},
{
label: '湿度 (%)',
data: [],
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.4,
fill: true,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
type: 'time',
time: {
unit: 'minute',
displayFormats: {
minute: 'HH:mm'
}
},
title: {
display: true,
text: '时间'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: '温度 (°C)'
},
min: 0,
max: 50
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: '湿度 (%)'
},
min: 0,
max: 100,
grid: {
drawOnChartArea: false
}
}
},
plugins: {
legend: {
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
}
}
});
this.#charts.set('realtime', chart);
}
/**
* @private
* @brief 初始化消息速率图表
*/
#initializeMessageRateChart() {
const ctx = document.getElementById('message-rate-chart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '消息/秒',
data: [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
display: false
},
y: {
beginAtZero: true,
title: {
display: true,
text: '消息数'
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
this.#charts.set('messageRate', chart);
}
/**
* @private
* @brief 更新实时图表
* @param {Object} data 设备数据
*/
#updateRealtimeChart(data) {
const chart = this.#charts.get('realtime');
if (!chart) return;
const timestamp = new Date(data.timestamp);
// 添加新数据点
if (data.data.temperature !== undefined) {
chart.data.datasets[0].data.push({
x: timestamp,
y: data.data.temperature
});
}
if (data.data.humidity !== undefined) {
chart.data.datasets[1].data.push({
x: timestamp,
y: data.data.humidity
});
}
// 限制数据点数量(保持最近100个点)
const maxPoints = 100;
if (chart.data.datasets[0].data.length > maxPoints) {
chart.data.datasets[0].data.shift();
}
if (chart.data.datasets[1].data.length > maxPoints) {
chart.data.datasets[1].data.shift();
}
// 更新图表
chart.update('none');
}
/**
* @private
* @brief 更新消息速率图表
* @param {Object} stats 统计信息
*/
#updateMessageRateChart(stats) {
const chart = this.#charts.get('messageRate');
if (!chart) return;
const now = new Date();
const timeLabel = now.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
// 添加新数据点
chart.data.labels.push(timeLabel);
chart.data.datasets[0].data.push(stats.messages_per_second || 0);
// 限制数据点数量(保持最近20个点)
const maxPoints = 20;
if (chart.data.labels.length > maxPoints) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
// 更新图表
chart.update();
}
/**
* @private
* @brief 初始化事件监听器
*/
#initializeEventListeners() {
// 刷新设备按钮
const refreshBtn = document.getElementById('refresh-devices');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => this.#refreshDevices());
}
// 保存设备按钮
const saveDeviceBtn = document.getElementById('saveDeviceBtn');
if (saveDeviceBtn) {
saveDeviceBtn.addEventListener('click', () => this.#saveDevice());
}
// 图表时间范围选择
const timeRangeSelect = document.getElementById('chart-time-range');
if (timeRangeSelect) {
timeRangeSelect.addEventListener('change', (e) => {
this.#changeChartTimeRange(e.target.value);
});
}
// 深色模式切换
const darkModeSwitch = document.getElementById('darkModeSwitch');
if (darkModeSwitch) {
darkModeSwitch.addEventListener('change', (e) => {
this.#toggleDarkMode(e.target.checked);
});
}
// 窗口大小改变时重新调整图表
window.addEventListener('resize', () => {
this.#resizeCharts();
});
// 页面可见性变化
document.addEventListener('visibilitychange', () => {
this.#handleVisibilityChange();
});
console.log('Event listeners initialized');
}
/**
* @private
* @brief 开始数据更新循环
*/
#startDataUpdateLoop() {
// 每秒更新一次UI
setInterval(() => {
this.#updateUI();
}, 1000);
// 每5秒请求一次系统状态
setInterval(() => {
if (this.#connected) {
this.#requestSystemStats();
}
}, 5000);
console.log('Data update loops started');
}
/**
* @private
* @brief 更新UI状态
*/
#updateUI() {
// 更新当前时间
this.#updateCurrentTime();
// 更新设备状态(检查超时)
this.#checkDeviceTimeouts();
// 更新连接状态指示器
this.#updateConnectionIndicator();
}
/**
* @private
* @brief 更新当前时间显示
*/
#updateCurrentTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dateString = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
const timeElement = document.getElementById('current-time');
if (timeElement) {
timeElement.textContent = `${dateString} ${timeString}`;
}
}
/**
* @private
* @brief 更新连接状态显示
* @param {boolean} connected 是否已连接
*/
#updateConnectionStatus(connected) {
const statusElement = document.getElementById('server-status');
const indicator = document.querySelector('.connection-indicator');
if (statusElement) {
statusElement.textContent = connected ? '运行中' : '已断开';
statusElement.className = connected ? 'stat-value text-success' : 'stat-value text-danger';
}
if (indicator) {
indicator.className = connected ?
'device-status status-online' :
'device-status status-offline';
}
// 更新连接状态指示器
this.#updateConnectionIndicator();
}
/**
* @private
* @brief 更新连接状态指示器
*/
#updateConnectionIndicator() {
const indicator = document.getElementById('connection-indicator');
if (!indicator) return;
if (this.#connected) {
indicator.className = 'device-status status-online pulse';
indicator.title = '已连接到服务器';
} else {
indicator.className = 'device-status status-offline';
indicator.title = '服务器连接已断开';
}
}
/**
* @private
* @brief 更新设备表格
* @param {string} deviceId 设备ID
* @param {Object} deviceData 设备数据
*/
#updateDeviceTable(deviceId, deviceData) {
const tableBody = document.getElementById('device-table-body');
if (!tableBody) return;
// 查找或创建设备行
let row = document.getElementById(`device-row-${deviceId}`);
if (!row) {
// 创建新行
row = this.#createDeviceTableRow(deviceId, deviceData);
tableBody.appendChild(row);
} else {
// 更新现有行
this.#updateDeviceTableRow(row, deviceData);
}
}
/**
* @private
* @brief 创建设备表格行
* @param {string} deviceId 设备ID
* @param {Object} deviceData 设备数据
* @return {HTMLTableRowElement} 表格行元素
*/
#createDeviceTableRow(deviceId, deviceData) {
const row = document.createElement('tr');
row.id = `device-row-${deviceId}`;
row.className = 'device-card';
// 设置点击事件
row.addEventListener('click', () => {
this.#showDeviceDetails(deviceId);
});
// 创建表格单元格
row.innerHTML = `
<td>
<span class="device-status ${deviceData.online ? 'status-online' : 'status-offline'}"></span>
${deviceData.online ? '在线' : '离线'}
</td>
<td>${deviceId}</td>
<td>${this.#getDeviceTypeName(deviceData.type)}</td>
<td>${this.#formatTimestamp(deviceData.last_seen)}</td>
<td>${deviceData.signal_strength || 'N/A'}</td>
<td>
${deviceData.battery_level !== undefined ?
this.#createBatteryIndicator(deviceData.battery_level) :
'N/A'}
</td>
<td>
<button class="btn btn-sm btn-outline-primary"
οnclick="event.stopPropagation(); Dashboard.getInstance().sendCommand('${deviceId}', 'ping')">
<i class="fas fa-sync"></i>
</button>
<button class="btn btn-sm btn-outline-warning ms-1"
οnclick="event.stopPropagation(); Dashboard.getInstance().sendCommand('${deviceId}', 'reboot')">
<i class="fas fa-redo"></i>
</button>
</td>
`;
return row;
}
/**
* @private
* @brief 更新设备表格行
* @param {HTMLTableRowElement} row 表格行
* @param {Object} deviceData 设备数据
*/
#updateDeviceTableRow(row, deviceData) {
// 更新状态单元格
const statusCell = row.cells[0];
const statusSpan = statusCell.querySelector('.device-status');
const statusText = statusCell.querySelector('span:last-child');
if (statusSpan) {
statusSpan.className = `device-status ${deviceData.online ? 'status-online' : 'status-offline'}`;
}
if (statusText) {
statusText.textContent = deviceData.online ? '在线' : '离线';
}
// 更新最后活跃时间
const lastSeenCell = row.cells[3];
if (lastSeenCell) {
lastSeenCell.textContent = this.#formatTimestamp(deviceData.last_seen);
}
// 更新电池电量
const batteryCell = row.cells[5];
if (batteryCell && deviceData.battery_level !== undefined) {
batteryCell.innerHTML = this.#createBatteryIndicator(deviceData.battery_level);
}
}
/**
* @private
* @brief 创建设备类型名称
* @param {string} type 设备类型代码
* @return {string} 设备类型名称
*/
#getDeviceTypeName(type) {
const typeMap = {
'temperature': '温度传感器',
'humidity': '湿度传感器',
'smartplug': '智能插座',
'camera': '摄像头',
'gateway': '网关设备',
'controller': '控制器',
'unknown': '未知设备'
};
return typeMap[type] || type;
}
/**
* @private
* @brief 格式化时间戳
* @param {number} timestamp Unix时间戳
* @return {string} 格式化后的时间字符串
*/
#formatTimestamp(timestamp) {
if (!timestamp) return '从未';
const now = Date.now();
const diff = now - timestamp;
if (diff < 60000) { // 1分钟内
return '刚刚';
} else if (diff < 3600000) { // 1小时内
const minutes = Math.floor(diff / 60000);
return `${minutes}分钟前`;
} else if (diff < 86400000) { // 1天内
const hours = Math.floor(diff / 3600000);
return `${hours}小时前`;
} else {
const date = new Date(timestamp);
return date.toLocaleDateString('zh-CN');
}
}
/**
* @private
* @brief 创建电池电量指示器
* @param {number} level 电池电量百分比 (0-100)
* @return {string} HTML字符串
*/
#createBatteryIndicator(level) {
let color = 'success';
let icon = 'fa-battery-full';
if (level < 20) {
color = 'danger';
icon = 'fa-battery-empty';
} else if (level < 50) {
color = 'warning';
icon = 'fa-battery-half';
} else if (level < 80) {
color = 'info';
icon = 'fa-battery-three-quarters';
}
return `
<div class="d-flex align-items-center">
<i class="fas ${icon} text-${color} me-2"></i>
<span>${level}%</span>
</div>
`;
}
/**
* @private
* @brief 更新连接统计
*/
#updateConnectionStats() {
// 计算在线设备数量
let onlineCount = 0;
let totalCount = 0;
this.#deviceCache.forEach(device => {
totalCount++;
if (device.online) {
onlineCount++;
}
});
// 更新UI
const onlineElement = document.getElementById('online-devices');
const totalElement = document.getElementById('total-devices');
const progressElement = document.getElementById('connection-progress');
const rateElement = document.getElementById('online-rate');
if (onlineElement) onlineElement.textContent = onlineCount;
if (totalElement) totalElement.textContent = totalCount;
if (progressElement && rateElement && totalCount > 0) {
const rate = Math.round((onlineCount / totalCount) * 100);
progressElement.style.width = `${rate}%`;
rateElement.textContent = `${rate}%`;
}
}
/**
* @private
* @brief 更新服务器统计
* @param {Object} stats 服务器统计数据
*/
#updateServerStats(stats) {
// 更新运行时间
const uptimeElement = document.getElementById('uptime');
if (uptimeElement && stats.uptime) {
const hours = Math.floor(stats.uptime / 3600);
uptimeElement.textContent = hours;
}
// 更新CPU使用率
const cpuElement = document.getElementById('cpu-usage');
if (cpuElement && stats.cpu_usage !== undefined) {
cpuElement.textContent = `${stats.cpu_usage.toFixed(1)}%`;
// 根据使用率改变颜色
if (stats.cpu_usage > 80) {
cpuElement.className = 'stat-value text-danger';
} else if (stats.cpu_usage > 60) {
cpuElement.className = 'stat-value text-warning';
} else {
cpuElement.className = 'stat-value text-success';
}
}
}
/**
* @private
* @brief 更新消息统计
* @param {Object} stats 消息统计数据
*/
#updateMessageStats(stats) {
// 更新总消息数
const totalMsgElement = document.getElementById('total-messages');
if (totalMsgElement && stats.total_messages !== undefined) {
totalMsgElement.textContent = this.#formatNumber(stats.total_messages);
}
// 更新每秒消息数
const msgPerSecElement = document.getElementById('messages-per-sec');
if (msgPerSecElement && stats.messages_per_second !== undefined) {
msgPerSecElement.textContent = stats.messages_per_second.toFixed(1);
}
// 更新消息速率图表
this.#updateMessageRateChart(stats);
}
/**
* @private
* @brief 更新统计图表
* @param {Object} stats 统计数据
*/
#updateStatsCharts(stats) {
// 这里可以添加更多统计图表的更新逻辑
// 例如:CPU使用率历史、内存使用历史等
}
/**
* @private
* @brief 更新最后更新时间
*/
#updateLastUpdateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const updateTimeElement = document.getElementById('update-time');
if (updateTimeElement) {
updateTimeElement.textContent = timeString;
}
}
/**
* @private
* @brief 添加告警到列表
* @param {Object} alertData 告警数据
*/
#addAlertToList(alertData) {
const alertList = document.getElementById('alert-list');
if (!alertList) return;
// 创建告警元素
const alertElement = document.createElement('div');
alertElement.className = `alert alert-${this.#getAlertLevelClass(alertData.level)}`;
alertElement.innerHTML = `
<div class="d-flex justify-content-between align-items-start">
<div>
<strong>
<i class="fas ${this.#getAlertIcon(alertData.level)} me-2"></i>
${alertData.title || '系统告警'}
</strong>
<div class="mt-1">${alertData.message}</div>
<small class="text-muted">
${new Date(alertData.timestamp).toLocaleString('zh-CN')}
${alertData.device_id ? ` - 设备: ${alertData.device_id}` : ''}
</small>
</div>
<button type="button" class="btn-close" οnclick="this.parentElement.parentElement.remove(); Dashboard.getInstance().updateAlertCount();"></button>
</div>
`;
// 添加到列表顶部
if (alertList.firstChild) {
alertList.insertBefore(alertElement, alertList.firstChild);
} else {
alertList.appendChild(alertElement);
}
// 限制告警数量(最多保留10条)
const maxAlerts = 10;
while (alertList.children.length > maxAlerts) {
alertList.removeChild(alertList.lastChild);
}
}
/**
* @private
* @brief 获取告警级别对应的CSS类
* @param {string} level 告警级别
* @return {string} CSS类名
*/
#getAlertLevelClass(level) {
const levelMap = {
'info': 'info',
'warning': 'warning',
'critical': 'danger',
'error': 'danger'
};
return levelMap[level] || 'info';
}
/**
* @private
* @brief 获取告警图标
* @param {string} level 告警级别
* @return {string} Font Awesome图标类名
*/
#getAlertIcon(level) {
const iconMap = {
'info': 'fa-info-circle',
'warning': 'fa-exclamation-triangle',
'critical': 'fa-skull-crossbones',
'error': 'fa-bug'
};
return iconMap[level] || 'fa-bell';
}
/**
* @private
* @brief 更新告警计数
*/
#updateAlertCount() {
const alertList = document.getElementById('alert-list');
const alertCountElement = document.getElementById('alert-count');
if (alertList && alertCountElement) {
const count = alertList.children.length;
alertCountElement.textContent = count;
// 如果没有告警,显示提示信息
if (count === 0) {
const noAlertsElement = document.createElement('div');
noAlertsElement.className = 'alert alert-info';
noAlertsElement.textContent = '暂无告警信息';
alertList.appendChild(noAlertsElement);
}
}
}
/**
* @private
* @brief 显示桌面通知
* @param {Object} alertData 告警数据
*/
#showDesktopNotification(alertData) {
// 检查通知权限
if (Notification.permission === 'granted') {
new Notification(alertData.title || 'IoT平台告警', {
body: alertData.message,
icon: '/favicon.ico',
tag: `alert-${alertData.timestamp}`
});
} else if (Notification.permission !== 'denied') {
// 请求通知权限
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
this.#showDesktopNotification(alertData);
}
});
}
}
/**
* @private
* @brief 播放告警声音
* @param {string} level 告警级别
*/
#playAlertSound(level) {
// 创建音频上下文(如果需要)
if (!window.alertAudioContext) {
window.alertAudioContext = new (window.AudioContext || window.webkitAudioContext)();
}
const audioContext = window.alertAudioContext;
// 创建振荡器
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
// 根据告警级别设置频率
let frequency = 800;
if (level === 'critical') {
frequency = 1200;
}
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
oscillator.type = 'sine';
// 设置音量包络
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(0.1, audioContext.currentTime + 0.1);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.5);
// 连接节点
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// 播放声音
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.5);
}
/**
* @private
* @brief 检查设备超时
*/
#checkDeviceTimeouts() {
const now = Date.now();
const timeout = 5 * 60 * 1000; // 5分钟超时
this.#deviceCache.forEach((device, deviceId) => {
if (device.online && now - device.last_seen > timeout) {
// 设备超时,标记为离线
device.online = false;
this.#updateDeviceTable(deviceId, device);
this.#updateConnectionStats();
console.log(`Device ${deviceId} timed out`);
}
});
}
/**
* @private
* @brief 调整图表大小
*/
#resizeCharts() {
this.#charts.forEach(chart => {
chart.resize();
});
}
/**
* @private
* @brief 处理页面可见性变化
*/
#handleVisibilityChange() {
if (document.hidden) {
// 页面不可见,暂停一些更新
console.log('Page hidden, reducing updates');
} else {
// 页面可见,恢复正常更新
console.log('Page visible, resuming updates');
// 立即请求最新数据
if (this.#connected) {
this.#requestSystemStats();
this.#refreshDevices();
}
}
}
/**
* @private
* @brief 请求初始数据
*/
#requestInitialData() {
// 请求系统状态
this.#requestSystemStats();
// 请求设备列表
this.#requestDeviceList();
// 请求历史数据
this.#requestHistoricalData();
}
/**
* @private
* @brief 请求系统统计信息
*/
#requestSystemStats() {
this.#sendWebSocketMessage({
type: 'get_system_stats',
timestamp: Date.now()
});
}
/**
* @private
* @brief 请求设备列表
*/
#requestDeviceList() {
this.#sendWebSocketMessage({
type: 'get_device_list',
timestamp: Date.now()
});
}
/**
* @private
* @brief 请求历史数据
*/
#requestHistoricalData() {
this.#sendWebSocketMessage({
type: 'get_historical_data',
timestamp: Date.now(),
hours: 24 // 请求24小时数据
});
}
/**
* @private
* @brief 刷新设备列表
*/
#refreshDevices() {
this.#requestDeviceList();
// 显示刷新状态
const refreshBtn = document.getElementById('refresh-devices');
if (refreshBtn) {
const originalHtml = refreshBtn.innerHTML;
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 刷新中';
refreshBtn.disabled = true;
setTimeout(() => {
refreshBtn.innerHTML = originalHtml;
refreshBtn.disabled = false;
}, 1000);
}
}
/**
* @public
* @brief 发送设备命令
* @param {string} deviceId 设备ID
* @param {string} command 命令类型
* @param {Object} parameters 命令参数
*/
sendCommand(deviceId, command, parameters = {}) {
const message = {
type: 'device_command',
command_id: `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
device_id: deviceId,
command: command,
parameters: parameters,
timestamp: Date.now()
};
this.#sendWebSocketMessage(message);
// 显示命令发送状态
this.#showToast(`命令已发送到设备 ${deviceId}`, 'info');
console.log(`Command sent to ${deviceId}: ${command}`, parameters);
}
/**
* @private
* @brief 显示命令响应
* @param {string} commandId 命令ID
* @param {boolean} success 是否成功
* @param {string} message 响应消息
*/
#showCommandResponse(commandId, success, message) {
const type = success ? 'success' : 'error';
const title = success ? '命令执行成功' : '命令执行失败';
this.#showToast(`${title}: ${message}`, type);
}
/**
* @private
* @brief 显示Toast通知
* @param {string} message 消息内容
* @param {string} type 消息类型 (success, error, warning, info)
*/
#showToast(message, type = 'info') {
// 创建Toast容器(如果不存在)
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3';
document.body.appendChild(toastContainer);
}
// 创建Toast元素
const toastId = `toast-${Date.now()}`;
const toastElement = document.createElement('div');
toastElement.id = toastId;
toastElement.className = `toast align-items-center text-bg-${type} border-0`;
toastElement.setAttribute('role', 'alert');
toastElement.setAttribute('aria-live', 'assertive');
toastElement.setAttribute('aria-atomic', 'true');
toastElement.innerHTML = `
<div class="d-flex">
<div class="toast-body">
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto"
data-bs-dismiss="toast"></button>
</div>
`;
toastContainer.appendChild(toastElement);
// 使用Bootstrap Toast显示
const toast = new bootstrap.Toast(toastElement, {
delay: 3000,
autohide: true
});
toast.show();
// 自动移除Toast元素
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
/**
* @private
* @brief 显示连接错误
* @param {string} message 错误消息
*/
#showConnectionError(message) {
// 在页面顶部显示错误横幅
let errorBanner = document.getElementById('connection-error-banner');
if (!errorBanner) {
errorBanner = document.createElement('div');
errorBanner.id = 'connection-error-banner';
errorBanner.className = 'alert alert-danger alert-dismissible fade show mb-0 rounded-0';
errorBanner.style.position = 'sticky';
errorBanner.style.top = '0';
errorBanner.style.zIndex = '1050';
errorBanner.innerHTML = `
<div class="container-fluid">
<i class="fas fa-exclamation-triangle me-2"></i>
<span id="connection-error-message"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
document.body.insertBefore(errorBanner, document.body.firstChild);
}
const messageElement = document.getElementById('connection-error-message');
if (messageElement) {
messageElement.textContent = message;
}
errorBanner.classList.remove('d-none');
}
/**
* @private
* @brief 格式化数字(添加千位分隔符)
* @param {number} num 要格式化的数字
* @return {string} 格式化后的字符串
*/
#formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
/**
* @private
* @brief 添加观察者
* @param {Function} callback 回调函数
*/
#addObserver(callback) {
if (typeof callback === 'function') {
this.#observers.push(callback);
}
}
/**
* @private
* @brief 移除观察者
* @param {Function} callback 要移除的回调函数
*/
#removeObserver(callback) {
const index = this.#observers.indexOf(callback);
if (index > -1) {
this.#observers.splice(index, 1);
}
}
/**
* @private
* @brief 通知所有观察者
* @param {string} event 事件名称
* @param {Object} data 事件数据
*/
#notifyObservers(event, data) {
this.#observers.forEach(callback => {
try {
callback(event, data);
} catch (error) {
console.error('Observer callback error:', error);
}
});
}
/**
* @private
* @brief 显示设备详情
* @param {string} deviceId 设备ID
*/
#showDeviceDetails(deviceId) {
const device = this.#deviceCache.get(deviceId);
if (!device) return;
// 设置模态框标题
const titleElement = document.getElementById('deviceDetailTitle');
if (titleElement) {
titleElement.textContent = `设备详情 - ${deviceId}`;
}
// 更新设备详情内容
const contentElement = document.getElementById('deviceDetailContent');
if (contentElement) {
contentElement.innerHTML = this.#createDeviceDetailContent(device);
}
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('deviceDetailModal'));
modal.show();
}
/**
* @private
* @brief 创建设备详情内容
* @param {Object} device 设备数据
* @return {string} HTML字符串
*/
#createDeviceDetailContent(device) {
return `
<div class="row">
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header">
<i class="fas fa-info-circle me-2"></i>
基本信息
</div>
<div class="card-body">
<table class="table table-sm">
<tr>
<th>设备ID:</th>
<td>${device.id}</td>
</tr>
<tr>
<th>设备类型:</th>
<td>${this.#getDeviceTypeName(device.type)}</td>
</tr>
<tr>
<th>当前状态:</th>
<td>
<span class="device-status ${device.online ? 'status-online' : 'status-offline'}"></span>
${device.online ? '在线' : '离线'}
</td>
</tr>
<tr>
<th>最后活跃:</th>
<td>${this.#formatTimestamp(device.last_seen)}</td>
</tr>
<tr>
<th>数据点数:</th>
<td>${device.data_history ? device.data_history.length : 0}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card mb-3">
<div class="card-header">
<i class="fas fa-chart-line me-2"></i>
最近数据
</div>
<div class="card-body">
${this.#createRecentDataTable(device)}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<i class="fas fa-cogs me-2"></i>
设备控制
</div>
<div class="card-body">
<div class="btn-group" role="group">
<button class="btn btn-primary"
οnclick="Dashboard.getInstance().sendCommand('${device.id}', 'ping')">
<i class="fas fa-sync me-2"></i>
发送心跳
</button>
<button class="btn btn-warning"
οnclick="Dashboard.getInstance().sendCommand('${device.id}', 'reboot')">
<i class="fas fa-redo me-2"></i>
重启设备
</button>
<button class="btn btn-info"
οnclick="Dashboard.getInstance().sendCommand('${device.id}', 'get_config')">
<i class="fas fa-cog me-2"></i>
获取配置
</button>
<button class="btn btn-danger"
οnclick="Dashboard.getInstance().sendCommand('${device.id}', 'factory_reset')">
<i class="fas fa-trash-alt me-2"></i>
恢复出厂
</button>
</div>
</div>
</div>
</div>
</div>
`;
}
/**
* @private
* @brief 创建最近数据表格
* @param {Object} device 设备数据
* @return {string} HTML字符串
*/
#createRecentDataTable(device) {
if (!device.data_history || device.data_history.length === 0) {
return '<p class="text-muted text-center">暂无数据</p>';
}
let tableHtml = `
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>时间</th>
<th>数据</th>
</tr>
</thead>
<tbody>
`;
// 显示最近5条数据
const recentData = device.data_history.slice(-5).reverse();
recentData.forEach(dataPoint => {
const time = new Date(dataPoint.timestamp).toLocaleString('zh-CN');
let dataStr = '';
if (typeof dataPoint.data === 'object') {
dataStr = Object.entries(dataPoint.data)
.map(([key, value]) => `${key}: ${value}`)
.join(', ');
} else {
dataStr = dataPoint.data.toString();
}
tableHtml += `
<tr>
<td>${time}</td>
<td>${dataStr}</td>
</tr>
`;
});
tableHtml += `
</tbody>
</table>
</div>
`;
return tableHtml;
}
/**
* @private
* @brief 保存新设备
*/
#saveDevice() {
const deviceId = document.getElementById('deviceId').value.trim();
const deviceType = document.getElementById('deviceType').value;
const deviceGroup = document.getElementById('deviceGroup').value;
const description = document.getElementById('deviceDescription').value.trim();
// 验证输入
if (!deviceId) {
this.#showToast('请输入设备ID', 'warning');
return;
}
if (!deviceType) {
this.#showToast('请选择设备类型', 'warning');
return;
}
// 发送添加设备请求
this.#sendWebSocketMessage({
type: 'add_device',
device_id: deviceId,
device_type: deviceType,
group: deviceGroup || undefined,
description: description || undefined,
timestamp: Date.now()
});
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('addDeviceModal'));
if (modal) {
modal.hide();
}
// 清空表单
document.getElementById('addDeviceForm').reset();
// 显示成功消息
this.#showToast(`设备 ${deviceId} 添加请求已发送`, 'success');
}
/**
* @private
* @brief 切换深色模式
* @param {boolean} enabled 是否启用深色模式
*/
#toggleDarkMode(enabled) {
if (enabled) {
document.documentElement.setAttribute('data-bs-theme', 'dark');
localStorage.setItem('darkMode', 'enabled');
} else {
document.documentElement.setAttribute('data-bs-theme', 'light');
localStorage.setItem('darkMode', 'disabled');
}
}
/**
* @private
* @brief 初始化深色模式
*/
#initDarkMode() {
const darkModePref = localStorage.getItem('darkMode');
const darkModeSwitch = document.getElementById('darkModeSwitch');
if (darkModePref === 'enabled' ||
(darkModePref === null && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.setAttribute('data-bs-theme', 'dark');
if (darkModeSwitch) {
darkModeSwitch.checked = true;
}
}
}
/**
* @private
* @brief 更改图表时间范围
* @param {string} range 时间范围(小时)
*/
#changeChartTimeRange(range) {
// 根据选择的时间范围请求历史数据
this.#sendWebSocketMessage({
type: 'get_historical_data',
timestamp: Date.now(),
hours: parseInt(range)
});
console.log(`Chart time range changed to ${range} hours`);
}
}
// 全局辅助函数
/**
* @brief 初始化当前时间显示(独立函数)
*/
function updateCurrentTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dateString = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
const timeElement = document.getElementById('current-time');
if (timeElement) {
timeElement.textContent = `${dateString} ${timeString}`;
}
}
/**
* @brief 初始化深色模式(独立函数)
*/
function initDarkMode() {
const darkModePref = localStorage.getItem('darkMode');
const darkModeSwitch = document.getElementById('darkModeSwitch');
if (darkModePref === 'enabled' ||
(darkModePref === null && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.setAttribute('data-bs-theme', 'dark');
if (darkModeSwitch) {
darkModeSwitch.checked = true;
}
}
}
/**
* @brief 初始化WebSocket连接(独立函数)
*/
function initWebSocket() {
// 创建Dashboard实例(单例模式)
const dashboard = Dashboard.getInstance();
console.log('WebSocket connection initialized via Dashboard');
}
/**
* @brief 初始化图表(独立函数)
*/
function initCharts() {
// 图表初始化由Dashboard类处理
console.log('Charts initialization delegated to Dashboard');
}
/**
* @brief 加载设备列表(独立函数)
*/
function loadDevices() {
// 设备加载由Dashboard类处理
console.log('Device loading delegated to Dashboard');
}
/**
* @brief 绑定事件监听器(独立函数)
*/
function bindEventListeners() {
// 事件监听器由Dashboard类处理
console.log('Event listeners delegated to Dashboard');
}
设计模式分析(第六部分)
1. 单例模式(Singleton Pattern)
-
应用位置:
Dashboard类的静态实例管理 -
处理突发事件:
-
重复初始化:防止Dashboard被多次实例化
-
状态不一致:确保全局只有一个状态源
-
资源浪费:避免重复创建WebSocket连接
-
-
工程作用:
-
全局状态管理
-
资源共享
-
统一的接口访问点
-
-
性能分析:
-
内存节省:只创建一个实例
-
初始化开销:首次访问时创建,后续快速访问
-
线程安全:JavaScript单线程环境自然线程安全
-
2. 观察者模式(Observer Pattern)
-
应用位置:
Dashboard中的观察者回调系统 -
处理突发事件:
-
数据更新通知:多个组件需要响应同一数据变化
-
组件解耦:数据源不依赖具体UI组件
-
动态订阅:运行时添加/移除观察者
-
-
工程作用:
-
松耦合的事件处理
-
支持一对多通知
-
易于扩展新观察者
-
-
性能分析:
-
通知开销:O(n)时间,n为观察者数量
-
内存开销:每个观察者函数引用
-
事件冒泡:支持事件传播
-
3. 策略模式(Strategy Pattern)
-
应用位置:数据处理的多种策略
-
处理突发事件:
-
数据格式变化:不同设备类型使用不同处理策略
-
处理逻辑变更:运行时切换处理算法
-
条件分支复杂:简化复杂的if-else逻辑
-
-
工程作用:
-
封装算法变化
-
提高代码复用性
-
易于测试和维护
-
4. 工厂方法模式
-
应用位置:UI组件动态创建
-
处理突发事件:
-
动态UI生成:根据数据动态创建表格行、图表等
-
组件复用:工厂方法创建可复用组件
-
配置变化:通过工厂参数定制组件
-
运行性能分析
前端性能优化:
-
虚拟DOM操作:最小化DOM操作,批量更新
-
数据缓冲:限制历史数据大小,防止内存泄漏
-
事件委托:使用事件委托减少事件监听器数量
-
图表优化:限制数据点数量,避免性能下降
内存管理:
-
缓存策略:设备数据缓存,减少网络请求
-
垃圾回收:及时清理不再需要的DOM元素
-
对象池:复用Toast、Alert等UI组件
网络优化:
-
WebSocket重连:指数退避算法
-
数据压缩:JSON数据轻量传输
-
请求合并:批量请求减少连接数
数据流结构设计
1. WebSocket消息格式:
{
type: 'device_data', // 消息类型
timestamp: 1640995200000, // Unix时间戳
device_id: 'sensor_001', // 设备ID
device_type: 'temperature', // 设备类型
data: { // 传感器数据
temperature: 25.5,
humidity: 60
},
metadata: { // 元数据
signal_strength: -50,
battery_level: 85
}
}
2. 前端数据缓存结构:
设备缓存结构:
Map {
"device_id": {
id: "sensor_001",
type: "temperature",
online: true,
last_seen: 1640995200000,
data_history: [
{timestamp: 1640995200000, data: {...}},
...
],
metadata: {...}
},
...
}
3. 图表数据缓冲区:
数据缓冲区结构:
{
temperature: [25.5, 26.0, ...], // 温度数据数组
humidity: [60, 61, ...], // 湿度数据数组
power: [120, 125, ...], // 功率数据数组
timestamp: [Date, Date, ...] // 时间戳数组
}
突发事件处理策略
| 突发事件 | 处理策略 | 设计模式应用 |
|---|---|---|
| 网络断开 | WebSocket重连,指数退避 | 观察者模式通知连接状态 |
| 数据风暴 | 数据缓冲,限制更新频率 | 策略模式的流控策略 |
| 内存泄漏 | 定期清理,对象池 | 工厂模式的资源管理 |
| UI卡顿 | 虚拟滚动,分批渲染 | 策略模式的渲染优化 |
| 浏览器兼容 | 特性检测,降级方案 | 工厂模式的组件创建 |
第六部分总结:实现了完整的Web前端仪表板,采用单例模式管理全局状态,观察者模式处理事件通知,策略模式处理不同类型的数据。前端具备实时数据展示、设备管理、告警通知和图表可视化功能,与后端服务器通过WebSocket进行实时通信。
整个IoT物联网平台项目现已完成,包含:
-
C++后端服务器:高性能WebSocket处理、设备管理、消息路由
-
协议定义:二进制消息格式,优化网络传输
-
Web前端:响应式仪表板,实时数据可视化
-
设计模式应用:反应器模式、责任链模式、发布-订阅模式、单例模式等
系统具备完整的IoT平台功能,支持大规模设备连接、实时数据监控和设备控制,适用于工业物联网、智能家居、环境监测等多种场景。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)