Node.js 和 树莓派(Raspberry Pi) - 带有 WebSocket 的 Web 服务器

什么是 WebSocket?

WebSocket 支持通过网络实时进行双向通信。

WebSocket 可以与普通的 HTTP 服务器一起运行。 您可以在 Web 浏览器中单击一个按钮,然后在您的树莓派(Raspberry Pi) 上启用一个 GPIO,它会打开您家中的灯。 一切都是实时的,双向沟通!

在本章中,我们将使用 WebSocket 建立一个 Web 服务器。 然后创建一个浏览器 UI 以与我们之前的使用按钮打开和关闭 LED 的示例进行交互。


我需要什么?

对于本教程,您需要一个树莓派(Raspberry Pi)。 在我们的示例中,我们使用的是树莓派(Raspberry Pi) 3,但本教程应该适用于大多数版本。

为此,您需要:

单击上面列表中的链接了解不同组件的说明。

注释:您需要的电阻器可能与我们使用的电阻器不同,具体取决于您使用的 LED 类型。 大多数小型 LED 只需要一个小电阻,大约 200-500 欧姆。 您使用的确切值通常并不重要,但电阻值越小,LED 就会越亮。

与我们之前的示例相比,我们唯一需要做的就是设置一个 Web 服务器,并安装 socket.io 模块。


用于树莓派(Raspberry Pi) 和 Node.js 的网络服务器

按照本 Node.js 教程中的前面章节,让我们设置一个可以提供 HTML 文件的 Web 服务器。

在我们的 "nodetest" 目录中创建一个新目录,我们可以将其用于静态 html 文件:

pi@w3demopi:~/nodetest $ mkdir public

现在让我们设置一个网络服务器。 创建一个 Node.js 文件,该文件打开请求的文件并将内容返回给客户端。 如果出现任何问题,抛出 404 错误。

pi@w3demopi:~/nodetest $ nano webserver.js

webserver.js:

var http = require('http').createServer(handler); //require http server, and create server with function handler()
var fs = require('fs'); //require filesystem module

http.listen(8080); //listen to port 8080

function handler (req, res) { //create server
  fs.readFile(__dirname + '/public/index.html', function(err, data) { //read file index.html in public folder
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error
      return res.end("404 Not Found");
    }
    res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML
    res.write(data); //write data from 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>Control LED light</h1>
<input id="light" type="checkbox">LED

</body>
</html>

这个文件还没有任何功能。 现在它只是一个占位符。 让我们看看网络服务器是否正常工作:

pi@w3demopi:~/nodetest/public $ cd ..
pi@w3demopi:~/nodetest $ node webserver.js

使用 http://[RaspberryPi_IP]:8080/ 在浏览器中打开网站:

网络服务器现在应该已启动并运行,我们可以继续进行 WebSocket 部分。


为 Node.js 安装 socket.io

设置好网络服务器后,将您的树莓派(Raspberry Pi) 系统包更新到最新版本。

更新你的系统包列表:

pi@w3demopi:~ $ sudo apt-get update

将所有已安装的软件包升级到最新版本:

pi@w3demopi:~ $ sudo apt-get dist-upgrade

定期执行此操作将使您的树莓派(Raspberry Pi) 安装保持最新状态。

要下载和安装最新版本的 socket.io,请使用以下命令:

pi@w3demopi:~ $ npm install socket.io --save


将 WebSocket 添加到我们的 Web 服务器

现在我们可以在我们的应用程序中使用 WebSocket。 让我们更新我们的 index.html 文件:

index.html:

<!DOCTYPE html>
<html>
<body>

<h1>Control LED light</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> <!-- include socket.io client side script -->
<script>
var socket = io(); //load socket.io-client and connect to the host that serves the page
window.addEventListener("load", function(){ //when page loads
  var lightbox = document.getElementById("light");
  lightbox.addEventListener("change", function() { //add event listener for when checkbox changes
    socket.emit("light", Number(this.checked)); //send button status to server (as 1 or 0)
  });
});
socket.on('light', function (data) { //get button status from client
  document.getElementById("light").checked = data; //change checkbox according to push button on树莓派(Raspberry Pi)
  socket.emit("light", data); //send push button status to back to server
});
</script>

</body>
</html>

还有我们的 webserver.js 文件:

webserver.js:

var http = require('http').createServer(handler); //require http server, and create server with function handler()
var fs = require('fs'); //require filesystem module
var io = require('socket.io')(http) //require socket.io module and pass the http object (server)

http.listen(8080); //listen to port 8080

function handler (req, res) { //create server
  fs.readFile(__dirname + '/public/index.html', function(err, data) { //read file index.html in public folder
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error
      return res.end("404 Not Found");
    }
    res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML
    res.write(data); //write data from index.html
    return res.end();
  });
}

io.sockets.on('connection', function (socket) {// WebSocket Connection
  var lightvalue = 0; //static variable for current status
  socket.on('light', function(data) { //get light switch status from client
    lightvalue = data;
    if (lightvalue) {
      console.log(lightvalue); //turn LED on or off, for now we will just show it in console.log
    }
  });
});

让我们测试服务器:

pi@w3demopi:~ $ node webserver.js

使用 http://[RaspberryPi_IP]:8080/ 在浏览器中打开网站:

现在服务器应该将复选框的所有更改输出到树莓派(Raspberry Pi) 上的控制台。

客户端正在向服务器发送更改,服务器正在响应。

让我们添加上一章中的按钮控制的 LED


添加硬件,并向客户端发送响应

让我们再次更新我们的 webserver.js 文件。 我们将使用按钮控制 LED 章节中的大量代码。

webserver.js:

var http = require('http').createServer(handler); //require http server, and create server with function handler()
var fs = require('fs'); //require filesystem module
var io = require('socket.io')(http) //require socket.io module and pass the http object (server)
var Gpio = require('onoff').Gpio; //include onoff to interact with the GPIO
var LED = new Gpio(4, 'out'); //use GPIO pin 4 as output
var pushButton = new Gpio(17, 'in', 'both'); //use GPIO pin 17 as input, and 'both' button presses, and releases should be handled

http.listen(8080); //listen to port 8080

function handler (req, res) { //create server
  fs.readFile(__dirname + '/public/index.html', function(err, data) { //read file index.html in public folder
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error
      return res.end("404 Not Found");
    }
    res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML
    res.write(data); //write data from index.html
    return res.end();
  });
}

io.sockets.on('connection', function (socket) {// WebSocket Connection
  var lightvalue = 0; //static variable for current status
  pushButton.watch(function (err, value) { //Watch for hardware interrupts on pushButton
    if (err) { //if an error
      console.error('There was an error', err); //output error message to console
      return;
    }
    lightvalue = value;
    socket.emit('light', lightvalue); //send button status to client
  });
  socket.on('light', function(data) { //get light switch status from client
    lightvalue = data;
    if (lightvalue != LED.readSync()) { //only change LED if status has changed
      LED.writeSync(lightvalue); //turn LED on or off
    }
  });
});

process.on('SIGINT', function () { //on ctrl+c
  LED.writeSync(0); // Turn LED off
  LED.unexport(); // Unexport LED GPIO to free resources
  pushButton.unexport(); // Unexport Button GPIO to free resources
  process.exit(); //exit completely
});

让我们测试服务器:

pi@w3demopi:~ $ node webserver.js

使用 http://[RaspberryPi_IP]:8080/ 在浏览器中打开网站:

现在服务器应该将复选框的所有更改输出到树莓派(Raspberry Pi) 上的控制台。

客户端正在向服务器发送更改,服务器正在响应。

Ctrl+c 结束程序。