Node.js 和 树莓派 - 带 WebSocket 的 Web 服务器
什么是 WebSocket?
WebSocket 允许通过 Web 实时进行双向通信。
WebSocket 可以与普通的 HTTP 服务器一起运行。您可以点击网页浏览器中的按钮,并启用树莓派的 GPIO,从而打开您家中的灯。所有这些都在实时进行,并且通信是双向的!
在本节中,我们将设置一个带 WebSocket 的 Web 服务器。然后创建一个浏览器 UI 来与我们之前的示例交互,即 用按钮控制 LED 的开关。
我需要什么?
本教程需要树莓派。在我们的示例中,我们使用树莓派 3,但本教程应该适用于大多数版本。
您需要
- 安装了 Raspian、互联网、SSH 和 Node.js 的树莓派
- Node.js 的 onoff 模块
- Node.js 的 socket.io 模块
- 1 个 面包板
- 1 个 68 欧姆电阻
- 1 个 1k 欧姆电阻
- 1 个 通孔 LED
- 1 个 按钮开关
- 4 根 母对公跳线
- 1 根 公对公跳线
点击上面列表中的链接查看不同组件的描述。
注意:您需要的电阻可能与我们使用的不同,具体取决于您使用的 LED 类型。大多数小型 LED 只需要一个小电阻,大约 200-500 欧姆。电阻的具体值通常不是关键,但电阻值越小,LED 的亮度越高。
与我们之前的示例相比,我们唯一需要新增的是设置 Web 服务器,以及安装 socket.io 模块。
树莓派和 Node.js 的 Web 服务器
根据本 Node.js 教程中之前的章节,让我们设置一个可以提供 HTML 文件的 Web 服务器。
在我们的 "nodetest" 目录中,创建一个新目录,我们可以用来存放静态 html 文件
pi@w3demopi:~/nodetest $ mkdir public
现在让我们设置一个 Web 服务器。创建一个 Node.js 文件,它打开请求的文件并将内容返回给客户端。如果出现错误,则抛出 404 错误。
pi@w3demopi:~/nodetest $ nano webserver.js
webserver.js
var http = require('http').createServer(handler); // 导入 http 服务器,并使用 handler() 函数创建服务器
var fs = require('fs'); // 导入文件系统模块
http.listen(8080); // 监听端口 8080
function handler (req, res) { // 创建服务器
fs.readFile(__dirname + '/public/index.html', function(err, data) { // 读取 public 文件夹中的 index.html 文件
if (err) {
res.writeHead(404, {'Content-Type': 'text/html'}); // 错误时显示 404
return res.end("404 Not Found");
}
res.writeHead(200, {'Content-Type': 'text/html'}); // 写入 HTML
res.write(data); // 写入来自 index.html 的数据
return res.end();
});
}
转到 "public" 文件夹
pi@w3demopi:~/nodetest $ cd public
并创建一个 HTML 文件,index.html
pi@w3demopi:~/nodetest/public $ nano index.html
index.html
<!DOCTYPE html>
<html>
<body>
<h1>控制 LED 灯</h1>
<input id="light" type="checkbox">LED
</body>
</html>
此文件目前没有功能。现在它只是一个占位符。让我们看看 Web 服务器是否正常工作
pi@w3demopi:~/nodetest/public $ cd ..
pi@w3demopi:~/nodetest $ node webserver.js
使用 http://[RaspberryPi_IP]:8080/ 在浏览器中打开网站。
Web 服务器现在应该已经启动并运行,我们可以继续进行 WebSocket 部分。
为 Node.js 安装 socket.io
在 Web 服务器设置完毕后,更新您的树莓派系统包到最新版本。
更新您的系统包列表
pi@w3demopi:~ $ sudo apt-get update
将所有已安装的包升级到最新版本
pi@w3demopi:~ $ sudo apt-get dist-upgrade
定期执行此操作将使您的树莓派安装保持最新。
要下载并安装最新版本的 socket.io,请使用以下命令
pi@w3demopi:~ $ npm install socket.io --save
在 Web 服务器中添加 WebSocket
现在我们可以使用 WebSocket 在应用程序中。让我们更新我们的 index.html 文件
index.html
<!DOCTYPE html>
<html>
<body>
<h1>控制 LED 灯</h1>
<p><input type="checkbox" id="light"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script> <!-- 包含 socket.io 客户端脚本 -->
<script>
var socket = io(); // 加载 socket.io-client 并连接到提供页面的主机
window.addEventListener("load", function(){ // 页面加载时
var lightbox = document.getElementById("light");
lightbox.addEventListener("change", function() { // 添加复选框更改时的事件监听器
socket.emit("light", Number(this.checked)); // 将按钮状态发送到服务器(作为 1 或 0)
});
});
socket.on('light', function (data) { // 从客户端获取按钮状态
document.getElementById("light").checked = data; // 根据树莓派的按钮更改复选框
socket.emit("light", data); // 将按钮状态发送回服务器
});
</script>
</body>
</html>
以及我们的 webserver.js 文件
webserver.js
var http = require('http').createServer(handler); // 导入 http 服务器,并使用 handler() 函数创建服务器
var fs = require('fs'); // 导入文件系统模块
var io = require('socket.io')(http) // 导入 socket.io 模块并将 http 对象(服务器)传递给它
http.listen(8080); // 监听端口 8080
function handler (req, res) { // 创建服务器
fs.readFile(__dirname + '/public/index.html', function(err, data) { // 读取 public 文件夹中的 index.html 文件
if (err) {
res.writeHead(404, {'Content-Type': 'text/html'}); // 错误时显示 404
return res.end("404 Not Found");
}
res.writeHead(200, {'Content-Type': 'text/html'}); // 写入 HTML
res.write(data); // 写入来自 index.html 的数据
return res.end();
});
}
io.sockets.on('connection', function (socket) {// WebSocket 连接
var lightvalue = 0; // 用于当前状态的静态变量
socket.on('light', function(data) { // 从客户端获取灯开关状态
lightvalue = data;
if (lightvalue) {
console.log(lightvalue); // 打开或关闭 LED,目前我们只会在 console.log 中显示它
}
});
});
让我们测试服务器
pi@w3demopi:~ $ node webserver.js
使用 http://[RaspberryPi_IP]:8080/ 在浏览器中打开网站。
现在服务器应该将复选框的所有更改输出到树莓派的控制台。
客户端正在将更改发送到服务器,服务器正在响应。
让我们添加 按钮控制的 LED,它来自上一章。
添加硬件并向客户端发送响应
让我们再次更新我们的 webserver.js 文件。我们将使用上一章“按钮控制的 LED”中的大部分代码。
webserver.js
var http = require('http').createServer(handler); // 导入 http 服务器,并使用 handler() 函数创建服务器
var fs = require('fs'); // 导入文件系统模块
var io = require('socket.io')(http) // 导入 socket.io 模块并将 http 对象(服务器)传递给它
var Gpio = require('onoff').Gpio; // 导入 onoff 以与 GPIO 交互
var LED = new Gpio(4, 'out'); // 使用 GPIO 引脚 4 作为输出
var pushButton = new Gpio(17, 'in', 'both'); // 使用 GPIO 引脚 17 作为输入,以及 "both" 按下和松开按钮,应该处理
http.listen(8080); // 监听端口 8080
function handler (req, res) { // 创建服务器
fs.readFile(__dirname + '/public/index.html', function(err, data) { // 读取 public 文件夹中的 index.html 文件
if (err) {
res.writeHead(404, {'Content-Type': 'text/html'}); // 错误时显示 404
return res.end("404 Not Found");
}
res.writeHead(200, {'Content-Type': 'text/html'}); // 写入 HTML
res.write(data); // 写入来自 index.html 的数据
return res.end();
});
}
io.sockets.on('connection', function (socket) {// WebSocket 连接
var lightvalue = 0; // 用于当前状态的静态变量
pushButton.watch(function (err, value) { // 监控 pushButton 的硬件中断
if (err) { // 如果发生错误
console.error('发生了错误', err); // 将错误消息输出到控制台
return;
}
lightvalue = value;
socket.emit('light', lightvalue); // 将按钮状态发送到客户端
});
socket.on('light', function(data) { // 从客户端获取灯开关状态
lightvalue = data;
if (lightvalue != LED.readSync()) { // 只有在状态发生更改时才更改 LED
LED.writeSync(lightvalue); // 打开或关闭 LED
}
});
});
process.on('SIGINT', function () { // 按下 Ctrl+C
LED.writeSync(0); // 关闭 LED
LED.unexport(); // 取消导出 LED GPIO 以释放资源
pushButton.unexport(); // 取消导出按钮 GPIO 以释放资源
process.exit(); // 完全退出
});
让我们测试服务器
pi@w3demopi:~ $ node webserver.js
使用 http://[RaspberryPi_IP]:8080/ 在浏览器中打开网站。
现在服务器应该将复选框的所有更改输出到树莓派的控制台。
客户端正在将更改发送到服务器,服务器正在响应。
使用 Ctrl+c
结束程序。