基于Node.js的设备控制与管理系统

引言

随着物联网技术的飞速发展,设备的智能化和互联性需求逐渐增加。为了满足这一需求,我们开发了一个基于Node.js的设备控制与管理系统。本文将详细介绍系统的设计思路、实现细节以及功能特点,以期为类似项目的开发提供有益的经验和启示。

设计目标

该系统旨在提供一个集中化的、易于使用的Web界面,使用户能够方便地控制和管理多个设备。主要设计目标包括:

  1. 用户友好性: 提供直观、简洁的界面,使用户能够轻松了解设备状态和进行操作。

  2. 实时反馈: 系统能够实时获取设备状态信息,确保用户获得最新的设备状态反馈。

  3. 灵活性: 允许用户通过Web界面执行设备操作,并且提供一种可扩展的方式,支持添加更多设备和功能。

技术栈

系统采用Node.js作为后端开发语言,利用HTTP模块搭建服务器。前端界面使用HTML、CSS和JavaScript构建,通过Ajax技术实现异步数据交互。系统中使用了querystring模块处理表单数据,实现了设备状态的实时查询和控制。

 

// 处理所有设备的并行请求
Promise.all(devices.map((device, index) => {
  return new Promise((resolve, reject) => {
    // 构造LED控制请求参数
    const ledOptions = {
      host: device.ip,
      path: '/LED-Control?ledPwm=9999',
      name: device.name
    };

    // 发送LED控制请求
    http.get(ledOptions, (ledResponse) => {
      // 处理LED响应数据
      ledResponse.setEncoding('utf-8');
      ledResponse.on('data', (chunk) => {
        ledHtml += chunk;
      });

      ledResponse.on('end', () => {
        // 解析LED响应数据,生成HTML片段
        const relayStatus = parseRelayStatus(ledHtml);
        const host = ledOptions.host;
        const name = ledOptions.name;
        const listData_on = parseListData(ledHtml, '开');
        const listData_off = parseListData(ledHtml, '关');

        // 解析结果拼接到HTML片段
        resolve(`
          <h1 style="color: ${relayStatus.includes('开启') ? 'green' : 'gray'};" >设备名:${name}(${host})  ${relayStatus}</h1>
          <p>开列表: ${JSON.stringify(listData_on)}</p>
          <p>关列表: ${JSON.stringify(listData_off)}</p>
          <!-- 省略其他HTML生成代码 -->
        `);
      });
    });
  });
})).then(deviceHtmls => {
  // 构建最终HTML页面
  const page = `
    <!DOCTYPE html>
    <html>
    <head>
      <!-- 省略头部信息 -->
    </head>
    <body>
      ${deviceHtmls.join('')}
    </body>
    </html>
  `;

  // 发送HTML页面作为响应
  res.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8' });
  res.end(page);
});

设备状态切换

系统支持通过Web界面切换设备的开关状态。通过POST请求,系统能够接收用户在Web界面上的操作,并通过HTTP请求向设备发送相应的控制指令。

// 处理切换设备状态的POST请求
else if (req.url.startsWith('/toggleDevice') && req.method === 'POST') {
  const deviceIndex = parseInt(req.url.split('/')[2]);

  // 切换设备状态
  toggleDevice(devices[deviceIndex], () => {
    res.writeHead(302, { 'Location': '/' });
    res.end();
  });
} 

设备控制设置

系统支持通过Web界面设置设备的控制参数,例如调整LED的亮度、设置开关时间等。用户通过填写表单并提交,系统通过HTTP请求将设置传递给设备。

// 处理设备控制设置的POST请求
else if (req.url.startsWith('/controlDevice') && req.method === 'POST') {
  const deviceIndex = parseInt(req.url.split('/')[2]);
  const formData = [];

  req.on('data', (chunk) => {
    formData.push(chunk);
  });

  req.on('end', () => {
    // 处理POST请求数据
    const postData = Buffer.concat(formData).toString();
    const parsedData = qs.parse(postData);

    const ledPwm = parsedData.ledPwm;
    const time = parsedData.time;

    // 验证时间格式
    const timeRegex = /^\d{2}:\d{2}:\d{2}$/;
    if (!timeRegex.test(time)) {
      res.writeHead(400, { 'Content-Type': 'text/plain' });
      res.end('Invalid time format. Please use xx:xx:xx format for time.');
      return;
    }

    // 控制设备
    controlDevice(devices[deviceIndex], ledPwm, time, (relayStatus) => {
      res.writeHead(302, { 'Location': '/' });
      res.end();
    });
  });
}

 

实现细节

设备列表与状态查询

系统通过预先定义的设备列表,使用HTTP模块向各个设备发送状态查询请求。通过异步处理,系统能够同时查询多个设备的状态,并将结果实时更新到Web界面。

以下全部代码

// 引入所需模块
const http = require('http');
const qs = require('querystring');

// 设备的 IP 地址和名称
const devices = [
  { ip: '192.168.31.160', name: '电脑2400插座' },
  { ip: '192.168.31.138', name: '充电插座' },
  // 添加更多设备
];

// 创建HTTP服务器
const server = http.createServer((req, res) => {
  // 处理根路径请求
  if (req.url === '/') {
    // 处理所有设备的并行请求
    Promise.all(devices.map((device, index) => {
      // 返回一个Promise,处理每个设备的状态查询
      return new Promise((resolve, reject) => {
        // 构造LED控制请求参数
        const ledOptions = {
          host: device.ip,
          path: '/LED-Control?ledPwm=9999',
          name: device.name
        };

        // 发送LED控制请求
        http.get(ledOptions, (ledResponse) => {
          let ledHtml = '';

          // 处理LED响应数据
          ledResponse.setEncoding('utf-8');
          ledResponse.on('data', (chunk) => {
            ledHtml += chunk;
          });

          ledResponse.on('end', () => {
            // 解析LED响应数据,生成HTML片段
            const relayStatus = parseRelayStatus(ledHtml);
            const host = ledOptions.host;
            const name = ledOptions.name;
            const listData_on = parseListData(ledHtml, '开');
            const listData_off = parseListData(ledHtml, '关');
            
            // 解析结果拼接到HTML片段
            resolve(`
              <h1 style="color: ${relayStatus.includes('开启') ? 'green' : 'gray'};" >设备名:${name}(${host})  ${relayStatus}</h1>
              <p>开列表: ${JSON.stringify(listData_on)}</p>
              <p>关列表: ${JSON.stringify(listData_off)}</p>
              <form action="/toggleDevice/${index}" method="post">
                <button type="submit" name="action" value="toggle" id="toggleButton" style="background-color: ${relayStatus.includes('开启') ? 'gray' : 'green'};">
                  ${relayStatus.includes('开启') ? '关闭' : '开启'}
                </button>
              </form>
              
              ${getControlForm(index)} <!-- 新增的控制表单 -->

              <hr>
            `);
          });
        });
      });
    })).then(deviceHtmls => {
      // 构建最终HTML页面
      const page = `
        <!DOCTYPE html>
        <html>
        <head>
          <meta charset="UTF-8">
          <title>控制台页面</title>
          <style>
            button {
              background-color: gray;
              color: white;
              padding: 10px 20px;
              border: none;
              cursor: pointer;
            }
          </style>
        </head>
        <body>
          ${deviceHtmls.join('')}
        </body>
        </html>
      `;

      // 发送HTML页面作为响应
      res.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8' });
      res.end(page);
    });
  } 
  // 处理切换设备状态的POST请求
  else if (req.url.startsWith('/toggleDevice') && req.method === 'POST') {
    const deviceIndex = parseInt(req.url.split('/')[2]);

    // 切换设备状态
    toggleDevice(devices[deviceIndex], () => {
      res.writeHead(302, { 'Location': '/' });
      res.end();
    });
  } 
  // 处理设备控制设置的POST请求
  else if (req.url.startsWith('/controlDevice') && req.method === 'POST') {
    const deviceIndex = parseInt(req.url.split('/')[2]);
    const formData = [];

    req.on('data', (chunk) => {
      formData.push(chunk);
    });

    req.on('end', () => {
      // 处理POST请求数据
      const postData = Buffer.concat(formData).toString();
      const parsedData = qs.parse(postData);

      const ledPwm = parsedData.ledPwm;
      const time = parsedData.time;

      // 验证时间格式
      const timeRegex = /^\d{2}:\d{2}:\d{2}$/;
      if (!timeRegex.test(time)) {
        res.writeHead(400, { 'Content-Type': 'text/plain' });
        res.end('Invalid time format. Please use xx:xx:xx format for time.');
        return;
      }

      // 控制设备
      controlDevice(devices[deviceIndex], ledPwm, time, (relayStatus) => {
        res.writeHead(302, { 'Location': '/' });
        res.end();
      });
    });
  } 
  // 处理未知路径的请求
  else {
    res.writeHead(404);
    res.end('未找到');
  }
});

// 新增HTML表单元素,提供ledPwm的下拉框和时间的输入框
function getControlForm(deviceIndex) {
  return `
  <br>
  <form action="/controlDevice/${deviceIndex}" method="post" style="display: flex; align-items:

 center; font-family: 'Arial', sans-serif;">
  <label for="ledPwm" style="margin-right: 10px; font-size: 16px;">操作:</label>
  <select name="ledPwm" id="ledPwm" style="margin-right: 10px; padding: 8px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px;">
    <option value="0">增加关机时间</option>
    <option value="1">增加开机时间</option>
    <option value="5">删除时间</option>
  </select>
  <label for="time" style="margin-right: 10px; font-size: 16px;">时间:</label>
  <input type="text" name="time" id="time" placeholder="xx:xx:xx" required style="margin-right: 10px; padding: 8px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px;">
  <button type="submit" name="action" value="control" style="background-color: #ff3333; color: white; padding: 10px 20px; font-size: 16px; border: none; border-radius: 4px;">修改时间</button>
</form>
  `;
}

// 处理LED控制和时间设置的请求
function controlDevice(device, ledPwm, time, callback) {
  const path = `/LED-Control?ledPwm=${ledPwm}&time=${time}`;
  const ledOptions = {
    host: device.ip,
    path: path,
  };

  // 发送LED控制请求
  http.get(ledOptions, (ledResponse) => {
    let ledHtml = '';

    // 处理LED响应数据
    ledResponse.setEncoding('utf-8');
    ledResponse.on('data', (chunk) => {
      ledHtml += chunk;
    });

    ledResponse.on('end', () => {
      // 解析LED响应数据,获取设备状态
      const relayStatus = parseRelayStatus(ledHtml);
      callback(relayStatus);
    });
  });
}

// 解析设备状态
function parseRelayStatus(html) {
  const startTag = '<span id="relayStatus">';
  const endTag = '</span>';
  let startIndex = html.indexOf(startTag);

  if (startIndex !== -1) {
    startIndex += startTag.length;
    let endIndex = html.indexOf(endTag, startIndex);

    if (endIndex !== -1) {
      const relayStatus = html.substring(startIndex, endIndex).trim();
      return relayStatus;
    }
  }

  return '未找到状态信息';
}

// 切换设备状态
function toggleDevice(device, callback) {
  const ledOptions = {
    host: device.ip,
    path: '/LED-Control?ledPwm=9999',
  };

  // 查询设备状态
  http.get(ledOptions, (ledResponse) => {
    let ledHtml = '';

    // 处理LED响应数据
    ledResponse.setEncoding('utf-8');
    ledResponse.on('data', (chunk) => {
      ledHtml += chunk;
    });

    ledResponse.on('end', () => {
      // 解析LED响应数据,切换设备状态
      const relayStatus = parseRelayStatus(ledHtml);

      if (relayStatus.includes('开启')) {
        const closeOptions = {
          host: device.ip,
          path: '/LED-Control?ledPwm=4',
        };
        http.get(closeOptions, callback);
      } else {
        const openOptions = {
          host: device.ip,
          path: '/LED-Control?ledPwm=3',
        };
        http.get(openOptions, callback);
      }
    });
  });
}

// 解析设备列表数据
function parseListData(html, key) {
  const startTag = `<p>${key}列表: `;
  const endTag = '</p>';
  let startIndex = html.indexOf(startTag);

  if (startIndex !== -1) {
    startIndex += startTag.length;
    let endIndex = html.indexOf(endTag, startIndex);

    if (endIndex !== -1) {
      let listData = html.substring(startIndex, endIndex).trim();
      // 使用正则表达式从字符串中提取JSON部分并清理HTML标签
      const jsonDataMatch = listData.match(/.*?<p>/);
      const jsonData = jsonDataMatch ? jsonDataMatch[0] : '';

      // 去除JSON数据中的<p>标签
      const cleanedJsonData = jsonData.replace(/<p>/g, '').replace(/<\/p>/g, '');

      try {
        return cleanedJsonData ? JSON.parse(cleanedJsonData) : [];
      } catch (error) {
        console.error('JSON 解析错误:', error.message);
        return [];
      }
    }
  }

  return [];
}

// 启动服务器监听端口3000
server.listen(3000, () => {
  console.log('应用程序正在监听端口 3000,访问:http://127.0.0.1:3000/');
});

功能扩展与展望

系统具有一定的灵活性,用户可以通过扩展设备列表和修改前端界面实现更多功能。未来,我们计划增加以下功能:

  1. 用户身份验证: 添加用户身份验证机制,确保只有授权用户能够访问和控制设备。

  2. 设备日志记录: 实现设备操作日志的记录和展示,便于用户追踪设

备的历史状态和操作。

  1. 远程控制: 支持用户通过互联网远程控制设备,提高设备的可访问性。

结论

本文详细介绍了基于Node.js的设备控制与管理系统的设计思路、实现细节以及未来展望。通过该系统,用户能够方便地监控和控制多个设备,提高设备的智能化和可管理性。随着物联网技术的不断发展,我们相信类似的设备控制系统将在未来得到更广泛的应用。