###### tags: `FM611A` `MQTT`
# MQTT 版 Lab 06 語音聲控電源插座實作教學
在 《[AI × LINE 聲控/人臉辨識生活大應用](https://www.flag.com.tw/maker/FM611A)》的〈Lab 06 語音聲控電源插座〉中, 由於 D1 mini 是**在區域網路**中, 如果你不在該區域網路下就無法使用, 減損了這個實驗的應用廣度。在這篇教學中, 我們就把原本的實驗改成使用在物聯網應用**相當普遍的 MQTT 傳輸協定**, 讓網頁改放至公開的網路上, 並透過 MQTT 當中介傳輸資料, 讓 D1 mini 可以接收到網頁語音辨識的結果。
## MQTT 協定簡介
傳統網頁使用的 http 因為是泛用的協定, 必須考量到各種情況, 因此在傳輸資料時, 光是表頭的資料就要耗掉一定的傳輸量, 甚至常常有表頭的大小是實際傳輸資料量的數十甚至數百倍的狀況。如果採用 https, 又再加上電子憑證處理與加解密, 更是加重運算的負擔。
由於物聯網應用中, 個別裝置傳輸的資料量通常不大, 像是溫濕度數值、開關訊號等, 都是幾個位元組而以, 使用 http 不但浪費傳輸頻寬, 也會加重單晶片或是控制板的負擔。因此便有了**專門為物聯網應用而生的傳輸協定--MQTT**。
MQTT 傳輸協定主要區分為三種角色:**發佈端 (publisher)、中介伺服器 (broker)、訂閱端 (subscriber)**:
```graphviz
digraph hierarchy {
nodesep=1.0 // increases the separation between nodes
node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour
edge [color=Blue, style=dashed] //All the lines look like this
發佈端->中介伺服器 [label="發佈"]
中介伺服器->訂閱端 [label="轉送"]
訂閱端->中介伺服器 [label="訂閱"]
{rank=same;發佈端 中介伺服器 訂閱端} // Put them on the same level
}
```
- **中介伺服器**:建立傳輸資料的通道, 稱為**主題 (topic)**, 負責轉送發佈端送出的資料給訂閱端。
- **發送端**:可將資料送至中介伺服器上的特定主題。
- **訂閱端**:可向中介伺服器訂閱主題, 並在發送端傳送資料到該主題時自動收到由中介伺服器轉送過來的資料。
由於發送端與訂閱端都是與中介伺服器溝通, 因此統稱為 **MQTT 用戶端 (client)**, 兩者並不直接傳輸資料。每一個裝置都可以連上中介伺服器成為發佈端或是訂閱端, 甚至同時是發佈端與訂閱端。
這樣的作法連帶的好處就是發佈端與訂閱端都只要能夠連上外部網路接到 MQTT 中介伺服器即可相互通訊, 不會因為**防火牆**或是**路由器**阻擋外部裝置連入而無法傳遞資料。
## 實驗架構
本實驗就以語音辨識網頁為發佈端, Adafruit IO(簡稱 AIO) 為中介伺服器, D1 mini 控制板為訂閱端, 利用 MQTT 傳輸語音辨識結果:
```graphviz
digraph hierarchy {
nodesep=1.0 // increases the separation between nodes
node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour
edge [color=Blue, style=dashed] //All the lines look like this
p[label="語音辨識網頁"]
m[label="AIO 中介伺服器"]
s[label="D1 mini 控制板"]
p->m [label="發佈"]
m->s [label="轉送"]
s->m [label="訂閱"]
{rank=same;p m s} // Put them on the same level
}
```
每當語音辨識網頁辨識出開燈的口令, 就要傳送 "100" 到中介伺服器的 voice 主題上;若是關燈的口令, 則要傳送 "0" 到 voice 主題上。D1 mini 則是要向中介伺服器訂閱 voice 主題, 就會自動在辨識出開關燈口令時收到 "100" 或是 "0", 即可依據收到的資料做出對應控制的動作了。
我們已經將語音辨識網頁準備好放在 https://flagtech.github.io/FM611A/voice_recog_mqtt.html, 只要使用瀏覽器連上就可以使用:

後續教學我們將著重在 AIO 以及 D1 mini 的程式上。
## 申請 AIO 帳號
要完成 MQTT 傳輸, 最重要的就是中介伺服器, 請依照以下步驟到 AIO 註冊帳號, 並且建立 MQTT 傳輸主題:
1. 請連線至 [Adafruit IO](https://io.adafruit.com/):

點選**Get Started fir Free**
1. 填寫必要欄位後按 **CREATE ACCOUNT** 建立帳號:

1. 建立後點選上方的 **IO** 頁次:

1. 點選 **Feeds** 頁次後按 **view all**:

按 **New Feed**

填入名稱為 "voice" 後按 **Create** 建立:

這裡建立的 feed 就是稍後 MQTT 傳輸時的主題。
1. 按一下網頁右上方的 **My Key**:

這裡顯示的 **Username** 與 **Active Key** 就是稍後用戶端連上 AIO 時要填入的使用者名稱與金鑰:

請把網頁停留在這個畫面, 方便稍後複製使用。
## 使用語音辨識網頁測試 AIO
建立好 AIO 帳號與傳輸主題後, 就可以使用語音辨識網頁測試:
1. 請使用 Chrome 瀏覽器連至 [https://flagtech.github.io/FM611A/voice_recog_mqtt.html](https://flagtech.github.io/FM611A/voice_recog_mqtt.html), 請在網頁下方欄位分別填入剛剛看到的 AIO 使用者名稱與金鑰, 然後按一下**開始辨識聲音**:

按**允許**以便能夠使用麥克風蒐集聲音:

上方會出現訊息告知以成功連至 MQTT 伺服器:

1. 此時即可說出『打開』口令, 若成功辨識, 便會出現訊息表示已將辨識結果傳送至 AIO:

1. 切換到 AIO 網頁, 關閉剛剛的金鑰顯示畫面後按剛剛建立的 voice:

往下捲就會看到剛剛打開口令傳送的 100:

1. 你可以試看看講『關閉』或是『打開』多次, 就可以看到每次辨識結果送來的資料:

:::info
有關語音辨識網頁的內容, 有興趣的人可以自行檢視原始碼, 我們並不會詳細說明。
:::
## FlagsBlock 程式
現在已經可以讓語音辨識網頁將辨識結果發佈至 AIO 上, 最後一步就是要讓 D1 mini 程式可以跟 AIO 訂閱資料, 並依據資料執行對應的開關動作了:
:::info
你可以在這裡[下載程式檔](https://github.com/FlagTech/FM611A_Extra/archive/8be273da9fc6abe8b30ccc43bc2a7d57ecba2d94.zip)
:::
1. 請進入 FlagsBlock, 完成以下積木, 這部分與其他實驗相同, 不再贅述:

請務必記得修改成你自己的無線網路名稱與密碼。
2. 定義處理收到 MQTT 訊息時的處理, 請先加入**函式/定義函式**積木後更改名稱為 "取得遠端指令":

加入**流程控制/如果**積木, 並展開**否則如果**分支:

加入 2 個**邏輯/=** 積木:

在 **=** 積木內部左側的欄位都放入 **ESP8266 物聯網/新訊息內容**積木, 右側分別放入**文字/❝❞**積木後填入 100 與 0:

最後填入收到個別訊息時要執行的開關繼電器動作:

1. 指定 MQTT 中介伺服器的位址與通訊埠邊號, 請加入**ESP8266 物聯網/啟用網址...埠號...的 MQTT 服務**, 填入 AIO 中介伺服器的網址 io.adafruit.com 與埠號 1883:

再加入**ESP8266 物聯網/使用...函式接收 MQTT 訊息**積木, 並選取剛剛設計的**取得遠端指令**函式, 這表示當收到新的訊息時, 會自動執行這個函式:

1. 最後就是實際連接 AIO 中介伺服器並接收資料的程式了, 請加入**流程控制/重複當**積木、**邏輯/非**積木、**ESP8266 物聯網/已連上 MQTT 伺服器?**積木後如下組合:

再加入**流程控制/如果**積木後展開**否則如果**分支, 如下組合:

加入**ESP8266 物聯網/以...名稱...帳號...密碼連上 MQTT 伺服器**積木, 名稱欄位請留空, 然後在**帳號**與**密碼**欄位填入剛剛看到的 AIO 名稱與金鑰:

加入**ESP8266 物聯網/訂閱 MQTT 主題**積木在連線成功時訂閱主題, 注意到雖然我們建立的 Feed 名稱是 "voice", 但是 AIO 規定主題要再加上使用者名稱與 "feeds" 字樣, 並以 "/" 分隔, 所以完整的主題是 "你的使用者名稱/feed/voice":

上例就是因為我的使用者名稱是 "meeboxflag", 所以訂閱的主題是 "meeboxflag/feeds/voice"。接著再加上**時間/暫停 1000 毫秒**積木在無法連上 MQTT 伺服器時等待 1 秒後再重試:

最後最重要的是加上**ESP8266 物聯網/處理 MQTT 請求**積木, 以便能夠接收 MQTT 伺服器送來的資料:

1. 這樣就完成了 D1 mini 的程式, 請記得修改你的無線網路名稱與密碼、AIO 使用者名稱與金鑰、訂閱主題中的 AIO 使用者名稱後上傳程式, 利用語音辨識網頁辨識口令, 即可控制接在 D1 mini 上的繼電器了。