Node.js 和 Raspberry Pi - 帶WebSocket的Web伺服器
什麼是WebSocket?
WebSocket 支援即時雙向通訊。
WebSocket 可以與常規HTTP伺服器一起執行。你可以點選網頁瀏覽器中的按鈕,然後啟用Raspberry Pi上的GPIO,從而開啟你家中的燈。所有這些都是即時的,並且通訊是雙向的!
在本章中,我們將設定一個帶WebSocket的Web伺服器。然後建立一個瀏覽器UI來與我們之前關於用按鈕控制LED開關的示例進行互動。
我需要什麼?
本教程需要一臺Raspberry Pi。在我們的示例中,我們使用的是Raspberry Pi 3,但本教程應該適用於大多數版本。
For this you need
- A Raspberry Pi with Raspian, internet, SSH, with Node.js installed
- Node.js 的 onoff 模組
- Node.js 的 socket.io 模組
- 1 x Breadboard
- 1 x 68 歐姆電阻
- 1 x 1k 歐姆電阻
- 1 x 直插 LED
- 1 x 按鈕
- 4 x Female to male jumper wires
- 1 x 公對公跳線
Click the links in the list above for descriptions of the different components.
Note: The resistor you need can be different from what we use depending on the type of LED you use. Most small LEDs only need a small resistor, around 200-500 ohms. It is generally not critical what exact value you use, but the smaller the value of the resistor, the brighter the LED will shine.
與我們之前的示例相比,我們唯一需要新增的是設定一個Web伺服器,並安裝socket.io模組。
Raspberry Pi 和 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伺服器後,將你的Raspberry Pi系統包更新到最新版本。
Update your system package list
pi@w3demopi:~ $ sudo apt-get update
將所有已安裝的軟體包升級到最新版本
pi@w3demopi:~ $ sudo apt-get dist-upgrade
定期執行此操作可以保持你的Raspberry Pi安裝的最新。
要下載並安裝最新版本的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; //根據Raspberry Pi上的按鈕更改複選框
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,目前我們只在控制檯中顯示它
}
});
});
讓我們測試一下伺服器
pi@w3demopi:~ $ node webserver.js
使用 http://[RaspberryPi_IP]:8080/ 在瀏覽器中開啟網站
現在伺服器應該將所有複選框的變化輸出到Raspberry Pi上的控制檯中。
客戶端正在傳送變化到伺服器,伺服器正在響應。
讓我們新增由按鈕控制的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('There was an 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(); //取消匯出Button GPIO以釋放資源
process.exit(); //完全退出
});
讓我們測試一下伺服器
pi@w3demopi:~ $ node webserver.js
使用 http://[RaspberryPi_IP]:8080/ 在瀏覽器中開啟網站
現在伺服器應該將所有複選框的變化輸出到Raspberry Pi上的控制檯中。
客戶端正在傳送變化到伺服器,伺服器正在響應。
End the program with Ctrl+c
.