raspberrypi 官網 raspberrypi 官網

互動 IoT 系統應用 - 上課教材

互動 IoT 系統應用 > 課程內容 > 第 10 章 - 使用 nodejs 控制互動元件

第 10 章 - 使用 nodejs 控制互動元件

上次更新日期 2022/11/30

我們前面學習了 python 來進行樹莓派的控制,事實上,還有其他方式可以直接控制樹莓派的 GPIO 等 IoT 的元件! 這邊我們就來測試一下使用 Node.js,使用類似 javascript 的程式碼,進行 LED 等元件的控制吧!

學習目標:

  1. 安裝 Node.js
  2. 學習 Node.js 的語法應用
  3. 使用 Node.js 控制 LED

10.1: 安裝與使用 Node.js 環境

我們前面玩了一下 python 與 IoT 元件的互動,不過,python 的語法畢竟比較特別,雖然是很簡單有趣。 那有沒有其他的程式可以取代 python 呢?其實也是有的,可以透過 Node.js 來處理!

  • 什麼是 Node.js

大家或多或少都聽過 javascript,javascript 作為網頁互動元件是相當棒的!不過,javascript 原本只能夠在瀏覽器上面運作! 也就是說, javascript 是用戶端的網頁程式語言,並不是伺服器端的程式語言。我們常聽到的伺服器端程式語言, 應該就是 PHP 了吧!所以,過去我們都知道,伺服器要用 PHP 取得資料庫內容,用戶端的互動元件則可以使用 javascript。 那,能不能 client/server 都用 javascript 呢?可以啊!那就是 Node.js 了!

Node.js 有什麼特色呢?基本上是這樣的:

  • Node.js 是開放原始碼的 server 環境
  • Node.js 也是可以跨平台運作
  • Node.js 就是在 server 上面跑 javascript
  • Node.js 的安裝

Node.js 的版本升級的很快,所以,不要隨便上網看到安裝過程就直接亂裝,那會出問題!可以前往 github 的 node.js 官網看看詳細流程, 如下連結,大概就能知道目前的最新版本以及相對的安裝方式:

我們樹莓派的系統,主要是在 ubuntu 衍生出來的版本,所以參考上面網址的 Ubuntu 系統安裝說明,很快的就可以安裝妥當了! 而安裝的指令大概有兩個,一個就是主要的 nodejs,另一個則是 nodejs 的套件管理員 (Nodejs Package Manager, npm)!就讓我們快點來安裝吧:

# 注意!務必先前往上面連結的官網,確認 node.js 的版本支援,才進行如下的安裝!
 $ curl -fsSL https://deb.nodesource.com/setup_19.x | sudo -E bash -
 $ sudo apt-get install -y nodejs

安裝會花費一小段時間,安裝妥當之後,就可以來測試一下,看看 nodejs 與 npm 的版本如何:

 $ node --version
v19.1.0

 $ npm --version
8.19.3

如上所示,那就代表安裝了 nodejs 第 19 版啦!安裝妥當之後,先來 hello 一下!

  • 第一隻 nodejs 程式

做什麼程式都是從 hello world 開始的!nodejs 比較有趣,剛剛提到了, nodejs 主要是在 Server 上面運作的腳本, 所以,建立 nodejs 時,做完之後就是得要從瀏覽器去查閱的。現在,就讓我們來建立一個 server 端的網頁程式! 透過 nodejs 建立的!

 $ mkdir ~/nodejs
 $ cd ~/nodejs
 $ vim hello.js
// 先匯入 http 的模組
var http = require('http');

// 定義好主機名稱與埠口,如果只是想要自我測試,就放到 127.0.0.1 即可
var hostname = '127.0.0.1';
var port = 8080;

// 建立 req 與 res 兩個變數,其中 res 主要是與網頁輸出的內容有關喔!
// var server = http.createServer( function(req, res) { ... } );
var server = http.createServer( function (req, res) {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Hello World my Node.js\n');
});

// 開始監聽網路服務!
server.listen( port, hostname );

 $ node  hello.js

此時系統不是卡住了!而是持續在運作 web server 的角度下!此時,請連線到樹莓派,使用另外一個終端機! 在該終端機底下查閱一下是否有 8080 的埠口,然後連線看看 web server 有沒有順利啟動呢!

 $ netstat -tlnp | egrep 'Active|Proto|8080'
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address    Foreign Address  State   PID/Program name
tcp        0      0 127.0.0.1:8080   0.0.0.0:*        LISTEN  16330/node

 $ curl http://127.0.0.1:8080
Hello World my Node.js

你可以看到,系統順利呼叫出剛剛你利用 nodejs 所建立的網頁了!要關閉這個網頁,就直接前往第一個終端機, 直接按下 [crtl]+c 之後,web server 就自動停止了!這就是我們的第一隻 nodejs 的程式囉!

10.2: Node.js 的模組安裝與呼叫

在 python 裡面,我們其實透過 gpiozero 模組來進行 LED 的控制。那麼在 nodejs 裡面,我們使用什麼模組呢? 剛剛做出 web server 使用的是 http 模組,而控制 GPIO 經常使用的就是 onoff 模組了!但是,在 onoff 模組的官網中, 就有提到,這個 onoff 並不是可以在所有平台上面運作的,目前只有在 Linux 系統上面才能夠使用!所以, onoff 預設就沒有安裝在 nodejs 的核心模組中了!那怎辦?此時要透過 npm (nodejs package manager, nodejs 套件管理員) 來處理。

  • 模組安裝到系統目錄 /usr/lib/node_modules

如果你上網查過資料,其實 nodejs 的模組安裝很簡單啊,使用 npm 加上 install 來安裝即可!但是, 不同的選項安裝的效果差很多!如果你要安裝到系統所在處,就得使用 root 權限,然後將模組安裝到 /usr/lib/node_modules/ 目錄下。如果是一般帳號,那只能夠安裝到專案目錄底下而已。讓我們都來裝裝看!

# 使用 root 權限,安裝到 /usr/lib/node_modules/ 的方法:
 $ ll /usr/lib/node_modules/
drwxr-xr-x 4 root root 4096 11月 28 10:24 corepack
drwxr-xr-x 7 root root 4096 11月 28 10:24 npm

 $ sudo npm install onoff -g
 $ sudo npm install onoff --global

 $ ll /usr/lib/node_modules/
drwxr-xr-x 4 root root 4096 11月 28 10:24 corepack
drwxr-xr-x 7 root root 4096 11月 28 10:24 npm
drwxr-xr-x 5 root root 4096 11月 30 08:59 onoff

-g 與 --global 是一樣的意思,就是全域處理!這樣就將軟體安裝到 /usr/lib/node_modules 裡面了!

  • 模組安裝到自己的專案目錄

那如果沒有加上 -g 的時候呢?會安裝到哪裡?基本上,就會安裝到該目錄底下!一般來說,安裝到自己專案的目錄下, 使用上會比較簡單方便,不需要額外設定模組目錄。而如果你的系統已經有該模組,也可以重複安裝一份該模組到自己的專案底下:

# 使用一般用戶權限,將本機的模組安裝到本專案目錄底下:
 $ cd ~/nodejs
 $ npm install /usr/lib/node_modules/onoff
 $ ll
-rw-r--r-- 1 rasppi rasppi  517 11月 28 11:42 hello.js
drwxr-xr-x 2 rasppi rasppi 4096 11月 30 10:03 node_modules
-rw-r--r-- 1 rasppi rasppi   84 11月 30 10:03 package.json
-rw-r--r-- 1 rasppi rasppi 1414 11月 30 10:03 package-lock.json

許多 nodejs 的開發者,還挺喜歡修改模組的參數,因此都可能會自己複製一份資料到專案目錄下,然後進行修改。 這樣就可以跟系統的原生模組拆開,比較好進行個人化處理。

  • 非專案目錄底下的模組呼叫方式

基本上,你要呼叫模組,可以使用絕對路徑/相對路徑的方式來找到模組的目錄,然後加以載入,例如要載入剛剛的 onoff 模組的話, 在 nodejs 檔案裡面,是可以這樣呼叫的:

var gpio = require('/usr/lib/node_modules/onoff').Gpio;

不過,這樣比較比較麻煩~有沒有直接呼叫 onoff 即可的方式?有的!將 /usr/lib/node_modules 加入到模組搜尋的路徑中就可以了! 怎麼做呢?編輯 bash 設定檔即可:

 $ vim ~/.bashrc
export NODE_PATH=/usr/lib/node_modules

 $ source ~/.bashrc

如果還是嫌麻煩,也可以在執行 node 之前,加上 NODE_PATH 變數的處理方式:

 $ NODE_PATH=/usr/lib/node_modules node hello.js
  • 新手的建議

為了避免模組之間的互相干擾,官方似乎比較建議大家,除了核心模組之外,自己安裝的模組,可以放在自己的專案目錄比較好。 放在專案目錄裡面的好處是,你可以隨意呼叫該模組或者是修改該模組的內容。缺點是,換一個目錄來撰寫專案, 該模組就得要重新安裝一份到你的新專案中~真的是各有優缺點啦~

10.3: 使用 Node.js 控制 LED

在你的專案目錄底下安裝好了 onoff 模組之後,準備來操作系統的 LED 燈吧!

  • 閃爍 LED 燈的練習

我們先來設定硬體~假設你的 LED 燈正極接到 J8:7 號腳位,負極接到 J8:9 號腳位~查詢 pinout 的輸出, 我們會知道你的 GPIO 腳位就是 4 號了!那麼我們得要來設計 4 號 GPIO 腳位為電力輸出 (output) 的定義值, 此時,撰寫一個 blink.js 的腳本,就會有點像這樣:

 $ vim blink.js
// 匯入 onoff 模組,且針對 Gpio 腳位進行處理:
var gpio = require('onoff').Gpio ;

// 設計 LED 使用 GPIO4 號為 output 功能
var LED  = new gpio( '4', 'out');

// 設計每隔 0.25 秒就會執行一次 blinkLED 的函數
var blinkTime = setInterval(blinkLED, 250);

// 設計總共會執行 10 秒,過 10 秒之後,就執行 endBlink 函數
setTimeout(endBlink, 10000);

// 這就是讓 LED 閃爍的功能函數
function blinkLED() {
        if ( LED.readSync() == 0 ) {  // 讀取 LED 腳位,若為 0,就是沒有供電
                LED.writeSync(1);     // 此時,供電為 1 (on)
        } else {
                LED.writeSync(0);     // 若是有供電,就轉為 0 !
        }
}

// 最後,建立一個停止閃爍的函數!會清除時間設計,且讓供電歸零
function endBlink() {
        clearInterval(blinkTime);
        LED.writeSync(0);
        LED.unexport();
}

 $ node blink.js

如果硬體也沒有問題,你就可以發現, LED 燈每隔 0.25 秒會開/關,每一秒會循環兩次,10 秒當然就閃爍 20 次! 基本上,javascript 也是透過 SetInterval 以及 setTimeout 功能來處理重複運作 (loop) 的工作喔!

  • 控制四個 LED 燈往同方向前進

我們在 gpiozero 的函式庫當中,使用過 4 顆 LED 的方式,現在,請使用 J8:31, J8:33, J8:35, J8:37 這四個腳位, 搭配 J8:39 的接地,同樣做成 4 顆 LED 燈的環境,接下來,我們將來設計 LED 燈持續朝向同一個方向閃爍的功能:

 $ vim led-1.js
// 匯入 onoff 模組,且針對 Gpio 腳位進行處理:
var gpio = require('onoff').Gpio ;

// 設計 LED 使用 GPIO 的腳位
var LED6  = new gpio( 6, 'out');
var LED13 = new gpio(13, 'out');
var LED19 = new gpio(19, 'out');
var LED26 = new gpio(26, 'out');

// 建立名為 leds 的陣列變數,內含有 4 個 LED 的設定。 leds[0] 是 LED6 的意思!
var leds = [ LED6, LED13, LED19, LED26 ];

// 定義從 0 號開始的 led 號碼
var lednow = 0;

var ledTime = setInterval(flowLED, 100);
setTimeout(clearLED, 10000);

function flowLED() {
        // 將所有的 LED 燈抓出來關掉!
        leds.forEach( function(thisled) {
                thisled.writeSync(0);
        } );
        //將目前的 LED 燈點亮
        leds[lednow].writeSync(1);
	// 累加下一次的 LED 燈號號碼
        lednow++;
	// 如果燈號大於目前既有的數量,就回到 1 !
        if ( lednow >= leds.length ) lednow = 0;
}

function clearLED() {
        clearInterval(ledTime);
        leds.forEach( function(thisled) {
                thisled.writeSync(0);
                thisled.unexport();
        } );
}

 $ node led-1.py

執行之後,你就會看到燈號開始連續閃爍了!

10.4: 當週實做

讓燈號可以產生霹靂燈

  • 將 led-1.js 另存成為 led-2.js
  • 目前有個想法,增加另外一個變數,名為 dir(方向的縮寫),可以讓燈號左、右移動,預設 dir 為 0
  • 當 lednow 為 0 時, dir 就變成 0,當 lednow 為陣列值 (最大值) - 1,則 dir 為 1
  • 當 dir 為 0 的時候,就產生 lednow++,當 dir 為 1 的時候,就產生 lednow--

完成程式撰寫後,直接『 node led-2.js 』看看燈號能不能變成霹靂燈即可。

  • 參考資料
  1. W3C schools 關於 Node.js 與樹莓派的教材:
    https://www.w3schools.com/nodejs/nodejs_raspberrypi.asp
  2. Node.js 官方網站:
    https://nodejs.org/en/
  3. Node.js 在不同系統上面的安裝流程:
    https://github.com/nodesource/distributions/blob/master/README.md
  4. Node.js 官網的 hello world 範本:
    https://nodejs.org/en/docs/guides/getting-started-guide/
  5. onoff npm 說明:
    https://www.npmjs.com/package/onoff

...