构建用于设备控制和管理的Node.js Web应用
基于Node.js的设备控制与管理系统
引言
随着物联网技术的飞速发展,设备的智能化和互联性需求逐渐增加。为了满足这一需求,我们开发了一个基于Node.js的设备控制与管理系统。本文将详细介绍系统的设计思路、实现细节以及功能特点,以期为类似项目的开发提供有益的经验和启示。
设计目标
该系统旨在提供一个集中化的、易于使用的Web界面,使用户能够方便地控制和管理多个设备。主要设计目标包括:
-
用户友好性: 提供直观、简洁的界面,使用户能够轻松了解设备状态和进行操作。
-
实时反馈: 系统能够实时获取设备状态信息,确保用户获得最新的设备状态反馈。
-
灵活性: 允许用户通过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/');
});
功能扩展与展望
系统具有一定的灵活性,用户可以通过扩展设备列表和修改前端界面实现更多功能。未来,我们计划增加以下功能:
-
用户身份验证: 添加用户身份验证机制,确保只有授权用户能够访问和控制设备。
-
设备日志记录: 实现设备操作日志的记录和展示,便于用户追踪设
备的历史状态和操作。
- 远程控制: 支持用户通过互联网远程控制设备,提高设备的可访问性。
结论
本文详细介绍了基于Node.js的设备控制与管理系统的设计思路、实现细节以及未来展望。通过该系统,用户能够方便地监控和控制多个设备,提高设备的智能化和可管理性。随着物联网技术的不断发展,我们相信类似的设备控制系统将在未来得到更广泛的应用。
本文作者: 永生
本文链接: https://yys.zone/detail/?id=316
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
评论列表 (0 条评论)