Electron物联网控制:设备连接与状态监控
概述
在现代物联网(IoT)应用开发中,桌面应用程序扮演着关键角色,它们需要与各种硬件设备进行通信、监控设备状态并提供直观的用户界面。Electron作为跨平台桌面应用开发框架,凭借其强大的Node.js后端能力和丰富的Web前端生态,成为物联网控制应用的理想选择。
本文将深入探讨如何使用Electron构建专业的物联网控制应用,涵盖设备连接、状态监控、数据可视化等核心功能。
物联网应用架构设计
系统架构图
核心模块功能
| 模块名称 | 功能描述 | 关键技术 |
|---|---|---|
| 设备连接管理 | 负责设备的发现、连接、断开和状态监控 | SerialPort, node-hid, usb |
| 数据协议处理 | 解析设备数据格式,处理通信协议 | Buffer, Protocol Buffers |
| 状态监控 | 实时监控设备状态,异常检测 | EventEmitter, WebSocket |
| 数据存储 | 设备数据持久化存储 | SQLite, LevelDB |
| 用户界面 | 提供直观的设备控制界面 | React/Vue, Chart.js |
设备连接实现
串口设备连接
const { app, BrowserWindow, ipcMain } = require('electron');
const SerialPort = require('serialport');
const Readline = require('@serialport/parser-readline');
class SerialDeviceManager {
constructor() {
this.ports = new Map();
this.connectedDevices = new Map();
}
// 发现可用串口
async discoverPorts() {
try {
const ports = await SerialPort.list();
return ports.filter(port =>
port.vendorId && port.productId && port.path
);
} catch (error) {
console.error('发现串口失败:', error);
return [];
}
}
// 连接串口设备
async connectDevice(portInfo, options = {}) {
const { path, baudRate = 9600, dataBits = 8, stopBits = 1, parity = 'none' } = options;
try {
const port = new SerialPort({
path: portInfo.path,
baudRate,
dataBits,
stopBits,
parity
});
const parser = port.pipe(new Readline({ delimiter: '\r\n' }));
port.on('open', () => {
console.log(`串口 ${portInfo.path} 连接成功`);
this.connectedDevices.set(portInfo.path, { port, parser });
// 发送连接成功事件到渲染进程
mainWindow.webContents.send('serial-device-connected', {
path: portInfo.path,
status: 'connected'
});
});
port.on('error', (err) => {
console.error('串口错误:', err);
this.handleDeviceError(portInfo.path, err);
});
port.on('close', () => {
console.log(`串口 ${portInfo.path} 已关闭`);
this.connectedDevices.delete(portInfo.path);
});
// 监听数据接收
parser.on('data', (data) => {
this.handleIncomingData(portInfo.path, data);
});
return true;
} catch (error) {
console.error('连接串口失败:', error);
return false;
}
}
// 处理接收到的数据
handleIncomingData(portPath, data) {
try {
const parsedData = this.parseDeviceData(data);
// 更新设备状态
this.updateDeviceStatus(portPath, parsedData);
// 发送数据到渲染进程
mainWindow.webContents.send('device-data-update', {
port: portPath,
data: parsedData,
timestamp: Date.now()
});
} catch (error) {
console.error('数据处理错误:', error);
}
}
// 发送数据到设备
sendData(portPath, data) {
const device = this.connectedDevices.get(portPath);
if (device && device.port.isOpen) {
device.port.write(data + '\r\n');
}
}
}
USB设备连接
const usb = require('usb');
class USBDeviceManager {
constructor() {
this.devices = new Map();
this.setupUSBEvents();
}
setupUSBEvents() {
// 监听USB设备连接事件
usb.on('attach', (device) => {
this.handleDeviceAttach(device);
});
// 监听USB设备断开事件
usb.on('detach', (device) => {
this.handleDeviceDetach(device);
});
}
async handleDeviceAttach(device) {
try {
device.open();
const deviceInfo = {
vendorId: device.deviceDescriptor.idVendor,
productId: device.deviceDescriptor.idProduct,
manufacturer: device.deviceDescriptor.iManufacturer,
product: device.deviceDescriptor.iProduct,
serialNumber: device.deviceDescriptor.iSerialNumber
};
this.devices.set(device.deviceAddress, {
device,
info: deviceInfo,
status: 'connected'
});
// 通知渲染进程
mainWindow.webContents.send('usb-device-attached', deviceInfo);
} catch (error) {
console.error('USB设备连接失败:', error);
}
}
handleDeviceDetach(device) {
const deviceAddress = device.deviceAddress;
if (this.devices.has(deviceAddress)) {
const deviceInfo = this.devices.get(deviceAddress).info;
this.devices.delete(deviceAddress);
// 通知渲染进程
mainWindow.webContents.send('usb-device-detached', deviceInfo);
}
}
// 获取所有连接的USB设备
getConnectedDevices() {
return Array.from(this.devices.values()).map(d => d.info);
}
}
状态监控系统
实时状态监控
class DeviceStatusMonitor {
constructor() {
this.statusData = new Map();
this.monitoringIntervals = new Map();
this.alertThresholds = {
temperature: { min: -10, max: 85 },
humidity: { min: 10, max: 90 },
voltage: { min: 3.0, max: 5.5 }
};
}
// 开始监控设备
startMonitoring(deviceId, interval = 1000) {
if (this.monitoringIntervals.has(deviceId)) {
this.stopMonitoring(deviceId);
}
const intervalId = setInterval(() => {
this.checkDeviceStatus(deviceId);
}, interval);
this.monitoringIntervals.set(deviceId, intervalId);
}
// 检查设备状态
async checkDeviceStatus(deviceId) {
try {
const status = await this.readDeviceStatus(deviceId);
this.updateStatusData(deviceId, status);
this.checkAlerts(deviceId, status);
} catch (error) {
console.error(`设备 ${deviceId} 状态检查失败:`, error);
this.handleDeviceError(deviceId, error);
}
}
// 更新状态数据
updateStatusData(deviceId, status) {
const currentData = this.statusData.get(deviceId) || [];
// 保留最近100条记录
if (currentData.length >= 100) {
currentData.shift();
}
currentData.push({
...status,
timestamp: Date.now()
});
this.statusData.set(deviceId, currentData);
// 发送实时更新
mainWindow.webContents.send('status-update', {
deviceId,
status,
history: currentData.slice(-10) // 发送最近10条记录
});
}
// 检查警报条件
checkAlerts(deviceId, status) {
const alerts = [];
Object.keys(this.alertThresholds).forEach(key => {
if (status[key] !== undefined) {
const threshold = this.alertThresholds[key];
if (status[key] < threshold.min || status[key] > threshold.max) {
alerts.push({
type: key,
value: status[key],
threshold,
severity: this.calculateSeverity(status[key], threshold)
});
}
}
});
if (alerts.length > 0) {
this.triggerAlerts(deviceId, alerts);
}
}
calculateSeverity(value, threshold) {
const range = threshold.max - threshold.min;
const deviation = Math.max(
Math.abs(value - threshold.min),
Math.abs(value - threshold.max)
);
return deviation > range * 0.3 ? 'critical' : 'warning';
}
}
数据可视化界面
<!-- 设备状态面板 -->
<div class="device-panel">
<div class="device-header">
<h3>设备状态监控</h3>
<div class="device-controls">
<button id="refresh-btn">刷新</button>
<select id="device-selector"></select>
</div>
</div>
<div class="status-grid">
<div class="status-card temperature">
<h4>温度</h4>
<div class="value" id="temp-value">-- °C</div>
<div class="trend" id="temp-trend"></div>
</div>
<div class="status-card humidity">
<h4>湿度</h4>
<div class="value" id="humidity-value">-- %</div>
<div class="trend" id="humidity-trend"></div>
</div>
<div class="status-card voltage">
<h4>电压</h4>
<div class="value" id="voltage-value">-- V</div>
<div class="trend" id="voltage-trend"></div>
</div>
</div>
<!-- 实时图表 -->
<div class="chart-container">
<canvas id="status-chart" width="800" height="300"></canvas>
</div>
</div>
// 图表初始化
class StatusChart {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.chart = null;
this.data = {
labels: [],
datasets: [
{
label: '温度 (°C)',
data: [],
borderColor: 'rgb(255, 99, 132)',
tension: 0.1
},
{
label: '湿度 (%)',
data: [],
borderColor: 'rgb(54, 162, 235)',
tension: 0.1
}
]
};
this.initChart();
}
initChart() {
this.chart = new Chart(this.ctx, {
type: 'line',
data: this.data,
options: {
responsive: true,
plugins: {
title: {
display: true,
text: '设备状态趋势图'
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}
updateChart(newData) {
// 更新数据
this.data.labels.push(new Date().toLocaleTimeString());
this.data.datasets[0].data.push(newData.temperature);
this.data.datasets[1].data.push(newData.humidity);
// 保持最近60个数据点
if (this.data.labels.length > 60) {
this.data.labels.shift();
this.data.datasets.forEach(dataset => dataset.data.shift());
}
this.chart.update();
}
}
通信协议处理
MQTT通信集成
const mqtt = require('mqtt');
class MQTTClient {
constructor(options = {}) {
this.options = {
brokerUrl: 'mqtt://localhost:1883',
clientId: `electron-iot-${Math.random().toString(16).substr(2, 8)}`,
...options
};
this.client = null;
this.subscriptions = new Map();
this.messageHandlers = new Map();
}
async connect() {
try {
this.client = mqtt.connect(this.options.brokerUrl, {
clientId: this.options.clientId,
clean: true,
connectTimeout: 4000,
reconnectPeriod: 1000
});
this.client.on('connect', () => {
console.log('MQTT连接成功');
this.onConnect();
});
this.client.on('message', (topic, message) => {
this.handleMessage(topic, message);
});
this.client.on('error', (error) => {
console.error('MQTT错误:', error);
this.handleError(error);
});
} catch (error) {
console.error('MQTT连接失败:', error);
throw error;
}
}
subscribe(topic, handler) {
if (this.client && this.client.connected) {
this.client.subscribe(topic, (err) => {
if (!err) {
this.subscriptions.set(topic, true);
this.messageHandlers.set(topic, handler);
}
});
}
}
publish(topic, message, options = {}) {
if (this.client && this.client.connected) {
this.client.publish(topic, JSON.stringify(message), options);
}
}
handleMessage(topic, message) {
try {
const parsedMessage = JSON.parse(message.toString());
const handler = this.messageHandlers.get(topic);
if (handler) {
handler(parsedMessage);
}
// 转发到渲染进程
mainWindow.webContents.send('mqtt-message', {
topic,
message: parsedMessage,
timestamp: Date.now()
});
} catch (error) {
console.error('消息处理错误:', error);
}
}
}
WebSocket实时通信
const WebSocket = require('ws');
class WebSocketServer {
constructor(port = 8080) {
this.port = port;
this.wss = null;
this.clients = new Set();
}
start() {
this.wss = new WebSocket.Server({ port: this.port });
this.wss.on('connection', (ws) => {
console.log('WebSocket客户端连接');
this.clients.add(ws);
ws.on('message', (message) => {
this.handleMessage(ws, message);
});
ws.on('close', () => {
console.log('WebSocket客户端断开');
this.clients.delete(ws);
});
});
console.log(`WebSocket服务器启动在端口 ${this.port}`);
}
broadcast(data) {
const message = JSON.stringify(data);
this.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
handleMessage(ws, message) {
try {
const parsed = JSON.parse(message);
switch (parsed.type) {
case 'device_control':
this.handleDeviceControl(parsed.data);
break;
case 'status_request':
this.handleStatusRequest(ws, parsed.data);
break;
default:
console.warn('未知的消息类型:', parsed.type);
}
} catch (error) {
console.error('消息解析错误:', error);
}
}
}
数据存储与管理
SQLite数据库集成
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
class DeviceDatabase {
constructor() {
this.db = null;
this.initDatabase();
}
initDatabase() {
const dbPath = path.join(app.getPath('userData'), 'devices.db');
this.db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('数据库连接失败:', err);
} else {
console.log('数据库连接成功');
this.createTables();
}
});
}
createTables() {
const tables = [
`CREATE TABLE IF NOT EXISTS devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
connection_type TEXT NOT NULL,
address TEXT,
config TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS device_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id INTEGER,
temperature REAL,
humidity REAL,
voltage REAL,
status TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (device_id) REFERENCES devices (id)
)`,
`CREATE TABLE IF NOT EXISTS device_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id INTEGER,
event_type TEXT,
event_data TEXT,
severity TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (device_id) REFERENCES devices (id)
)`
];
tables.forEach(sql => {
this.db.run(sql, (err) => {
if (err) console.error('创建表失败:', err);
});
});
}
async saveDeviceData(deviceId, data) {
return new Promise((resolve, reject) => {
const sql = `INSERT INTO device_data
(device_id, temperature, humidity, voltage, status)
VALUES (?, ?, ?, ?, ?)`;
this.db.run(sql, [
deviceId,
data.temperature,
data.humidity,
data.voltage,
data.status
], function(err) {
if (err) reject(err);
else resolve(this.lastID);
});
});
}
async getDeviceHistory(deviceId, limit = 100) {
return new Promise((resolve, reject) => {
const sql = `SELECT * FROM device_data
WHERE device_id = ?
ORDER BY timestamp DESC
LIMIT ?`;
this.db.all(sql, [deviceId, limit], (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
}
安全性与错误处理
安全措施实现
class SecurityManager {
constructor() {
this.encryptionKey = null;
this.initSecurity();
}
async initSecurity() {
// 生成或加载加密密钥
this.encryptionKey = await this.loadOrGenerateKey();
}
async encryptData(data) {
const crypto = require('crypto');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv);
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
iv: iv.toString('hex'),
data: encrypted,
authTag: cipher.getAuthTag().toString('hex')
};
}
async decryptData(encryptedData) {
const crypto = require('crypto');
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
this.encryptionKey,
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
validateDeviceCommand(command) {
// 命令验证逻辑
const allowedCommands = ['start', 'stop', 'reset', 'configure'];
const maxParams = 10;
if (!allowedCommands.includes(command.action)) {
throw new Error('无效的命令类型');
}
if (Object.keys(command.parameters || {}).length > maxParams) {
throw new Error('参数数量超出限制');
}
return true;
}
}
错误处理与日志记录
class ErrorHandler {
constructor() {
this.logger = this.setupLogger();
}
setupLogger() {
const { createLogger, format, transports } = require('winston');
const path = require('path');
return createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.json()
),
transports: [
new transports.File({
filename: path.join(app.getPath('userData'), 'error.log'),
level: 'error'
}),
new transports.File({
filename: path.join(app.getPath('userData'), 'combined.log')
})
]
});
}
handleError(error, context = {}) {
const errorInfo = {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
...context
};
this.logger.error(errorInfo);
// 发送错误通知到渲染进程
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('error-occurred', errorInfo);
}
// 根据错误类型采取不同措施
if (error.code === 'DEVICE_DISCONNECTED') {
this.handleDeviceDisconnect(error);
} else if (error.code === 'NETWORK_ERROR') {
this.handleNetworkError(error);
}
}
handleDeviceDisconnect(error) {
// 尝试重新连接设备
setTimeout(() => {
this.attemptReconnect(error.deviceId);
}, 5000);
}
}
性能优化建议
内存管理优化
class PerformanceOptimizer {
constructor() {
this.memoryUsage = {
maxHeapSize: 0,
currentHeapSize: 0
};
this.setupMonitoring();
}
setupMonitoring() {
// 监控内存使用情况
setInterval(() => {
const memoryUsage = process.memoryUsage();
this.memoryUsage.currentHeapSize = memoryUsage.heapUsed;
this.memoryUsage.maxHeapSize = Math.max(
this.memoryUsage.maxHeapSize,
memoryUsage.heapUsed
);
this.checkMemoryThreshold();
}, 30000);
}
checkMemoryThreshold() {
const memoryUsage = process.memoryUsage();
const threshold = 500 * 1024 * 1024; // 500MB
if (memoryUsage.heapUsed > threshold) {
this.cleanupMemory();
}
}
cleanupMemory() {
// 清理过期的设备数据缓存
const now = Date.now();
const oneHourAgo = now - 3600000;
deviceStatusMonitor.cleanupOldData(oneHourAgo);
// 建议垃圾回收
if (global.gc) {
global.gc();
}
}
// 数据分页加载
async loadDataInPages(deviceId, page = 1, pageSize = 100) {
const offset = (page - 1) * pageSize;
return deviceDatabase.getDeviceDataPage(deviceId, offset, pageSize);
}
}
部署与分发
应用打包配置
{
"name": "iot-control-center",
"version": "1.0.0",
"description": "物联网设备控制中心",
"main": "dist/main.js",
"scripts": {
"start": "electron .",
"build": "webpack --mode production",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"postinstall": "electron-builder install-app-deps"
},
"build": {
"appId": "com.example.iotcontrol",
"productName": "IoT控制中心",
"directories": {
"output": "release"
},
"files": [
"dist/**/*",
"node_modules/**/*",
"package.json"
],
"win": {
"target": "nsis",
"icon": "assets/icon.ico"
},
"mac": {
"target": "dmg",
"icon": "assets/icon.icns"
},
"linux": {
"target": "AppImage",
"icon": "assets/icon.png"
}
}
}
总结
Electron为物联网控制应用开发提供了强大的技术基础,结合Node.js的设备连接能力和现代Web技术的用户界面,可以构建出功能丰富、性能优异的跨平台物联网控制解决方案。
关键优势包括:
- 跨平台兼容性:一套代码支持Windows、macOS、Linux
- 丰富的设备支持:通过Node.js生态支持各种硬件接口
- 实时性能:基于Web技术实现流畅的用户体验
- 扩展性强:易于集成新的通信协议和设备类型
通过本文介绍的架构设计和实现方案,开发者可以快速构建专业的物联网控制应用,满足不同场景下的设备监控和控制需求。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



