Electron物联网控制:设备连接与状态监控

Electron物联网控制:设备连接与状态监控

【免费下载链接】electron 使用Electron构建跨平台桌面应用程序,支持JavaScript、HTML和CSS 【免费下载链接】electron 项目地址: https://gitcode.com/GitHub_Trending/el/electron

概述

在现代物联网(IoT)应用开发中,桌面应用程序扮演着关键角色,它们需要与各种硬件设备进行通信、监控设备状态并提供直观的用户界面。Electron作为跨平台桌面应用开发框架,凭借其强大的Node.js后端能力和丰富的Web前端生态,成为物联网控制应用的理想选择。

本文将深入探讨如何使用Electron构建专业的物联网控制应用,涵盖设备连接、状态监控、数据可视化等核心功能。

物联网应用架构设计

系统架构图

mermaid

核心模块功能

模块名称功能描述关键技术
设备连接管理负责设备的发现、连接、断开和状态监控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技术实现流畅的用户体验
  • 扩展性强:易于集成新的通信协议和设备类型

通过本文介绍的架构设计和实现方案,开发者可以快速构建专业的物联网控制应用,满足不同场景下的设备监控和控制需求。

【免费下载链接】electron 使用Electron构建跨平台桌面应用程序,支持JavaScript、HTML和CSS 【免费下载链接】electron 项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值