網路燈是一個簡單的物聯網專案,使用網頁框架 Next.js 與機器人控制系統 Johnny-five 來遠端控制Led燈。
GitHub Repo : Led Delivery Network by VFLC
Node.js建立於chrome的V8引擎之上,被設計來開發網頁應用程式,安裝後使用者能夠在瀏覽器外運行Javascript程式語言。
套件管理工具(npm)會在安裝node.js時一併安裝,使用者可以將寫好的程式碼包裝成"package",透過npm上傳至雲端與其他開發者分享。目前有相當多種類的package可以使用,像是網頁框架、圖像處理、Email以及更多。
前往 https://github.com/coreybutler/nvm-windows/releases 並下載 nvm-setup.exe。安裝完成後以系統管理員模式執行 命令提示字元,並輸入 nvm install 12.22.10 來安裝版本12的node.js。這需要一點時間,完成後照著終端機的指示輸入 nvm use 12.22.10 便完成安裝了。
如果安裝成功,在輸入 node -v 時會看到 v12.22.10。
VS code本身有內建支援JavaScript, TypeScript和Node.js,不過也有社群所維護的擴充(extension)功能來支援其他程式語言。VS code整合了debugging、git和終端機,並提供強大的熱鍵(keyboard shortcuts)來加速開發流程。
除了支援程式語言的擴充外,另外還有其他像是linter、formatter、vim、git相關的擴充,可以根據喜好來客製化開發體驗。
前往VS code官方網站下載並安裝
https://code.visualstudio.com
前往 左側側欄 > 方格堆疊 > 在搜尋欄位分別貼上以下名稱 > 點擊安裝按鈕
VisualStudioExptTeam.vscodeintellicode - 提供程式碼提示
rangav.vscode-thunder-client - http client
安裝完成後,會看到左側的搜尋欄位多出一個閃電圖案的按鈕,那是thunder client。
Javascript,簡稱JS。和python一樣屬於高階語言,javascript可以被即時的編譯,動態類別,單一執行緒(single threaded)卻使用事件循環(event loop)做到異步執行(async/await)。
大部分網頁皆是使用javascript來更新、控制html與css的狀態。
Typescript的語法和Javascrpt沒有太大的差別,而是額外加入了新的語法和靜態類別檢查,在撰寫程式碼的同時提供嚴格但客製化的變數類型檢查,這改善了Javascript的變數為動態型別的缺點。並且在編譯過後能夠指定產生特定版本的Javascript程式碼,提供更好的瀏覽器支援。
這邊整理了一些本次實作會用到的概念,上面的區塊是python的語法,下面是對應的javascript/typescript碼。
React.js為三大使用者介面框架(User Interface Framework)之一,由Facebook和社群共同維護與開發,另外兩個框架分別為Google的Angular以及Vue。
React.js擁有大量由社群共同維護的套件,為React.js提供額外功能。另外,也有許多建立於React.js之上的網頁框架,如Next.js、Remix.js、Blitz.js、Redwood.js。
Next.js是網頁框架,它建立於React.js之上。除了server side rendering, 支援TypeScript, route pre-fetching和其他網頁技術之外,Next.js也提供能夠執行後端程式碼的api route,讓使用者能夠在一個專案內同時完成前後端的開發,對於小型專案的建置非常的方便。
使用以下指令來初始化一個包含Next.js並使用Typescript程式語言撰寫的node.js專案
npx create-next-app . --ts
使用以下指令來啟動Next.js伺服器
npm run dev
啟動伺服器後,前往網址 http://localhost:3000 會看到網頁會顯示 "Hello",這是我們剛剛在檔案 pages/index.tsx 設定的。
前往 http://localhost:3000/api/led,則會看到網頁顯示 "Hello from api route",這是在檔案 pages/api/led.ts 設定的。
可以注意到雖然兩個網頁看起來是同一個樣子,但對應的兩個檔案內容完全不一樣。
這兩個檔案在類型上有所差異,前者(pages/index.tsx)是正常的網頁,後者(pages/api/led.ts)則是伺服器。Next.js會將放在api資料夾裡的所有檔案都當作是伺服器的程式碼,這個伺服器會在對應的網址第一次被拜訪時初始化,整個檔案從第一行開始由上至下執行,並在包含首次的每一次被拜訪時將 res.end() 內的東西當作回傳值。因此,"Hello from api route" 其實是被伺服器作為回傳值回傳的字串,請求(request)則是瀏覽器自動幫我們發出的,剛剛安裝的 thunder client 也是用來發出請求的工具。
按鈕是網頁常見的元素,將pages/index.tsx修改成以下的程式片段來為網頁加入一個按下後在瀏覽器的console顯示文字的按鈕。前往 http://localhost:3000,按下 f12 來開啟console,並按下剛才加入的按鈕
第4行:定義函式 greet
第5行:當greet函式被呼叫後,在console顯示 "hey"
第10行:使用<button></button>加上按鈕。並使用 onClick={ } 為按鈕加上被點擊後執行的 function
點選剛剛安裝到VS Code的thunder client(左側的閃電圖案按鈕),並點選 New Request。接著,在左上方的 send按鈕 旁的欄位輸入 http://localhost:3000/api/led 後按下 send按鈕。經過一小段後會看到右側顯示 "Hello from api route"。
我們透過剛剛的一連串步驟發送了一個 get request 到Next.js的api route。get是其中一種發送requset的方法(method),這次的實作只會用到get request。
可以透過在網址後面加上 "?key=value" 來加上查詢參數(query parameter)。將pages/api/led.ts改成以下程式碼片段後,再次發送一個包含query的get request,query將會被原封不動地作為回傳值回傳。
可以使用 req.query.key 來取得 key 的值。如果該 key 沒有在query parameter內,則會回傳 undefined,類似python的 None。
現在我們要改用 axios 套件來以程式發送 http request。請ctrl+c終止終端機,並使用以下指令安裝 axios 套件
npm i axios
接著,將 pages/index.tsx 的內容改成以下程式碼片段
第1行:引入 axios 套件
第5行:定義非同步函式(async function) led,並接收一個參數 status。使用typescript的類型系統(type system)來規定參數只能是 "on" 或是 "off"
第6行:建立變數 url,儲存做為發送request的網址。注意到我們沒有把完整的網址寫出來,因為axios會將 /api/led 轉換成 http://localhost:3000/api/led
第7行:使用函式 axios.get() 來發送get requst到第6行設定的url,並將回傳的值存入變數 result
第13、14行:加入兩個按鈕,在按下後分別以第5行規定的參數 "on" / "off" 呼叫led函式
在終端機執行 npm run dev ,前往 http://localhost:3000 。在按下按鈕後將會在終端機,或者瀏覽器的console看到 {on: '1'} 或是 {off: '1'}。
Johnny-five是一個機器人控制系統的套件(package),讓使用者能夠使用Javascript來控制Arduino、Raspberry Pi等微處理器/單板電腦。優點是能與nodejs平台的各種套件結合,例如網頁框架、cli框架、各種api的client以及更多。
Johnny-five使用 firmware "StandardFirmataPlus"來與各種板子溝通,因此在使用前需要先燒錄製板子上。可以按照以下步驟安裝或參考官方文件:
安裝、開啟Arduino IDE,將板子插入電腦
點選 工具 > 開發版 選擇正確的板子與port
點選 檔案 > 範例 > Firmata > StandardFirmataPlus
點選 上傳 燒錄到板子上
使用以下指令來將Johnny-five加入現有專案
npm i johnny-five
使用以下指令來將Johnny-five的Typescript設定檔加入現有專案作為開發用套件(dev dependency)
npm i --save-dev @types/johnny-five
正極(長腳)插入 pin 13,負極(短腳)插入 GND
現在,我們要將Johnny-five加入伺服器端。Johnny-five負責控制led燈,當api route第一次收到request時,將會初始化johnny-five。將 pages/api/led.ts 的內容改成以下程式碼片段
第1行:從 Johnny-five 套件引入分別用來控制板子和LED的 Board 和 Led
第4行:初始化板子,並將互動式終端機關閉
第6行:當板子初始化完成,可以使用時執行7~10行的內容
第9行:初始化Led燈,並將 pin 腳位設為 13。這表示你需要將Led燈的正極(長腳)插入Arduino開發版的pin 13,並將負極(短腳)插入pin 13旁邊的GND
第10行:讓Led以500毫秒的間隔不斷循環開關
第16行:使用if語法判斷,如果req.query包含 on ,也就是req.query.on不是falsy的undefine時,會在第17行回傳字串 "on"
第24行:如果req.query沒有包含 on 或 off,則直接將req.query原封不動地回傳
更新完成後,關閉伺服器並再次開啟,會發現Led沒有反應。接著,前往 http://localhost:3000 並按下任一按鈕,會發現Led開始閃爍,這代表我們成功地將Johnny-five加入伺服器了。
接下來我們要控制Led的開關。請使用以下指令安裝 nanoevents 套件
npm i nanoevents
接著,將 pages/api/led.ts 的內容改成以下程式碼片段
第2行:引入 nanoevents 套件
第5行:初始化事件監聽器
第13、14行:為事件監聽器加入兩個監聽值 "on"、"off",當接收到事件(event)時會執行對應的函式。其中,led.on() 用來點亮Led,led.off() 則是關閉
第21行:當 req.query 包含 on 時,發出(emit) "on" 事件。發出事件後,第13行會接收到事件,並執行 led.on()
更新完成後,再次開啟伺服器,前往 http://localhost:3000 並只按一下按鈕 on,會發現Led閃爍了兩下後就暗掉了,並沒有照著我們寫的程式開啟Led燈。
這是因為當伺服器在接收到第一次的請求時會從第一行開始由上往下執行,當執行到第6行時開始初始化板子,接著往下執行到第21行時emit "on" 事件,問題就發生在這裡。當收到request且emit "on" 事件後,如果第6行的初始化還沒結束,也就是第8~15行 "應該在初始化結束後執行的程式" 還沒被執行時,第13、14行設定事件監聽器的程式碼自然也就還不會被執行。
為了解決這個問題,我們可以在使用者按下按鈕前,先發送一個請求來初始化伺服器,藉此提前初始化板子。
hook是react functional component的核心元素之一,在React.js版本16.8更新時加入,可以在官方網站得到更多資訊。
useEffect會在他的依賴變數陣列(dependencies array)內的值改變時,執行side effect。本次實作不利用useEffect的原理,在網頁載入完成後立刻發送一次請求到伺服器,並且確保只會發送剛好一次請求。
回到 pages/index.tsx 後,將useEffect加入現有的程式碼,將dependencies array設為空陣列後,加入發出請求到伺服器的side effect。因為伺服器的寫法,即使網址一樣是 http://localhost:3000/api/led,只要不加上query parameter就不會被判定成想要開啟或關閉Led燈。
第3行:從 react 引入 useEffect hook
第12行:將side effect設定為在頁面一載入完成後發送一個get requsst到 http://localhost:3000/api/led
雖然已經解決伺服器初始化時遇到的問題了,但到目前為止我們都只能在本地端控制Led燈。透過以下指令安裝並執行localtunnel來將所有request從localtunnel提供的網址proxy到本地端的伺服器
安裝
npm i -g localtunnel
啟動
lt -p 3000
啟動後終端機會顯示一個網址,現在大家可以透過拜訪那個網址來訪問你的電腦了。
到這邊就完成本次實作了。接下來請使用以下指令來建置(build)和執行Next.js專案。
npm run build
npm run start
本次實作完整的程式碼可以在 https://github.com/bwsix/led-delivery-network 被找到。
左側的影片是本次實作的原型:將Led換成檯燈,並使用建立於webrtc之上的函式庫Peer.js加入即時的影像串流。